- Home /
Replacing OnMouseEnter/Exit/Down/etc with Raycasting
OnMouseDown/etc won't work in scenes with multiple cameras and one being Orthographic.
I think I've solved some of them (still need to add a LayerMask), but I'm not sure how do the OnMouseEnter/Exit properly. The issue is just sending the message once.
I was thinking just store the hoveredGO at all times, and check if it changes, but I'm not sure how to get the OnMouseExit to work doing this:
public GameObject hoveredGO;
void Update()
{
RaycastHit hitInfo = new RaycastHit();
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// OnMouseDown
if (Input.GetMouseButtonDown(0))
{
if (Physics.Raycast(ray, out hitInfo))
{
hitInfo.collider.SendMessage("OnMouseDown", SendMessageOptions.DontRequireReceiver);
}
}
// OnMouseUp
if (Input.GetMouseButtonUp(0))
{
if (Physics.Raycast(ray, out hitInfo))
{
hitInfo.collider.SendMessage("OnMouseUp", SendMessageOptions.DontRequireReceiver);
}
}
// OnMouseOver
if (Physics.Raycast(ray, out hitInfo))
{
hitInfo.collider.SendMessage("OnMouseOver", SendMessageOptions.DontRequireReceiver);
}
// OnMouseEnter
if (Physics.Raycast(ray, out hitInfo))
{
if (hitInfo.collider.gameObject != hoveredGO)
{
hitInfo.collider.SendMessage("OnMouseEnter", SendMessageOptions.DontRequireReceiver);
hoveredGO = hitInfo.collider.gameObject;
}
}
}
All in all, I think your implementation is looking good, and re$$anonymous$$ds me quite a bit of the way we handle touch input on mobile devices.
To handle On$$anonymous$$ouseExit, you can check whether the raycast is still hitting hoveredGO on the next frame, which it looks like you are basically doing in On$$anonymous$$ouseEnter. I think that if hitInfo.collider.gameObject != hoveredGO, you can check if hoveredGO != null & send a message to hoveredGO with On$$anonymous$$ouseExit. The only caveat here is if hoveredGO changes because something else moved in front of it, and whether you care about that.
Couple other things I noticed:
1) You can probably just call Physics.Raycast a single time and use the result for all calculations, since you have to call it every frame to deal with On$$anonymous$$ouseOver anyway.
2) Depending on your game, you could run into issues with compound colliders and rigidbodies. Usually, if a raycast hits a rigidbody, you'll want to send the message to the parent rigidbody ins$$anonymous$$d of any individual collider in the rigidbody.
3) Really small thing, but I don't think you actually need to create a new RaycastHit object, since Physics.Raycast will "new" it for you.
Not submitting as an answer because I'm not sure if this is correct, but hope it helps!
Yes, I based it on a script that makes On$$anonymous$$ouseDown work for touch. It helps, thank you.
Answer by Josh707 · Dec 02, 2013 at 03:36 AM
The way you have it now works, but I'd write it a little differently - All you need to do is have a way to remember what the mouse was doing in the previous frame, be it with a boolean or anything. I just chose to use an enum because the names make it simple to look at.
public GameObject hoveredGO;
public enum HoverState{HOVER, NONE};
public HoverState hover_state = HoverState.NONE;
void Update () {
RaycastHit hitInfo = new RaycastHit();
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast(ray, out hitInfo)){
if(hover_state == HoverState.NONE){
hitInfo.collider.SendMessage("OnMouseEnter", SendMessageOptions.DontRequireReceiver);
hoveredGO = hitInfo.collider.gameObject;
}
hover_state = HoverState.HOVER;
}
else{
if(hover_state == HoverState.HOVER){
hoveredGO.SendMessage("OnMouseExit", SendMessageOptions.DontRequireReceiver);
}
hover_state = HoverState.NONE;
}
if(hover_state == HoverState.HOVER){
hitInfo.collider.SendMessage("OnMouseOver", SendMessageOptions.DontRequireReceiver); //Mouse is hovering
if(Input.GetMouseButtonDown(0)){
hitInfo.collider.SendMessage("OnMouseDown", SendMessageOptions.DontRequireReceiver); //Mouse down
}
if(Input.GetMouseButtonUp(0)){
hitInfo.collider.SendMessage("OnMouseUp", SendMessageOptions.DontRequireReceiver); //Mouse up
}
}
}
You don't need to use this, but the key here is just having a way to remember what the mouse was doing in the previous frame like the other person commented, so you can check if it only just entered or exited, or is staying over the object.
I think this is basically right, with just some adjustment. Thanks!
Answer by roisym · Jul 06, 2016 at 09:02 PM
This is what I used for multi-touch raycasting in a 2D game. It supports many types of messages. Since I'm very new to programing, I'm interested in any comments and advice on my code.
What I notice in both codes proposed above is that OnMouseDown, OnMouseOver, etc. messages are used. According to this post: http://answers.unity3d.com/questions/1064394/onmousedown-and-mobile.html its not good too use OnMouse events in mobile development (Unity has to dance through fiery hoops in every frame to make it work). Therefore IMO its better to use custom messages.
public class TouchInput2D : MonoBehaviour {
public LayerMask touchInputMask;
private Camera cam;
private List<GameObject> touchList = new List<GameObject>();
private List<GameObject> touchListOld = new List<GameObject>();
private RaycastHit2D hit;
void Start ()
{
cam = this.GetComponent<Camera>();
}
void Update ()
{
#if UNITY_EDITOR || UNITY_STANDALONE
if (Input.GetMouseButton(0) || Input.GetMouseButtonDown(0) || Input.GetMouseButtonUp(0))
{
touchListOld.Clear();
touchListOld.AddRange(touchList);
touchList.Clear();
hit = Physics2D.Raycast(cam.ScreenToWorldPoint(Input.mousePosition), Vector2.zero, 1f, touchInputMask);
if (hit.collider != null)
{
GameObject recipent = hit.transform.gameObject;
touchList.Add(recipent);
if (Input.GetMouseButtonDown(0))
{
recipent.SendMessage("OnTouchDown", hit.point, SendMessageOptions.DontRequireReceiver);
}
if (Input.GetMouseButtonUp(0))
{
recipent.SendMessage("OnTouchUp", hit.point, SendMessageOptions.DontRequireReceiver);
}
if (Input.GetMouseButton(0) && Input.GetMouseButtonDown(0) == false &&touchListOld.Contains(recipent) == false)
{
recipent.SendMessage("OnTouchEntered", hit.point, SendMessageOptions.DontRequireReceiver);
}
if (Input.GetMouseButton(0))
{
recipent.SendMessage("OnTouchStay", hit.point, SendMessageOptions.DontRequireReceiver);
}
}
foreach (GameObject g in touchListOld)
{
if (!touchList.Contains(g) && g != null)
{
g.SendMessage("OnTouchExit", hit.point, SendMessageOptions.DontRequireReceiver);
}
}
}
#endif
#if UNITY_IOS
if (Input.touchCount > 0)
{
touchListOld.Clear();
touchListOld.AddRange(touchList);
touchList.Clear();
Touch[] currentTouches = Input.touches;
for (int i = 0; i < Input.touchCount; i++)
{
hit = Physics2D.Raycast(cam.ScreenToWorldPoint(currentTouches[i].position), Vector2.zero, 1f, touchInputMask);
if (hit.collider != null)
{
GameObject recipent = hit.transform.gameObject;
touchList.Add(recipent);
if (currentTouches[i].phase == TouchPhase.Began)
{
recipent.SendMessage("OnTouchDown", hit.point, SendMessageOptions.DontRequireReceiver);
}
if (currentTouches[i].phase == TouchPhase.Ended)
{
recipent.SendMessage("OnTouchUp", hit.point, SendMessageOptions.DontRequireReceiver);
}
if (currentTouches[i].phase == TouchPhase.Stationary || currentTouches[i].phase == TouchPhase.Moved)
{
recipent.SendMessage("OnTouchStay", hit.point, SendMessageOptions.DontRequireReceiver);
}
if (currentTouches[i].phase == TouchPhase.Moved && currentTouches[i].phase != TouchPhase.Began && touchListOld.Contains(recipent) == false)
{
recipent.SendMessage("OnTouchEntered", hit.point, SendMessageOptions.DontRequireReceiver);
}
if (currentTouches[i].phase == TouchPhase.Moved)
{
recipent.SendMessage("OnTouchMoved", hit.point, SendMessageOptions.DontRequireReceiver);
}
if (currentTouches[i].phase == TouchPhase.Canceled)
{
recipent.SendMessage("OnTouchExit", hit.point, SendMessageOptions.DontRequireReceiver);
}
}
}
foreach (GameObject g in touchListOld)
{
if (!touchList.Contains(g) && g != null)
{
g.SendMessage("OnTouchExit", hit.point, SendMessageOptions.DontRequireReceiver);
}
}
}
#endif
}
}
Answer by unity_8o0VCZu0dFDNEw · Dec 25, 2020 at 05:54 PM
using UnityEngine;
internal class test : MonoBehaviour
{
public GameObject hoveredGO;
public enum HoverState { HOVER, NONE };
public HoverState hover_state = HoverState.NONE;
public bool colour_Changed = false;
private void Awake()
{
hoveredGO = gameObject;
}
void Update()
{
RaycastHit hitInfo = new RaycastHit();
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hitInfo))
{
if (hover_state == HoverState.NONE && hitInfo.collider.gameObject == hoveredGO)
{
colour_Changed = true;
gameObject.GetComponent<Renderer>().material.color = Color.red;
hitInfo.collider.SendMessage("OnMouseEnter", SendMessageOptions.DontRequireReceiver);
hoveredGO = hitInfo.collider.gameObject;
}
hover_state = HoverState.HOVER;
}
else
{
if (hover_state == HoverState.HOVER && colour_Changed == true)
{
hoveredGO.SendMessage("OnMouseExit", SendMessageOptions.DontRequireReceiver);
gameObject.GetComponent<Renderer>().material.color = Color.blue;
colour_Changed = false;
}
hover_state = HoverState.NONE;
}
if (hover_state == HoverState.HOVER)
{
hitInfo.collider.SendMessage("OnMouseOver", SendMessageOptions.DontRequireReceiver); //Mouse is hovering
if (Input.GetMouseButtonDown(0) && hitInfo.collider.gameObject == hoveredGO)
{
hitInfo.collider.SendMessage("OnMouseDown", SendMessageOptions.DontRequireReceiver); //Mouse down
gameObject.GetComponent<Renderer>().material.color = Color.yellow;
}
if (Input.GetMouseButtonUp(0) && hitInfo.collider.gameObject == hoveredGO)
{
hitInfo.collider.SendMessage("OnMouseUp", SendMessageOptions.DontRequireReceiver); //Mouse up
gameObject.GetComponent<Renderer>().material.color = Color.green;
}
}
}
}
However, this script can be used with more object without the problem that when you have 2 or more different gameobjects with the first script it becomes both objects red also if your mouse is only on one of this.