Hello,
I am working on a city map type of interface and I am trying to set it up so that MouseEnterEvent and MouseLeaveEvent trigger when they are over an opaque part of the background image of an Visual Element and to ignore the transparent parts of the image. Basically the functionality that I am after is that certain buildings should highlight when I mouse over them. The entire UI is being done with UI Toolkit so I cant really use raycasts as far as I am aware. Any help pointing me in the right direction would be appreciated.
There is a VisualElement.ContainsPoint method (Unity - Scripting API: UIElements.VisualElement.ContainsPoint) that basically does what you’re describing. You could try computing the relative position of the point in texture coordinates and sampling your texture to check if it’s transparent at that location.
I haven’t seen many uses of that method in general so I’m not 100% sure how robust it is, but it’s what it was created for so if it doesn’t work we’d want to know. I hope this helps!
Awesome thank you,
I was just looking at the docs and noticed that it doesnt show this method for 2021.1
Was this recently added to 2021.2 or is it also available in 2021.1?
If so I will have to update to the beta version to test later.
I think it’s been there for quite a while. The documentation may have just been added recently, that’s probable what happened.
It only partially works (see attached).
With or without call to base.ContainsPoint, which ensures whether it’s in the rect or not.
using Common.Util;
// TODO this can't be changed for now
namespace UnityEngine.UIElements
{
public class TransparentButton : Button
{
public new class UxmlFactory : UxmlFactory<TransparentButton, UxmlTraits>
{
}
public new class UxmlTraits : Button.UxmlTraits
{
public UxmlTraits() => focusable.defaultValue = true;
}
public override bool ContainsPoint(Vector2 point)
{
if (style.backgroundImage.value.sprite != null)
{
return !TextureHelper.IsTransparent(style.backgroundImage.value.sprite.texture, resolvedStyle, point);
}
if (style.backgroundImage.value.texture != null)
{
return !TextureHelper.IsTransparent(style.backgroundImage.value.texture, resolvedStyle, point);
}
return false;
}
}
}
And the IsTransparent code:
private static Color GetPixel(Texture2D tex, float x, float y)
{
Color pix;
var x1 = (int) Mathf.Floor(x);
var y1 = (int) Mathf.Floor(y);
if (x1 > tex.width || x1 < 0 || y1 > tex.height || y1 < 0)
{
pix = Color.clear;
}
else
{
pix = tex.GetPixel(x1, y1);
}
return pix;
}
private static bool IsTransparent(Texture2D tex, float x, float y)
{
return GetPixel(tex, x, y).a < 0.5f;
}
public static bool IsTransparent(Texture2D tex, IResolvedStyle resolvedStyle, Vector2 point)
{
var posX = MathHelper.Remap(point.x, 0, resolvedStyle.width, 0, tex.width);
var posY = MathHelper.Remap(point.y, 0, resolvedStyle.height, 0, tex.height);
return IsTransparent(tex, posX, posY);
}
I left the gray area on purpose to highlight the overlap, however even if I set the color to rgba(0,0,0,0) it obviously has no effect.
I mentioned that it only partially works - the highlight event no longer happens on the other image if I do this, however it doesn’t achieve “true” transparency because the element below it is still not hit through the transparent area.
Pointer events just don’t trigger on the item below it (I profiled it).
Is there a way to “force” it through like with raycasts?
Any feedback here?