I was going to get rid of the only camera (in a game which has only UI), and found out that OnMouseDown() is actually casting rays from the camera to the objects… so I want find another way to implement the mouse.
I currently know of 2 other (similar) ways to do this, and am asking for advise on which is best to use and why, as I may be missing some aspects of their differences :
via the update method - by using the update() method of the canvas, I can place a if(Input.GetMouseButtonDown(0)) statement to detect a click anywhere on the canvas.
via the EventSystem - by implementing OnPointerDown(PointerEventData eventData) in a script.
About this implementation I’m not sure to which object’s code should it be added to … the EventSystem object itself? the canvas?
#1 That’d basically be a very light-version of the event system and it will probably take some time to get a variety of events working (you may want to add something like FocusEnter, FocusLost, caching the currently focused object) etc.
This being said: Use #2unless you want to rewrite the whole system for a better understanding or in order to have your own sophisticated system that’s open to any absraction, a variety of extensions and changes in behaviour.
#2 The standard UI elements do already have these interfaces implemented. You can usually add subscribers/listeners to the UnityEvents that are exposed in the inspector or you’d just add listeners in code.
There’s no need to attach anything to the event sytem itself.
Thank you. Some clarity gained
I’ll go for what’s behind curtain number 2!
So I understand that I can just override OnPointerDown() on any UI object, right?
A conclusion of the above that puzzles me (although not critical, I’ll test myself) :
I have a board with grid tiles, so I can implement the OnPointerDown() method on the game board object or on the game tiles themselves.
But to trigger the method, I still don’t understand how Unity sees I’m clicking inside a UI object when there is no camera to raycast on… if it doesn’t use raycast, what does it use to realize I’ve clicked on a tile?
I’ll check it out myself in the coming days.
You could do that, but it doesn’t make alot of sense unless you want to extend its overall features & functionality.
For instance, if you want to define on-click behaviour, it’s better to leave the button implementation alone and add it as an event listener instead. This is great, as it decouples the button itself from application-specific logic. It knows how to raise the event and does not care what happens “on click”.
Instead, if you were to ignore the events and override the OnPointerClick methods, you’re essentially creating specialized buttons that are aware of what they’re used for. It might seems intuitive at first, but there’s no real benefit (unless - as already stated - you’ll actually extend the buttons features itself).
Well, it’s not as tricky as it seems.
From the documentation:
That is, it’s enough information to know where the input devices points to. There’s no space-related information that needs to be considered, as all the elements are arranged in screen space. And both, screen resolution and size, is information that’s always available.
If you want to take a look at the whole event and UI system, check it out in the official repository.
Ok, I understand that the second part (that “screen space - overlay” is basically the same as the screen).
About the Event system – it’s… new to me - I do want to add a mouse listener in one place to catch any mouse input.
But I don’t use Buttons as tiles, I use Images, and I don’t want each image to have a listener but instead I want to have one listener for the whole game.
All mouse input (and all tiles) are and will be on my “board”, which is a rect transform under canvas taking a part of the screen. I intend to put all game logic there, including the mouse listener.
So my current thinking (and thank you for holding on!) is :
I don’t have to do anything to trigger mouse events, I just need to catch them.
Catching them on my board is still confusing to me…
I found out I need to be adding something like :
board.onClick.AddListener(() => MouseWasClicked());
But :
Where do I get the MouseEvent itself from (to know my x,y) ? I need to have it somehow in my MouseWasClicked()… or do I just take it from the “Input” variable?
My board doesn’t inherit the onClick method (it’s just a rect transform + script), so I shouldn’t be able to add a listener to it… or is there something I’m missing? Perhaps being a child of Canvas makes it inherit OnClick??
True. Events can be seen as an inversion of control. The event system does the work for you and notifies listeners.
This reduces redundant polling and moves it to a single place, which happens to be hidden somewhere in the core update function of the event system.
Oh you’re correct. That UniyEvent omits (or let’s say “swallows”) all that information (in fact the complete instance of the PointerEventData).
That’s not very useful, as you want to have only one listener and need to determine the tile yourself.
However, it might still be better to attach components to each of the tiles. This is less of a pain and reduces the code that you need to write yourself. That “one listener” is basically in “one place” if you just do everything in code.
In contrast to that, a “global” listener needs to know all the tiles, it needs to determine which tile is going to be affected by the incoming event and either access a component and call a method on it or - if you completely eliminate the inversion - examine that tiles properties and decide what’s supposed to happen. The latter is a questionable design, it’s not very flexible.
No, parenting an object to the canvas does not make it “inherit” anything, but it allows the UI components to receive events.
Your script component needs to implement the event interfaces or even better, it should inherit something that’s already well integrated into the event & ui system and implements lots of common behaviour that you’d otherwise just re-implement redundantly. Perhaps derive from UnityEngine.UI.Selectable? This could be a reasonable candidate. Otherwise, just take a look at the documentation and/or repository that I’ve linked in my previous post and try to find a suitable base class.
I’ll still go for the “global” listener, as the tile calculation is easy, if I can get the mouse information from the Input variable.
I’ve also looked about implementing an interface for getting Events, but found out a nicer way : to add a component “Event Trigger” to my board object. This should do the job and allow me to use all events in the board script. In theory
It looks like this takes me back to the OnPointerClick method and not the onClick one, but I’ll start at some point and this will sort itself out once I get to it!
Ye it’s basically a pre-made component to catch all of the events so that you only need to put listeners, similarly to the pre-made controls, except that you get the event args, too.
Personally I prefer the programmatic way, but that should be your decision. Since you do not plan to have alot of listenres, it’ll be fine I guess.
Ok I just finished understanding my (many) options, and implementing successfully.
I’ll sumup my 2 tested solutions with EventTrigger code for future generations of peoploids and AIs alike who are searching for answers :
There are 2 options to actually use EventTrigger, where I think the first one is best :
Inherit from EventTrigger, then override OnPointerDown() :
This doesn’t involve the editor and seems like a nice straightforward way to do it:
using UnityEngine.EventSystems;// Required when using Event data.
public class ExampleClass : EventTrigger // EventTrigger inherits from MonoBehaviour
public override void OnPointerDown(PointerEventData eventData)
{
Debug.Log("Down");
Debug.Log("Mouse position " + eventData.position.x + "," + eventData.position.y);
}
Add a component “Event Trigger”, but note that Unity writes : “Attaching this component to a GameObject will make that object intercept ALL events, and no events will propagate to parent objects.”
It is also a bit cumbersome, as you need to cast the event variable too.
To do this :
In the editor, add a component “Event Trigger”, press the “+” to add an event, and choose the OnPointerDown event. Then choose the gameObject that contains your function (FunctionName) that will be invoked on OnPointerDown events.
In the code :
public void FunctionName(BaseEventData eventData)
{
PointerEventData pointerData = eventData as PointerEventData;
Debug.Log("Down");
Debug.Log("Mouse position " + pointerData.position.x + "," + pointerData.position.y);
}
I hope you don’t mind what I’m about to say about it…
The sole purpose of the event trigger is to catch all the events and forward them to the registered listeners. It’s a component you’ll use when you want to add the listeners via the inspector instead of implementing the interface.
For your purposes, you could have a behaviour that’s got a public method and put it into the event trigger’s list. That’s how you would use an event trigger.
However, your current implementation defeats the whole purpose of the event trigger and (strictly speaking) violates the Liskov substitution principle, because your implementation does no longer raise that particular event. Anything you’ll put into the list for the PointerDown event will be ignored, because you’ve overriden it’s implementation. (you could just add a call to base.OnPointer… , but there’s still no reason to derive from event trigger in the first place in your particular situation).
Instead, either define your listener in a seperate behaviour and add it to the list via inspector, or implement the IPointerDownHandler interface (that’s basically the same code, without the override keyword and without inheriting EventTrigger, instead you’ll derive from MonoBehaviour directly).
using UnityEngine;
using UnityEngine.EventSystems;
public class Example : MonoBehaviour, IPointerDownHandler
{
public void OnPointerDown(PointerEventData eventData)
{
Debug.Log("clicked");
}
}
That’s all it needs if you only want to listen to that one event.
Yep. Implemented IPointerDownHandler just now like you said, and it works
Thank you again :}
Also learned about Liskov substitution principle…
Although my game is simple enough for me not to fall in too many such pitholes, I nearly managed to enter one!
Watch out folks, I think I’m a former C trainwreck crashing in the C# direction…
Booga. out of this thread!