- Home /
how to click in only one object at a time?
I have two objects (in an isometric view) in a way that one is in top of another. when I click on the object that is on top, te object behind seems to detect the click aswell, and that´s a problem (in my game). Is there a way to make the detect only the oject on the top? as it were "opaque".
I use void OnMouseDown()
to detect the click in both GameObjects scripts.
thanks in advance!
The general idea is to have a single object responsible for handling of clicks, and to have it perform the test for what you clicked on. Ins$$anonymous$$d of every object grabbing the mouse, this guy grabs the mouse and then gives it to a single object. I'll answer your question to describe the specific process for doing this, but to do that, I need to know more about what you want to click. Are you trying to click a 3D object, as with a Collider, or a UI element?
You can use Raycast for detecting clicks ins$$anonymous$$d of On$$anonymous$$ouseDown. I will suggest that you use RaycastAll with a particular layer and length. Then you can decide which object to act upon from the raycast result. The reason for raycastall is, in your project you might later need to do task even when something is behind the front clicked object. It will come in handy.
I agree that RaycastAll is more future proof, but one of the tenets of producing code for a project is to not program future proofing for features that aren't planned. The code for plain old Raycast is much simpler to implement, and takes care of sorting distances for you. If you then later decide that you need to click on multiple objects, porting that function isn't hard at all. It's the rest of the codebase, that's designed to work with only one object, that's going to need the most refactoring. And that's going to need refactoring even if you use RaycastAll, because it's only going to be tested to work with one object.
"seems to"? That should be impossible shouldn't it? I don't work in isometric, but a mouseclick is just a raycast. Whatever it hits it hits, it doesn't keep going. Have you used debug.log on both scripts, etc.?
Wow, you're right. I should have tested this. I assumed that On$$anonymous$$ouseDown
would just be called on all behaviours under the cursor in an undefined order.
yes, I used Debug.Log on both and i got both messages in the console
On$$anonymous$$ouseDown is really old and bad practice, if you need some complex selection/clicking behaviour, you should go with centralised raycasting, otherwise you can simply attach button component or event trigger component to your object (and add raycaster component to the camera)
Answer by rainChu · Aug 27, 2018 at 11:54 AM
Edit: OnMouseDown should be called for only the topmost object, rendering this answer not a proper solution for your problem. I've left the code below though, because it still has benefits over OnMouseDown such as being able to be adapted for clicking on objects besides the topmost, and passing them data such as the hit normal or mouse depth.
You need a mouse manager class. Instead of OnMouseDown
, you want to poll for the mouse button in the Update
function of a single mouse manager. I want to clarify that using Update
should be avoided when possible, because it adds code that's called every frame. We're going to do this here because there's only ever going to be one MouseManager
so the cycles it takes up is insignificant. Try a MouseManager
similar to this:
public class MouseManager : MonoBehaviour
{
[SerializeField] private Camera m_camera;
private void Update( )
{
// If the mouse button is down, call TestMouseClick.
// We're testing instantly. If you want to only check for when the mouse cursor was pressed
// down on an object, then lifted up on that same object, just keep track of the GameObject
// the cursor was pressed on, and call OnClick on mouse up.
if ( Input.GetMouseButtonDown( 0 ) )
TestMouseClick();
}
private void TestMouseClick( )
{
// Get a ray representing the mouse cursor from the current camera.
// m_camera should be a reference to the game's main camera you're seeing the objects from.
Ray ray = m_camera.ScreenPointToRay( Input.mousePosition );
// Create a layer mask containing just the objects you want to click.
// This is so that if there's a non-clickable collider, it won't obstruct
// a clickable collider. If you want a collider to block mouse click,
// add it here.
int layerMask = LayerMask.GetMask( "Ground", "Environment" );
// Perform the raycast.
RaycastHit raycastHit;
if ( Physics.Raycast( ray, out raycastHit, Mathf.Infinity, layerMask ) )
{ // If something was hit:
// Get every MonoBehaviour sibling of the topmost collider under the mouse cursor which
// is an IClickableObject
var hitClickableObjects = raycastHit.collider.GetComponents<IClickableObject>();
// Call OnClick in each of them. This is so that if multiple MonoBehaviours on the single
// clicked GameObject exist, OnClick will be called for all of them.
foreach ( var clickableObject in hitClickableObjects )
// We're passing RaycastHit.point to the method. You can obviously define
// the interface to better suit your needs.
clickableObject.OnClick( raycastHit.point );
}
}
}
Of course, you also need an interface. If you've never worked with those before, they're similar to classes in some ways. Namely, you can use the interface as a reference to the object. The interface the above MouseManager
expects looks like this:
/// <summary>
/// An object that can be clicked by MouseManager
/// </summary>
interface IClickableObject
{
/// <summary>
/// The object was clicked by MouseManager.
/// </summary>
/// <param name="mousePosition3D">The postiion, including depth, of the point
/// under the mouse cursor</param>
void OnClick( Vector3 mousePosition3D );
}
And finally, on every MonoBehaviour
you want to catch mouse clicks in, just implement that interface:
public class MyCoolBehaviour : MonoBehaviour, IClickableObject
{
// ... Other methods, fields, properties ...
// Implement the interface.
public void OnClick( Vector3 mousePosition3D )
{
Debug.Log( "I was clicked at the following position: " + mousePosition3D );
}
// ... Other methods, fields, properties ...
}
Just attach the MouseManager
to a single GameObject
, such as on the one your game manager or app manager is on. Good luck!
I tried to implement it, but I dont really know how. I am sorry. You took your time to make that code and help me, but I cannot truly understand it due to my lack of knowledge. I copied the
interface IClickableObject
{
/// <summary>
/// The object was clicked by $$anonymous$$ouse$$anonymous$$anager.
/// </summary>
/// <param name="mousePosition3D">The postiion, including depth, of the point
/// under the mouse cursor</param>
void OnClick( Vector3 mousePosition3D );
}
on the game$$anonymous$$anager script, and I also made a new script for interfaces, and then replace On$$anonymous$$ouseDown()
for OnClick
but it doesnt work either. anyways thanks for answering
Answer by malkere · Aug 28, 2018 at 12:35 AM
What you're reporting is impossible in the normal sense. You said you've got Debug.Log() reports from two GameObjects with one click, and you're using OnMouseDown so that means a single frame, a single raycast. I assume that means you've got a reference mix up somewhere. When you click the mouse it just sends a raycast out and will call OnMouseDown on whatever it hits. We'll need more info to figure out what else is wrong with your setup.
Creating a fresh scene and a simple OnMouseDown script, it doesn't matter how many objects you put behind or in front of one another, only the first collider receives the OnMouseDown call, I just double checked.
Can you post your script?
Try adding:
Debug.Log("Hi " + transform.name);
To the OnMouseDown and make sure your GameObjects have different names. Something isn't as it should be, but you can always track it down.
Your answer
Follow this Question
Related Questions
Click on two objects to render third 2 Answers
Hexagonal Grid equal distances 0 Answers
Picking up an object and dropping it with the same keycode 1 Answer
OnMouseDown() Basic Help 1 Answer
change scene on click of a 3d object 1 Answer