Unity3D: Detect if mouse has clicked on a UI element? - c#

This is a pretty simple question, but it seems something has changed on Unity in the last versions answers I've found on the internet are no longer valid, so here goes nothing:
I got some UI elements, and a "InputController" class which is intended to handle the user input during the game (Input on the controllers are handled through the onclick events).
What I'm looking is for a way to being able to know if the mouse is clicking a UI element to block the execution of my input handling (and avoid the user clicking on "pause" while also the game executes "left button clicked."
Now, most solutions I've fond were a bit messy or used EventSystem.current.IsPointerOverGameObject() (like this one, which was shown when writing this question), which in 2019.4 does not longer appear. So, there's any new way to do this, do I have to make some hacky solution to receive the event from the UI, then block the execution of my code or am I missing something here?

You should look into interfaces like IPointerEnterHandler and IPointerExitHandler. If you implement these interfaces for your UI elements, you can add the necessary code to the OnPointerEnter and OnPointerExit methods that those interfaces require.
It should be as simple as adding a bool to your InputController such as isInputEnabled and only handling input when that is true. Set it to false OnPointerEnter and true OnPointerExit.

I spent a good amount of time trying to figure this out as well. I am on Unity 2022.1 using the Input System and UI Toolkit (UI Elements)
The following should help anyone else who is struggling with this to get the behavior they need.
Examine your UI Documents
You don't want your UI Document to always report clicks. So you need to set the picking mode in your UXML documents accordingly. Have a look at the images below,
In the image on the left, I have a wrapping element that allows me to position the panel at the bottom of the document. By default this element will receive all pointer events. What I actually want is to only receive pointer events inside of the orange area seen in the image on the right.
I can fix this by setting the picking mode of all parent elements to ignore:
Set up the input system
Setting up the new input system is a topic in itself. Refer to the documentation for more information, but in the image you can see I am using a simple button event as a click/tap action:
Set up your script
Next you need to respond to the input action and check if your input is seen by the UI
public class SimpleInput : MonoBehaviour
{
public Camera ViewCamera;
private PlayerControls _controls;
private void OnEnable()
{
Assert.IsNotNull(ViewCamera, "ViewCamera cannot be null");
// This class will vary depending on the name of your Input Action Asset
_controls = new PlayerControls();
_controls.Enable();
_controls.Gameplay.TapAction.performed += OnInputTapAction;
}
private void OnInputTapAction(InputAction.CallbackContext obj)
{
Vector2 position = Pointer.current.position.ReadValue();
Ray ray = ViewCamera.ScreenPointToRay(position);
if (PointerIsUIHit(position))
{
Debug.Log("Ui event received");
}
else
{
// Perform game-world events here
}
}
private bool PointerIsUIHit(Vector2 position)
{
PointerEventData pointer = new PointerEventData(EventSystem.current);
pointer.position = position;
List<RaycastResult> raycastResults = new List<RaycastResult>();
// UI Elements must have `picking mode` set to `position` to be hit
EventSystem.current.RaycastAll(pointer, raycastResults);
if (raycastResults.Count > 0)
{
foreach (RaycastResult result in raycastResults)
{
if (result.distance == 0 && result.isValid)
{
return true;
}
}
}
return false;
}
}
The helper method PointerIsUIHit was inspired by a conversation found in the Unity Forums which had some insight into how to get this done. It also shed some insight into the frustrating experience of being a Unity Dev.
Hopefully this helps other people struggling to find a proper guide.

Related

Why would my HitTestResultCallback not be called

