- Home /
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
Does the functionality have to be centrally controlled (in one or more of your "God" classes) or can the script be on the GameObject itself?
If it can be controlled in a separate script you could just (as I imaging from your description above you have) just add an On$$anonymous$$ouseEnter / On$$anonymous$$ouseOver event to it.
Otherwise there are some other ways you could do it using tags on gameobjects / GameObject name / other information on the gameobject which you can gather using a raycast.
It should be controlled by the god classes, because as I already stated, I don't want a $$anonymous$$onoBehaviour component on the collider/gameObject that the function is checking. Again, this is probably just a crazy idea I've came up with past midnight.
I haven't encountered any way to do that. Raycasts can point out an object but I don't think there's any way to fake a function like that.
That's what I am thinking. I'll probably just end up indexing the booleans of colliders in a dictionary and raycast.
Ive written an example... Yep I'd use an enum personally rather than a dictionary just to keep track of things more easily and make syntax more readable if you are going to have lots of objects.
Answer by DavidDebnar · Feb 22, 2013 at 02:04 PM
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
Hello $$anonymous$$,
I really like your ideas which I found here :
I need it to cast spells in my game. The view is an RTS style game and what I need is to just cast anywhere I want my spell by mouse click, detect if any collider is present and apply damage.
Does your JS cover this ? If so, can you provide a C# script as well? Your help would be much appreciated, as I am really new at coding and I stumbled for some time over this particular problem.
Thank you !
Answer by Jeremy Hindle · Feb 14, 2013 at 09:02 PM
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.
Yes, I'll probably end up going with something similar. I'll work on the solution more tomorrow and will answer if I find any interesting solution that might be worth sharing ;)
A simple solution for a lot of objects might be to just put them in another layer. On that note, it might be an idea to make a public/serialised Layer$$anonymous$$ask you can tweak in the editor ins$$anonymous$$d of hardcoding it. That way it will be easier to change later/reuse.
$$anonymous$$y solution only uses a single layer, for faster raycasting and it'd be easy just to create a variable ins$$anonymous$$d of a harcoded bit shift value.
I don't think doing two layers is any slower than one, assu$$anonymous$$g the number of objects is the same, although I could be wrong? But I meant that by using separate layers you could just put only the things you want to "test against" ins$$anonymous$$d of an enum or dictionary/etc.