- Home /
Best way to click on a object behind other
Hi. This is a bit more general question.
I made a quick illustration about my problem
And my question is: what would be the best way to ensure that when I click on any of those objects coloured area, then that object actually gets clicked/hit, even if it actually is behind other objects. Basically all the objects are simple plane meshes with some textures on them. The textures are transparent where theres no colour. So for example in my illustration if I would click in the marked point I would like the green sphere to get selected, but the problem is that it lays behind both of the other objects mesh/collider, so actually the red cross would get selected.
Now I did some research and there seem to be some solutions out there that detect if the hit point pixel is transparent or not. For example this one but Im worried that even if I could detect that I clicked on the transparent part of Object Z1 then I wouldn't know how to make the ray keep going until it hits a actual pixel. Another thought I had was to make custom meshes or even only collider meshes for each of the object's, but that seems a lot of manual labor.
So in conclusion should I go with:
a) Detecting pixel alphas
b) Custom meshes for each object
c) Something totally different ???
Any guidance much appreciated!
Sry if too long for Answers.
Answer by Professor Snake · Sep 29, 2013 at 06:53 PM
You can use RaycastHit.textureCoord to get the UV coordinates of the texture at the point you hit the object. Then you need to use Texture2D.GetPixel, and check that pixel's alpha value. If the alpha value is near zero (or below a transparency threshold), temporarily disable that object's collider and do another identical raycast, repeating the process until you hit something or the raycast returns false. Don't forget to re-enable the colliders once everything is finished.
Thank you for the answer. All the things you talk about seem doable. How should I deal with the objects that get they'r colliders disabled? Should I collect them in to an array? Because as in my example I should disable two objects before I get to the green sphere.
You should indeed collect them in an array. If you don't want to use the Array class, use this simple function to increase the size of a "normal" array:
function IncreaseArray(arr:Transform[]):Transform[]{
if(arr.length==0){
return new Transform[1];
}
var newArr:Transform[]=new Transform[arr.length+1];
for(var i:int=0;i<arr.length;i++)
newArr[i]=arr[i];
return newArr;
}
Then you'd just do
disabledObjectsArray=IncreaseArray(disabledObjectsArray);
disabledObjectsArray[disabledObjectsArray.length-1]=what we hit;
Answer by Hiilo · Oct 27, 2013 at 11:40 AM
Thought I'll share my final code I got working.
using UnityEngine;
using System.Collections;
using System.Linq;
using System.Collections.Generic;
using System;
public class AlphaTest : MonoBehaviour {
private RaycastHit[] hits;
private Ray ray;
Camera myCam;
private GameObject selectedPlayer = null;
List<GameObject> disabledGameobjects = new List<GameObject>();
List<RaycastResult> hitList = new List<RaycastResult>();
// Use this for initialization
void Start () {
myCam=Camera.main;
}
public class RaycastResult: IComparable<RaycastResult>
{
public float distance;
public Collider collider;
public Vector2 textureCoord;
public RaycastResult(RaycastHit hit)
{
distance = hit.distance;
collider = hit.collider;
textureCoord = hit.textureCoord;
}
public int CompareTo(RaycastResult other)
{
return distance.CompareTo(other.distance);
}
}
void SortDistances()
{
hitList.Clear();
hits= Physics.RaycastAll(ray);
foreach(RaycastHit hit in hits)
{
hitList.Add(new RaycastResult(hit));
}
hitList.Sort();
}
void Update() {
ray = myCam.ScreenPointToRay(Input.mousePosition);
//When mouse left button is pressed down | This Fires only once
if(Input.GetMouseButtonDown(0)){
SortDistances();
Debug.Log("HIT-----------------------------");
foreach(RaycastHit hit in hits){
Debug.Log(hit.collider.name+""+hit.distance);
}
Debug.Log("LIST-----------------------------");
foreach(RaycastResult listItem in hitList){
Debug.Log(listItem.distance);
}
for(int i=0;i<hitList.Count;i++)
{
if(hitList[i].collider.tag=="Player")
{
selectedPlayer = hitList[i].collider.transform.gameObject;
Renderer renderer = hitList[i].collider.renderer;
MeshCollider meshCollider = hitList[i].collider as MeshCollider;
if (renderer == null || renderer.sharedMaterial == null || renderer.sharedMaterial.mainTexture == null || meshCollider == null)
return;
Texture2D texture = renderer.material.mainTexture as Texture2D;
Vector2 pixelUV = hitList[i].textureCoord;
pixelUV.x *= texture.width;
pixelUV.y *= texture.height;
Color pixel = texture.GetPixel((int)pixelUV.x, (int)pixelUV.y);
//Debug.Log(pixel.a);
if(pixel.a < 1){
//We hit a transparent pixel
selectedPlayer.renderer.material.color = Color.black;
selectedPlayer.collider.enabled=false;
disabledGameobjects.Add(selectedPlayer);
}
else if(pixel.a >= 1){
//We did not hit a transparent pixel
selectedPlayer.renderer.material.color = Color.white;
//Debug.Log(selectedPlayer.name);
return;
}
}
}
}
//While mouse left button is held down| This Fires every frame
if(Input.GetMouseButton(0)){
}
//When mouse left button is released | This Fires only once
if(Input.GetMouseButtonUp(0)){
foreach(GameObject disabledGameobject in disabledGameobjects){
disabledGameobject.collider.enabled=true;
disabledGameobject.renderer.material.color=Color.white;
}
disabledGameobjects.Clear();
}
}
}
A couple of notes. All the textures have to have Read/Write enabled and a compression format that allows alpha.For example RGBA 32 bit Also this code works only with Meshcolliders. I'm also using a IComparable to order RaycastAll results. It could be done with Linq but I had problems with Linq on IOS.
Anyway hope somebody finds this useful.
Update for Unity 5.6:
Replace collider with GetComponent(), and renderer with GetComponent()
Your answer

Follow this Question
Related Questions
Trail Renderer detection 0 Answers
Hard time getting mouse click to work 2 Answers
How to prevent 2d objects go over each other? 1 Answer
IgnoreCollision doesnt work? 0 Answers