I've inherited a program that uses hit testing to handle mouse events against a bunch of drawings on a canvas. Under some circumstances my HitTestResultCallBack stops being called.
Here's where the HitTest is called (this is called from the mousemove event):-
internal void HitTest(System.Windows.Input.MouseEventArgs e)
{
m_visualTrackerHit = m_visualTrackerHit2 = null;
Point location = e.GetPosition(this);
Geometry g = new RectangleGeometry(new Rect(location.X - m_connectDistance, location.Y - m_connectDistance, m_connectDistanceX2, m_connectDistanceX2));
HitTestParameters parameters = new GeometryHitTestParameters(g);
HitTestResultCallback callback = new HitTestResultCallback(this.HitTestCallback);
System.Diagnostics.Debug.Print("About to Hit Test");
VisualTreeHelper.HitTest(this, null, callback, parameters);
}
Here's my call back function:-
private HitTestResultBehavior HitTestCallback(HitTestResult result)
{
System.Diagnostics.Debug.Print("HitTestCallBack");
DrawingVisual visual = result.VisualHit as DrawingVisual;
if (visual != null)
{
VisualTracker visualTracker = visual.GetValue(FrameworkElement.TagProperty) as VisualTracker;
if (visualTracker != null && visualTracker.Type != VisualType.Selection && visualTracker.Type != VisualType.Ignore)
{
if (m_visualTrackerHit == null || visualTracker.Type < m_visualTrackerHit.Type)
{
m_visualTrackerHit = visualTracker;
}
}
}
return HitTestResultBehavior.Continue;
}
This all works fine until I take a particular, apparently unrelated action. (In this case each drawing represents a "component" that will have various properties. Setting a property to an invalid value causes the problem but this is all domain stuff and almost certainly isn't relevant to my question). Once that action is taken the call back method just stops being called. N.b. not just for the drawing the property was changed on either, the call back stops getting called for both the canvas and all the objects on it.
I've traced through the code path from the action and can't see anything obvious in there but it's highly complex and I could well have missed something so I'd like to come at this from the other direction. What are the possible reasons the callback wouldn't be called.
I've checked the following:-
The RectangleGeometry is being defined with correct values so it should indicate a collision with the drawing
Nothing is setting IsHitTestVisible to false anywhere.
Any suggestions?
I wanted to come back on this as I finally found the answer - or part of it at least. The property that was set to an invalid value on the component was causing a divide by zero error in the draw method of that component. That meant that the component was not redrawn but the old drawing of it was left in place, meaning it was not obvious that an error had occurred.
I've tested around this a bit and any unhandled error drawing to the canvas seems to result in the hitTestCallback no longer being called for anything on that canvas. I don't know why that is but it's easy for me to solve at this point as I can just guard against divide by zero exceptions
Debug your program using Snoop:
identify the path of events for a working case
identify the path of events for a non-working case
once you pinpoint the cause, you can come up with a fix for it

How to place a prefab and then remove it via voice when looked at

User can place objects (prefabs) at runtime using hands/gaze.
Voice command ""Remove" should remove the current focussed (looked at) object.
I tried instantiating the objects and adding the Intractable script. But I am stuck add adding OnfocusEnter and OnfocusExit events at runtime.
Hooking up the events on the prefab wont work as the references are in the scene.
I worked this out over on GitHub and posting it here so we can remove it from the other sources.
I didn't tackle the voice input yet as I am not there yet on my own MRTK project.
This submission should cover this answer for MRTK under version RC1. This was a quick job just to show proof of concept - feel free to modify and go on with it but I won't be :)
For run-time placement you would just need to add a method to instantiate an object that contains all of the information I setup in this example. There were some other solutions in the GitHub channel, I've copied the links below (not sure how long they will be active). This example is assuming you have some sort of already default prefab with the MRTK interactable class part of it.
Other discussions on GitHub from Microsoft: https://github.com/microsoft/MixedRealityToolkit-Unity/issues/4456
Example Video is here: https://www.youtube.com/watch?v=47OExTOOuyU&feature=youtu.be
Example Unity Package is here: https://github.com/JShull/MRTKExamples
Based on #jShull answer I came up with a simple solution for what I needed. Since there is no global listener for focus events I basically made my own.
I also added an earlier discussion (before I posted the question here) with two Microsoft Developers of the Mixed Reality Toolkit which could help out of you are looking for more functionality: https://github.com/microsoft/MixedRealityToolkit-Unity/issues/4456
"Object" script that is a component of the object that needs to be removed or interacted with.
using Microsoft.MixedReality.Toolkit.Input;
using UnityEngine;
public class Object: MonoBehaviour, IMixedRealityFocusHandler
{
public GameManager _gameManager;
public void OnFocusEnter(FocusEventData eventData)
{
Debug.Log("Focus ON: " + gameObject);
_gameManager.SetFocussedObject(gameObject);
}
public void OnFocusExit(FocusEventData eventData)
{
Debug.Log("Focus OFF: " + gameObject);
_gameManager.ResetFocussedObject();
}
}
"GameManager" script functions that sets the focussedObject
public void SetFocussedObject(GameObject object)
{
focussedObject = object;
}
public void ResetFocussedObject()
{
focussedObject = null;
}
Remove Object function is connect to the "Remove" global speech command in the "Speech Input Handler" component. It just removes the "focussedObject" inside the GameManager.

Set On End Edit Event from a Input Box (Text Mesh Pro) while runtime

I am programming a game in Unity, but I have problems with the On End Edit Event on a Input box from Text Mesh Pro. I need to define the event while runtime (in code). I have really no idea how to approch this.
Here is a picture from the event in the editor and I want it to connect to the PressedButton Method in the class PlayerConnectionManager:
Thaks for any kind of help!
I'm assuming that you need to call the PlayerConnectionManager.PressedButton at runtime and not via editor. If that's the case its quite easy. You just have to use addlistener. Here is a snippet.
public class TextMeshAdd : MonoBehaviour
{
//input field object
public TMP_InputField tmpInputField;
// Use this for initialization
void Start ()
{
//Add a listener function here
//Note: The function has to be of the type with parameter string
tmpInputField.onEndEdit.AddListener(TextMeshUpdated);
}
public void TextMeshUpdated(string text)
{
Debug.Log("Output string " + text);
}
}
Remember the function you are giving it should have a parameter with string. ie
PlayerConnectionManager.PressedButton should be of similer type mentioned above TextMeshUpdated(string text). This will allow it callback the function on end at runtime.
Another thing you have to keep in mind is that if you are using the inputfield in some other place make sure to remove the old listener before adding new listener.
You can do it by using this
tmpInputField.onEndEdit.RemoveListener(TextMeshUpdated);
or
tmpInputField.onEndEdit.RemoveAllListeners();
The first method will remove only specific function callback while RemoveAllListeners will remove all event listeners attached to the callback. If you dont do this and try to assign new callback it will try to call old functions, and possibly might throw some errors.

Xamarin / Monotouch: Custom class events

I'm currently building an application for iOS and Android using Xamarin and MonoTouch. In the application there is going to be a lot of data loaded from JSON, and therefore I wanted to incorporate a unified loader, an object that runs on application start to check whether it needs to re-download information or not.
The loading class is done and is fully functional, and has the following methods that I want to be able to bind events to. See below:
BeginLoading
ReloadPosts
ReloadLayers
ReloadRunners
FinishedLoading
These are all self contained and run in the loader class which I initiate in ViewDidLoad in my main screen (MainScreen.cs) using the following code:
var loader = new UnifiedLoader();
This starts the process of checking the local cache, last reload time etc and either starts the reloading process - posts, layers, runners or jumps straight to FinishedLoading.
What I'd like to be able to do is to listen for these "events" in some fashion, and I have no idea how to go about doing so. Please look below for an example.
var loader = new UnifiedLoader();
loader.LoadingDidBegin += () => {
Console.Out.WriteLine("Loading started");
// Display spinner or something...
};
loader.DidReloadPosts += () => {
Console.Out.WriteLine("Posts were reloaded");
// Update reloading percentage, show user...
};
loader.DidReloadLayers += () => {
Console.Out.WriteLine("Layers were reloaded");
// Update reloading percentage, show user...
};
loader.DidReloadRunners += () => {
Console.Out.WriteLine("Runners were reloaded");
// Update reloading percentage, show user...
};
loader.LoadingDidFinish += () => {
Console.Out.WriteLine("Loading finished");
// Remove spinner, proceed...
};
As of now I have no idea how I would go about implementing these events in the loading class. I've been searching and going through the API documentation but found nothing to aid me.
I would be more than thankful if someone could help me solve this.
Thanks in advance,
Jonathan
The preferred way would be to just write:
public EventHandler LoadingDidBegin;
This saves you from declaring the delegates and conforms to coding guidelines: http://msdn.microsoft.com/en-us/library/w369ty8x.aspx
I solved it by finding the Microsoft documentation for C# events. It was as simple as using the following code to register the event delegates and events.
This code goes outside of the class:
public delegate void LoadingDidBegin();
And this code goes inside the class:
public event LoadingDidBegin LoadingDidBegin;
And in the method where you want to invoke the event, call this:
// Trigger event:
if (this.CheckingDidBegin != null){
this.CheckingDidBegin ();
}
And last, in the class where you bind the event, bind the delegate like this:
var loader = new UnifiedLoader ();
loader.LoadingDidBegin += delegate {
// Do something here, show a HUD for instance...
};
loader.InitiateLoader ();
That's pretty much it, just remember to register the delegates before initiating the methods that carry the event triggers, otherwise they will just return null and you will get no feedback.
Good luck!

Boolean seemingly changing itself (XNA)

I am currently working on a save system in my XNA game that fires off an event when it's finished saving.
My game has multiple screens which are managed by a screen manager, each screen has its own Update and Draw methods.
In the constructor, my _newSave boolean is flagged as true, this boolean starts off the game save process (which is managed by helper), and once it starts the process, it is flagged to false to prevent constant looping (since i only want to save once per save screen).
When helper is finished saving, it fires off the event, back inside the SaveScreen class, which sets _newSave back to true, and tells the ScreenManager to change screens.
Playing the game everything seems to work correctly, until the second time saving, when this save is attempted the game freezes. It is worth noting that this bool is private and only used in this method the 4 times shown (found usage by visual studio to confirm)
class SaveScreen : LoadingScreen
{
private bool _newSave;
private Player _player;
public SaveScreen(EventHandler screenEvent, ContentManager Content, GraphicsDevice GraphicsDevice, Player player)
: base(screenEvent, Content, GraphicsDevice)
{
screenTexture = Content.Load<Texture2D>("saving");
_newSave = true;
this._player = player;
helper.FinishedSaving = new EventHandler(FinishedSaving);
}
public override void Update(GameTime gameTime)
{
if (_newSave)
{
helper.RequestSave(_player);
_newSave = false;
}
helper.UpdateSaving();
base.Update(gameTime);
}
private void FinishedSaving(object o, EventArgs e)
{
_newSave = true;
screenEvent.Invoke(this, new EventArgs());
}
}
With a breakpoint inside the FinishedSaving event, i confirmed that the bool is changing to true, which is correct, however, a following breakpoint on the next save cycle on the if statement, shows that bool is false.
I am so completely confused by this, as the only _newSave = false statement is inside the if statement that needs to be true to access, the breakpoint inside the event shows that it is true, yet on the first "new" loop of the update code, it is false.
I am really pulling my hair out with this and cannot understand what's happening.
Any help is hugely appreciated, and i'm sorry if it's something silly, i'm a relatively new programmer :)
Thank You! <3
EDIT: Pastebin of entire Helper.cs class: http://pastebin.com/uJ0g6e00
Loading Screen Class: pastebin.com/8W8HxBnq
Screen Class: pastebin.com/qr29gzuq
Are you using multiple threads? If so, I wonder whether it's a race condition.
For example:
Update is called
Let's suppose that _newSave is true
RequestSave is called
Before RequestSave returns, Update is called again, but _newSave is still true...
I figured out the problem, apologies to everyone for wasting their time, and thank you for trying to help, turns out that simply switching the _newSave; = false and helper.RequestSave(_player); statements around fixed the issue, I thought that the problem was isolated into this class alone due it being the only place where the changes happened, in fact the issue was that i invoked the delegate inside the RequestSave method when the player score was not greater than the high score, this caused the code to then return to the if block and since the "_newSave = false;" statement was directly after the method call, it was reverting the statement back to false!
So silly of me but sometimes it's really hard to see the simple problems :P Thank you again!
<3

Categories

Resources