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!