Collider priority with onMouseDown()

Hey everybody, hope you re having a good one : D.

I was wondering if i could somehow ( using Layer Order, the Z axis or any other way ) keep the onMouseDown() functionality and prioritize colliders.

Example: There are two elements, foreground and background. The desired behavior is to always run the onMouseDown() function on the foreground collider if the click happens withint it’s collider area

I am aware i can do this with Raycasting by selecting filtering specific layers or using tags but it would be much preferable to do this with onMouseDown() if possible in any way ( need to de-centralize clicking functionality yadda yadda boring details).

Any suggestions/approach ideas for this? Thank you all guys and girls : ).

Update: Still looking into the matter. Atm it seems like what collider the onMouseDown() hits is almost completely random.

Update #2: Changing the Z axis seems to work for the most part. I dont know if i would suggest that solution to anybody though since it doesnt look very proffesional.

I’m not sure if you are working with 2D colliders; however, the example I will provide for you could probably be re-worked for 3D as well.

Basically my solution was to create two scripts; first, a MouseDispatcher which I would attach to an empty game object and second, a MouseTarget script that I would add to my objects (I have two sprites with a Box Collider 2D component marked as a trigger). The MouseTarget script has two fields, one to hold the game object for the MouseDispatcher, and one called “Z Order”.

The MouseTarget script adds itself to a collection on the mouse dispatcher on Awake() and removes itself in OnDestroy(). In addition, the OnMouseDown() method defined needs to delegate the mouse down event to the dispatcher. Finally, a “MouseButtonDown()” handler in a script needs to exist to get the “real” dispatched mouse down.

The dispatcher’s job is to then determine from the mouse position all of the coinciding 2D colliders and get them ordered by their Z Order. Note that the only real purpose of the Z Order property is to determine which one is going to be considered the foreground by sorting. The item with the lowest Z Order value is considered the foreground, so if you set one object’s Z Order property to zero, then set all the rest to one, your object will have priority if overlapped by those 10 other items. Note that you can also set the priority of multiple items to determine the absolute order, kind of like layers. However, it is important to note that it only uses the Z Order for ordering, the first object intersected with the lower Z Order value is triggered.

All of that said, here are the scripts that I used. First, the MouseDispatcher, added to an empty GameObject in the scene:

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class MouseDispatcher : MonoBehaviour
{
    private static List<GameObject> mouseActionItems = new List<GameObject>();

    public void AddMouseTarget(GameObject mouseTarget)
    {
        MouseDispatcher.mouseActionItems.Add(mouseTarget);
    }

    public void RemoveMouseTarget(GameObject mouseTarget)
    {
        MouseDispatcher.mouseActionItems.Remove(mouseTarget);
    }

    public void TriggerMouseDown()
    {
        Ray cameraRay = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit2D[] result = Physics2D.GetRayIntersectionAll(cameraRay);

        var item = result.AsEnumerable()
            .Select(i => i.collider.gameObject.GetComponent<MouseTarget>())
            .Where(i => i != null)
            .OrderBy(i => i.zOrder)
            .Select(i => i.gameObject)
            .FirstOrDefault();

        if (item != null)
        {
            item.SendMessage("MouseButtonDown");
        }
    }
}

And next, the MouseTarget script, which is added to your objects with the colliders and you must attach your MouseDispatcher’s game object, give it a Z Order value (lower is higher priority) and a name for testing:

using UnityEngine;

public class MouseTarget : MonoBehaviour
{
    public MouseDispatcher mouseDispatcher;
    
    public int zOrder;
    public string displayName;

    public void Awake()
    {
        mouseDispatcher.AddMouseTarget(this.gameObject);
    }

    public void OnDestroy()
    {
        mouseDispatcher.RemoveMouseTarget(this.gameObject);
    }

    public void OnMouseDown()
    {
        mouseDispatcher.TriggerMouseDown();
    }

    public void MouseButtonDown()
    {
        Debug.Log("Item " + displayName + " was triggered.");
    }
}

Error handling was left out for brevity. If you create two sprites, then overlap them, change the following lines temporarily to see which one is always getting called first on the overlap:

    public void OnMouseDown()
    {
        //mouseDispatcher.TriggerMouseDown();
        MouseButtonDown();
    }

This will show you which one is always being called… now fix the code back and change your Z Orders to set your foreground item and then test clicking on the overlapping again. You should see the item which has your lower Z Order being called.

Hopefully this is an acceptable approach.

Hey there! I found myself in the same pit. Did you find a better way?