Clickable area of a button for non-rectangular graphics

For a button that contains a graphic that is not rectangular, I was able to get Unity to limit the clickable area to the visible area of the graphic with the “old” UI system using the “Image.alphaHitTestMinimumThreshold” function.
How do you do that with the UI Builder?


Bump, looking for this too, as far as I looked into it yet, it’s not easily doable, I don’t even need pixel-based / raster graphics like sampling. Would love to see the possibility to define a function to be checked whether a pointer is inside or not inside the control
There are two undocumented overridable functions if you use a custom-control which inherits from VisualElement which check if a point or rect overlaps, but they seem layout-specific, don’t know it they’re used for any events like OnPointerEnter (And I had not the time to try that yet, its only a private side-project I work on here and there):

public virtual bool ContainsPoint(Vector2 localPoint) => this.rect.Contains(localPoint);

public virtual bool Overlaps(Rect rectangle) => this.rect.Overlaps(rectangle, true);

I also saw the overridable ExecuteDefaultAction EventFunctions they seem pretty complex in what they do didn’t had the time to get deep into that too. Not sure if you’d even be able to override the whole event delegation (trickle-down / bubble-up) there, on first glance it looks like the key-parts are not accessible from the public side. Some clarification from UT / documentation on how to achieve that would be nice.

Hello Nefahl

Thanks for the info. I must continue to deal with the issue. I stay tuned and wenn I have a solution, I will let you know here!

Small update: I tried to override the mentioned functions above, to no success, they’re indeed not called from UITK when hovering a VisualElement / moving the pointer on it.

Here is one example where a custom ContainsPoint implementation will ignore transparent pixels.
Please note that it assumes the bg image is stretched, not repeated, etc. so YMMV.

9412109–1318205–AlphaTestElement.cs (1.85 KB)

Seems like my rider wasn’t able to break on a breakpoint set in the overridden ContainsPoint function due to an outdated Rider-Package in my project, after updateing the plugin the breakpoint got hit properly, Sorry for the misinformation above. :slight_smile:

Thanks for the info!

Unfortunately I can’t figure out how to use the script exactly. I have no experience how a script that inherits from a “VisualElement” is built in or used exactly in Unity.

  • I can’t attach the script to a GameObject
  • How do I get true or false from “ContainsPoint” exactly?
  • Do I have to check the cursor position? But I can’t do that with a “VisualElement”?!

Thanks in advance!

It’s a visual element. You either use it in the UI builder or manually create it via C# code.

Though not sure ifyou need to use the ContainsPoint method yourself, or if the overarching system handles that. The documentation implies the latter.

Though this example is probably better off as a base-class that you inherit other custom visual elements from and use as required. You could make a Button derived class that uses this implementation.

If you don’t know how to make/use custom visual elements you should consult with the documentation: https://docs.unity3d.com/2021.3/Documentation/Manual/UIE-expose-custom-control-to-uxml.html

  • I can’t attach the script to a GameObject:
    VisualElement is only used inside the UITK context, you don’t operate in GameObject-Space with those, other than using a UI-Document (if used for in-game-ui).
    That said, unfortunately I don’t know yet how to expose all of that to the UI builder to be used without coding as I only started with the framework myself and build a small editor where I don’t intent to reuse the Element outside of, so I only create the Elements in my Editor-panel from script yet.

  • How do I get true or false from “ContainsPoint” exactly?
    You don’t, you have to think in the opposite way, the framework calls the function to determine whether it should or should not enter/exit focus/press/etc. states on your visual element.
    you overwrite the function and basically can control via the function whether the element reacts or not. The example script of uMathieu does so by reading pixel-information from the texture being used. if the pixel is transparent, false is returned to tell the system that the button/element shouldn’t be regarded as valid pointer-target, thus not focusing / not reacting to button clicks. When returned true, the button/element will react.
    If you build your custom control for non-rectangular buttons you inherit from VisualElement (Maybe you can even inherit from the UITK Button-class i haven’t looked into that may be open to extention).

Then you simply override the ContainsPoint-Function the UITK-system then will use yours instead of the one define by the class you’ve inherited from:

public class NonRectangularButton : VisualElement // you may get away with using Button instead of VisualElement here too :)
{
  // [...] Some fields and properties you want


    public override bool ContainsPoint(Vector2 localPoint)
    {
           if(IsLocalPointAboveTransparentPixel(localPoint)) // you have to define that yourself somehow
           {
                // tell the system that the pointer is not above this element.
                return false;
           }
           // else tell the system the pointer is above and the element should react accordingly.
           return true;
      }
}
  • Do I have to check the cursor position? But I can’t do that with a “VisualElement”?!
    Yes, the system provides you the position of the cursor in local-coordinates when calling the ContainsPoint function when it is above the control-rect (you can imagine that as a bounding-box of your texture, see screenshot below) of the button, thats the parameter given to the ContainsPoint-Function you can use that to determine whether its above an empty pixel or not.

The good thing about that if you have a “uniform” form of button you don’t have to expensively load / read the texture, but can define a simple SDF (Signed Distance Function) or some even more simple method of determining if your point is inside/outside of your button.
For example in my case I build a small hexagon-map editor for a project of mine, so my visual-element draws a hexagon on the rectangular area of the VisualElement. In the ContainsPoint function I just have to check whether the local point given is inside or outside of the hex drawn, which involes a few simply localPoint.x is greater / smaller checks and a small left/right crossproduct calculation for the triangular part of the hex.
I don’t use a texture at all as of now. The yellow hex has the pointer inside right at the diagonal border to the centered hex (snipping tool doesn’t capture that sadly, blue point drawn in) before using the function the center-hex would be highlighted instead because of the overlap in bouncing-rects and the centered hex being the top-most in the list of elements.
The local point given to the red-bordered hex would be sth like Vector2(0.9f, 0.1f) (my hex is 1 by 1 without scaling and the localPoint wont be without scaling too, top left corner is the reference-point for the local-position given). Since that point doesn’t lie inside of the hex according to my CointainsPoint function (returns false) the system will ignore that element in terms of focus etc. and proceed to the next element where the pointer is inside the control-rect (the yellow hex) where I return true inside of ContainsPoint (the localPoint given to that Element would be sth like: Vector2(0.1f, 0.6f), so the UITK layout/focus stuff reacts accordingly and uses that as “topmost” visual element that can react to the focus-event.

9424067--1320884--upload_2023-10-22_15-4-38.png
Hopefully that helps you a bit :slight_smile:

Hello Nefahl

Thank you very much for your comments!
I will now first read up and test in detail.
I will report to you when I have implemented this for me. Possibly I come again with a question if I may?

Thanks Splash…

Hi!
Still actual, so here is a small gist to read texture and return ContainsPoint only for non-transparent pixels: Unity UIToolkit non-rectangular button, ignoring pixels with alpha less than 0.5 · GitHub