I recently solved a seemingly common problem that I came across in Unity. I had a non-rectangular image (that is, an image with transparency), and I wanted to only detect clicks on the areas that made up the actual graphic, filtering out any transparent pixel clicks. Unfortunately, this functionality is not built into Unity (at least not as of Unity 5.2). But alas, don’t lose hope! There’s a simple solution at hand. With one script, you should be on your way to buttons of any shape.

Prereqisites

  1. Make sure you have an Image component attached to your UI object (this refers to using the “new” Unity UI framework introduced in 4.6).
  2. Make sure your textures are imported as readable by changing the texture type to Advanced in the texture’s import settings and checking Read/Write Enabled.

The script

Create the following script and attach it to your UI object. It works regardless of the object’s anchor and pivot locations, and regardless of canvas scaling (for example, via the Canvas Scaler component on a UI canvas).

using UnityEngine;
using UnityEngine.UI;

public class AlphaRaycastFilter : MonoBehaviour, ICanvasRaycastFilter
{
    public float minAlpha;

    private RectTransform rectTransform;
    private Image image;

    void Awake()
    {
      rectTransform = transform as RectTransform;
      image = GetComponent<Image>();
    }

    #region ICanvasRaycastFilter implementation

    public bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
    {
        // Get normalized hit point within rectangle (aka UV coordinates originating from bottom-left)
        Vector2 rectPoint;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera, out rectPoint);
        Vector2 normPoint = (rectPoint - rectTransform.rect.min);
        normPoint.x /= rectTransform.rect.width;
        normPoint.y /= rectTransform.rect.height;

        // Read pixel color at normalized hit point
        Texture2D texture = image.sprite.texture;
        Color color = texture.GetPixel((int)(normPoint.x * texture.width), (int)(normPoint.y * texture.height));

        // Keep hits on pixels above minimum alpha
        return color.a > minAlpha;
    }

    #endregion
}

Test it

You can test it by attaching this script to the same object:

using UnityEngine;
using UnityEngine.EventSystems;

public class Test : MonoBehaviour, IPointerClickHandler
{
    #region IPointerClickHandler implementation

    public void OnPointerClick(PointerEventData eventData)
    {
        print("Clicked me!");
    }

    #endregion
}

Going further

Because we’re simply implementing ICanvasRaycastFilter.IsRaycastLocationValid, the body of this method can be tweaked for whatever scenario you’re in (not just looking at pixel values). Note that the above script is valid for Simple image types only. Adjustments would need to be made to support images that are tiled, sliced, etc.


icanvasraycastfilter unity button
PREV NEXT