How can I call OnMouse functions on a specific collider?

Hello,
A crazy idea/question that I’ve came up with past midnight incoming!

I was wondering if it was possible to call OnMouseEnter and similar functions on different colliders/gameObjects? Example:

function ColliderOfADifferentObjectA.OnMouseOver () {
    isMouseOverTheColliderOfADifferentObjectA = true;    
}

function ColliderOfADifferentObjectB.OnMouseOver () {
    isMouseOverTheColliderOfADifferentObjectB = true;
}

this of course doesn’t work, but it’s just an example to give you an idea of what I mean. There are workarounds I could use, but for a long time I’ve always had to create a second script that handles nothing but the OnMouse events and I’d welcome a cleaner solution than that.

— David

This is what I ended up doing. It works on desktop and mobile (mouse and touch control).

This code calls “OnActive” and “OnHover” functions instead of OnMouseDown, so a receiver would look like this:

public var isMouseOver : boolean = false;
public var isMouseDown : boolean = false;

function OnHover (isHovered:boolean) {
	isMouseOver = isHovered;
}

function OnActive (isActive:boolean) {
	isMouseDown = isActive;
}

The code (JS, I can provide a C# version, if anyone would want):

#pragma strict

public var UICamera : Camera;
public var isMobile : boolean = false;

private var lastCollider : Collider;

function Awake () {

	if(Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.Android)
		isMobile = true;
	else
		isMobile = false;
}

function Update () {

	var clickPosition : Vector2 = Vector2.zero;

	if(isMobile) clickPosition = Input.touches[0].position;
	else clickPosition = Input.mousePosition;

	var ray : Ray = UICamera.ScreenPointToRay(clickPosition);
	var hit : RaycastHit;

	var didHit : boolean;

	if(!isMobile || Input.touches.length > 0)
		didHit = Physics.Raycast(ray,hit,10,1<<8);

	if(didHit) {
		if(hit.collider != lastCollider || lastCollider == null) {

			if(lastCollider != null) {
				lastCollider.gameObject.SendMessage("OnActive",false);
				lastCollider.gameObject.SendMessage("OnHover",false);
			}

			lastCollider = hit.collider;

			lastCollider.gameObject.SendMessage("OnHover",true);
		}

	}
	else if(lastCollider != null) {
		lastCollider.gameObject.SendMessage("OnActive",false);
		lastCollider.gameObject.SendMessage("OnHover",false);

		lastCollider = null;
	}

	if(lastCollider != null) {
		if((!isMobile && Input.GetMouseButtonDown(0)) || (isMobile && Input.touches[0].phase == TouchPhase.Began))
			lastCollider.gameObject.SendMessage("OnActive",true);
		else if((!isMobile && Input.GetMouseButtonUp(0)) || (isMobile && Input.touches[0].phase == TouchPhase.Ended))
			lastCollider.gameObject.SendMessage("OnActive",false);
	}
}

This code uses SendMessage each time a button was hovered, clicked or released, so it could get quite heavy on a mobile device. To solve this, you could create a Collider-Receiver Component dictionary and instead of calling SendMessage on the collider (gameObject), use the collider as the key in the dictionary to get the reference to the component and call OnActive(true/false)/OnHover(true/false) on it.

Code to do this, assuming each clickable element is tagged with “GUI”:

import System.Collections.Generic;
//...
private var GUIElements : Dictionary.<Collider,ReceiverComponent>;
//...
function Start () {
    GUIElements = new Dictionary.<Collider,ReceiverComponent>();
    var allGUIElements : GameObject[] = GameObject.FindGameObjectsWithTag("GUI");
    for(var GUIElement : GameObject in allGUIElements)
        GUIElements[GUIElement.collider] = GUIElement.GetComponent(ReceiverComponent);
}
//...
//Replace each SendMessage call with:
GUIElements[lastCollider].IsActive(true);

– David

If you don’t want to put an OnMouseOver() script on each GameObject you can do what you want with a single raycast in Update():

Draw a raycast out from the mouse:


 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

Then in update check if it hits anything:


if (Physics.Raycast(ray.origin, ray.direction, out ObjectHitByMouseRaycast, 2000))
        {


        }

Then if it does hit something add checks in for what it’s hit. Below I am checking the gameobject name as an example but you can really check anything about it:


        if (Physics.Raycast(ray.origin, ray.direction, out ObjectHitByMouseRaycast, 2000))
        {
            if (ObjectHitByMouseRaycast.collider.gameObject.name == "This")
            {
                Debug.Log("Im over 'This'");
            }

            if (ObjectHitByMouseRaycast.collider.gameObject.name == "That")
            {
                Debug.Log("Im over 'That'");
            }
        }

Here is a working example if you put this script anywhere in your scene and make a couple of game objects called “This” and “That” you can test it out:


public class MouseRaycastDetector : MonoBehaviour {

    RaycastHit ObjectHitByMouseRaycast;
	// Update is called once per frame
	void Update () 
    {

        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        if (Physics.Raycast(ray.origin, ray.direction, out ObjectHitByMouseRaycast, 2000))
        {
            if (ObjectHitByMouseRaycast.collider.gameObject.name == "This")
            {
                Debug.Log("Im over 'This'");
            }

            if (ObjectHitByMouseRaycast.collider.gameObject.name == "That")
            {
                Debug.Log("Im over 'That'");
            }

        }
        
	}
}

If you are going to have a lot of objects I suggest you create an enum or a dictionary of the things you will test against to make things easier to keep track of and maintain.

Hope that helps.