Created
July 14, 2022 00:55
-
-
Save NickMercer/60b13551aaf8e3b86129c6a3ee35bc67 to your computer and use it in GitHub Desktop.
UIToolkitRaycastChecker is a script for Unity UIElements to replicate the EventSystem.current.IsPointerOverGameObject() functionality for UI Elements.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using UnityEngine; | |
| public class ExampleNonUIMonoBehaviour : MonoBehaviour | |
| { | |
| private void Start() | |
| { | |
| //Example of how to check for a position. This is the equivalent of EventSystem.current.IsPointerOverGameObject() except for UI Elements. | |
| if (UIToolkitRaycastChecker.IsPointerOverUI()) | |
| { | |
| //I'm under a part of the UI that's set to block raycasts! Don't try to raycast or something. | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using UnityEngine; | |
| using UnityEngine.UIElements; | |
| public class ExampleUIController : MonoBehaviour | |
| { | |
| [SerializeField] | |
| private UIDocument _document; | |
| private VisualElement _root; | |
| private void OnEnable() | |
| { | |
| _root = _document.rootVisualElement; | |
| _rectangle = _root.Q<VisualElement>("Rectangle"); | |
| _root.BlockRaycasts(); //This optional extension method lets you register visual elements as if it were built in. | |
| UIToolkitRaycastChecker.RegisterBlockingElement(_rectangle); // Same effect as the above code, but this is more explicit that you are registering your element to some system you'll need to unregister it from. | |
| } | |
| private void OnDisable() | |
| { | |
| //Counterparts to the above methods: | |
| _root.AllowRaycasts(); | |
| UIToolkitRaycastChecker.UnregisterBlockingElement(_rectangle); | |
| } | |
| private void ExampleMethod() //You can check an element to see whether it's blocking raycasts or not. | |
| { | |
| if(_root.IsBlockingRaycasts()) | |
| { | |
| //Do a thing | |
| } | |
| if(UIToolkitRaycastChecker.IsBlockingRaycasts(_rectangle)) | |
| { | |
| //Do the same thing | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System.Collections.Generic; | |
| using UnityEngine; | |
| using UnityEngine.InputSystem; | |
| using UnityEngine.UIElements; | |
| #if UNITY_EDITOR | |
| using UnityEditor; | |
| #endif | |
| public static class UIToolkitRaycastChecker | |
| { | |
| private static HashSet<VisualElement> _blockingElements = new HashSet<VisualElement>(); | |
| public static void RegisterBlockingElement(VisualElement blockingElement) => | |
| _blockingElements.Add(blockingElement); | |
| public static void UnregisterBlockingElement(VisualElement blockingElement) => | |
| _blockingElements.Remove(blockingElement); | |
| public static bool IsBlockingRaycasts(VisualElement element) | |
| { | |
| return _blockingElements.Contains(element) && | |
| element.visible && | |
| element.resolvedStyle.display == DisplayStyle.Flex; | |
| } | |
| public static bool IsPointerOverUI() | |
| { | |
| foreach (var element in _blockingElements) | |
| { | |
| if (IsBlockingRaycasts(element) == false) | |
| continue; | |
| if (ContainsMouse(element)) | |
| return true; | |
| } | |
| return false; | |
| } | |
| private static bool ContainsMouse(VisualElement element) | |
| { | |
| var mousePosition = Mouse.current.position.ReadValue(); | |
| var scaledMousePosition = new Vector2(mousePosition.x / Screen.width, mousePosition.y / Screen.height); | |
| var flippedPosition = new Vector2(scaledMousePosition.x, 1 - scaledMousePosition.y); | |
| var adjustedPosition = flippedPosition * element.panel.visualTree.layout.size; | |
| var localPosition = element.WorldToLocal(adjustedPosition); | |
| return element.ContainsPoint(localPosition); | |
| } | |
| #if UNITY_EDITOR | |
| //This is used to reset the blocking elements set on playmode enter | |
| //to fix a bug if you have the quick enter playmode settings turned on | |
| //and don't unregister all your blocking elements before leaving playmode. | |
| [InitializeOnEnterPlayMode] | |
| private static void ResetBlockingElements() | |
| { | |
| _blockingElements = new HashSet<VisualElement>(); | |
| } | |
| #endif | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using UnityEngine.UIElements; | |
| public static class VisualElementExtensions | |
| { | |
| public static void BlockRaycasts(this VisualElement element) => | |
| UIToolkitRaycastChecker.RegisterBlockingElement(element); | |
| public static void AllowRaycasts(this VisualElement element) => | |
| UIToolkitRaycastChecker.UnregisterBlockingElement(element); | |
| public static void IsBlockingRaycasts(this VisualElement element) => | |
| UIToolkitRaycastChecker.IsBlockingRaycasts(element); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey, awesome utility! 👍 Just took this to use in one of my projects and wanted to contribute a few improvement ideas:
In
UIToolkitRaycastChecker.ContainsMouse, you can use the UIElements built-inRuntimePanelUtilshelper class for most of the mouse position scaling stuff and you can also useelement.worldBound.Containsinstead ofelement.ContainsPointso we can skip converting to the element's local space, e.g.It might be a good idea to cache the result of
UIToolkitRaycastChecker.ContainsMouseper-frame as it's something that could very realistically get called multiple times a frame and could get expensiveUIToolkitRaycastChecker.IsPointerOverUIiterates the elements in_blockingElementsand callsIsBlockingRaycastson them, which then checks whether they are contained in_blockingElements. Could be optimized to leave that check out for that code path.The "raycast blocking" naming is a bit confusing, as the code does not actually do anything to block ray casts and is not even aware of them. Since it's just checking whether the mouse is over the UI, probably best to name it along those lines. I went with
UIMouseOverfor the static main class andTrack/UntrackMouseOverfor the extension methods.