Skip to content

Instantly share code, notes, and snippets.

@NickMercer
Created July 14, 2022 00:55
Show Gist options
  • Save NickMercer/60b13551aaf8e3b86129c6a3ee35bc67 to your computer and use it in GitHub Desktop.
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.
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.
}
}
}
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
}
}
}
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
}
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);
}
@NickMercer
Copy link
Author

NickMercer commented Jul 14, 2022

UIToolkitRaycastChecker is a script for Unity UIElements to replicate the EventSystem.current.IsPointerOverGameObject() functionality for UI Elements. UI Elements is missing the ability to tell whether a mouse position is covered by VisualElements, and so it becomes difficult to cancel raycasts that would go through UI.

While this won't actually block raycasts for you, it at least allows you a simple way to do it yourself.

  1. Register a VisualElement as a blocking element (or use the optional extension method to do it from the VisualElement itself)
  2. Call UIToolkitRaycastChecker.IsPointerOverUI(), which will return true if your pointer is over any of the registered elements.

Dependencies:
UI Toolkit V 1.0.0
New Unity Input System (If you want to use the old input system, you can swap it out and it should work, but I haven't tested that the mouse position needs to be transformed in the same way.)

Notes:
Registering a VisualElement registers it's entire bounding box. If you register an element with other elements within it's bounding box, this means you don't need to register the children.
However, because of the way UIDocuments work, if you register the root element, the entire space available to the document will be covered. So if you have multiple panels on a screen sized UI Document, you want to register the panels, not the root element itself. Otherwise the whole screen will be considered blocked.

@jmaler
Copy link

jmaler commented Aug 6, 2022

Great, this is exactly what I was looking for after a few days with UI Toolkit, thank you very much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment