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