None Rectangle shaped button?

Yay, worked for me. Thnx !

Doesnt seem to work in version 5.0.
EDIT:
This post solved my problem: How to stop non-rectangular buttons from overlapping? - Questions & Answers - Unity Discussions
I leave it here in case anyone else finds this thread and gets stuck.

1 Like

No, you could do something like that, so that the feature will be updated along with Unity.

Scripts by users to add basic functionality to the UI system run a risk of breaking when Unity is updated. Which is a pain in the ass.

Hi, how did you use that post to solve this problem? I am not sure how can I implement that

The idea is the same as here, just the script in this thread doesnt seem to work. This one does: http://forum.unity3d.com/attachments/raycastmask-cs.110370/

2 Likes

Works fine for the GUI events being triggered, but…
When I try to EventSystem.current.RaycastAll I still get the GUI elements for the entire square, ignoring the raycast mask implementation of ICanvasRaycastFilter.IsRaycastLocationValid.

I found a simpler (a bit hacky maybe) option using a Collider2D and a custom raycast filter:

using UnityEngine;
using UnityEngine.UI;

[RequireComponent (typeof (RectTransform), typeof (Collider2D))]
public class Collider2DRaycastFilter : MonoBehaviour, ICanvasRaycastFilter
{
    Collider2D myCollider;
    RectTransform rectTransform;
   
    void Awake ()
    {
        myCollider = GetComponent<Collider2D>();
        rectTransform = GetComponent<RectTransform>();
    }

#region ICanvasRaycastFilter implementation
    public bool IsRaycastLocationValid (Vector2 screenPos, Camera eventCamera)
    {
        var worldPoint = Vector3.zero;
        var isInside = RectTransformUtility.ScreenPointToWorldPointInRectangle(
            rectTransform,
            screenPos,
            eventCamera,
            out worldPoint
        );
        if (isInside)
            isInside = myCollider.OverlapPoint(worldPoint);

        return isInside;
    }
#endregion
}

This could be improved by creating a custom alternative to replace the Collider2D but that’s a bit too much work for what I need.

1 Like

I am getting this error when trying to reproduce your result:

importer.GetNPOTScale() == TextureImporter::kNPOTKeep
UnityEditor.DockArea:OnGUI()

When I set the texture mode to advanced and sprite single on Unity5.1.0f3

Edit: Error got removed when I set the texture type back to Sprite 2D and then again to advanced. So no idea what was causing it but this seemed to avoid the problem.

anyway for RawImage ?

Could you please share if there’s any chance we can get non-rectangular button/toggle functionality out of the box in the future versions?

I would like to offer one more solution to the problem: we can use objects texture and check its alpha when deciding whether to respond to input events. That way UI objects will not react to input in any transparent areas, which seems like the most versatile solution.

I’ve made a plugin that works like that: Unity Asset Store - The Best Assets for Game Making

It also handles any transformations (scale, rotation), anchor setups, works with both orthographic and perspective camera modes (screen/world spaces), supports atlases and all the filled image modes. Been using it myself in several projects for all kind of non-rect buttons\toggles and should say it does it’s job quite good :slight_smile:

3 Likes

Hello,

first thanks to @senritsu_1 for this cool script.

I was searching for the best method to “None Rectangel shaped button”.
And I found this post, however it works partially for me and can’t figure out the problem.

I have triangle buttons, with this script, transparent area are not clickable as I wanted, but some colored/non-transparent areas are being disabled too.
I am printing the alpha values of the current mouse positon, and in the green areas in the picture, alpha is printed 0 and the button is not clickable.
This happens randomly since I have another triangle buttons, where the borders are respected completed, while others have different edge-areas not working … I though of a collider-rlated issue, and now using polygon colliders, and colliders expand to the borders of my triangle image, but the behavoir was the same.

Can you tell me what my issue might be related to or any hints?

Thanks!
2801221--203330--quest1.jpg

Hello there
You can make a panel and add on it a “button component”.
And now, you can change colors on press or above it!

Hi,
I tried both the proposed methods, the alphaHitTestMinimumThreshold and the RaycastMask script.
They work fine except there seems to be a vertical offset between the pixels of the sprite and the position of the mouse, when the on enter/exit events occur.

I attach a test project to show the problem: you can see that when you put the mouse in the transparent area, very near the cars icons, the image reacts with a vertical offset of about 5 pixels.

What could be the problem?

Thank you

3269772–252557–TestPixelButton.rar (2.9 MB)

Hi, i updated the RaycastMask class with all the Image type included if you want

using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(RectTransform))]
[RequireComponent(typeof(Image))]
public class RaycastMask : MonoBehaviour, ICanvasRaycastFilter
{
    #region Properties
    Image m_Image;
    Sprite m_Sprite;
    #endregion


    #region Public Methods
    public bool IsRaycastLocationValid(Vector2 screenPosition, Camera eventCamera)
    {
        // Set sprite
        m_Sprite = m_Image.sprite;

        // SetRectTransform
        RectTransform rectTransform = (RectTransform)transform;

        // GetLocalPosition relative to pivot point
        Vector2 localPositionPivotRelative;
        RectTransformUtility.ScreenPointToLocalPointInRectangle((RectTransform)transform, screenPosition, eventCamera, out localPositionPivotRelative);

        // convert to bottom-left origin coordinates
        Vector2 localPosition = new Vector2(localPositionPivotRelative.x + rectTransform.pivot.x * rectTransform.rect.width,
            localPositionPivotRelative.y + rectTransform.pivot.y * rectTransform.rect.height);

        Rect spriteRect = m_Sprite.textureRect;
        Rect maskRect = rectTransform.rect;

        Vector2Int pixelPosition = new Vector2Int(0, 0);
        Vector2 ratioPosition = new Vector2(localPosition.x / maskRect.width, localPosition.y / maskRect.height);
       
        bool isValid = false;

        switch (m_Image.type)
        {
            case Image.Type.Filled:
                {
                    pixelPosition = new Vector2Int(Mathf.FloorToInt(spriteRect.x + spriteRect.width * ratioPosition.x), Mathf.FloorToInt(spriteRect.y + spriteRect.height * ratioPosition.y));
                    if( m_Image.fillMethod == Image.FillMethod.Vertical || m_Image.fillMethod == Image.FillMethod.Horizontal)
                    {
                        float position = 0;
                        switch (m_Image.fillMethod)
                        {
                            case Image.FillMethod.Horizontal:
                                position = ratioPosition.x;
                                break;
                            case Image.FillMethod.Vertical:
                                position = ratioPosition.y;
                                break;
                        }
                        isValid = (m_Image.fillOrigin == 0 && position <= m_Image.fillAmount) || (m_Image.fillOrigin == 1 && position >= (1 - m_Image.fillAmount));
                    }
                    else
                    {
                        Vector2 ratioRelativeToCenter = new Vector2();
                        float positionAngle = 0, startFillAngle = 0, variableFillAngle = 0;
                        switch (m_Image.fillMethod)
                        {
                            case Image.FillMethod.Radial90:
                                variableFillAngle = Mathf.PI / 2;
                                switch ((Image.Origin90)m_Image.fillOrigin)
                                {
                                    case Image.Origin90.BottomLeft:
                                        ratioRelativeToCenter = ratioPosition;
                                        startFillAngle = 0;
                                        break;
                                    case Image.Origin90.BottomRight:
                                        ratioRelativeToCenter = ratioPosition + Vector2.left;
                                        startFillAngle = Mathf.PI / 2;
                                        break;
                                    case Image.Origin90.TopLeft:
                                        ratioRelativeToCenter = ratioPosition + Vector2.down;
                                        startFillAngle = -Mathf.PI / 2;
                                        break;
                                    case Image.Origin90.TopRight:
                                        ratioRelativeToCenter = ratioPosition - Vector2.one;
                                        startFillAngle = -Mathf.PI;
                                        break;
                                }
                                break;
                            case Image.FillMethod.Radial180:
                                variableFillAngle = Mathf.PI;
                                switch ((Image.Origin180)m_Image.fillOrigin)
                                {
                                    case Image.Origin180.Bottom:
                                        ratioRelativeToCenter = new Vector2(2 * ratioPosition.x - 1, ratioPosition.y);
                                        startFillAngle = 0;
                                        break;
                                    case Image.Origin180.Right:
                                        ratioRelativeToCenter = new Vector2(ratioPosition.x - 1, 2 * ratioPosition.y - 1);
                                        startFillAngle = Mathf.PI / 2;
                                        break;
                                    case Image.Origin180.Left:
                                        ratioRelativeToCenter = new Vector2(ratioPosition.x, 2 * ratioPosition.y - 1);
                                        startFillAngle = -Mathf.PI / 2;
                                        break;
                                    case Image.Origin180.Top:
                                        ratioRelativeToCenter = new Vector2(2 * ratioPosition.x - 1, ratioPosition.y - 1);
                                        startFillAngle = -Mathf.PI;
                                        break;
                                }
                                break;
                            case Image.FillMethod.Radial360:
                                ratioRelativeToCenter = new Vector2(2 * ratioPosition.x - 1, 2 * ratioPosition.y - 1);
                                variableFillAngle = 2 * Mathf.PI;
                                switch ((Image.Origin360)m_Image.fillOrigin)
                                {
                                    case Image.Origin360.Bottom:
                                        startFillAngle = -Mathf.PI / 2;
                                        break;
                                    case Image.Origin360.Top:
                                        startFillAngle = Mathf.PI / 2;

                                        break;
                                    case Image.Origin360.Left:
                                        startFillAngle = -Mathf.PI;
                                        break;
                                    case Image.Origin360.Right:
                                        startFillAngle = 0;
                                        break;
                                }
                                break;
                        }
                        positionAngle = Mathf.Atan2(ratioRelativeToCenter.y, ratioRelativeToCenter.x);
                        if (positionAngle < startFillAngle) positionAngle += 2 * Mathf.PI;
                        isValid = (m_Image.fillClockwise && positionAngle >= startFillAngle + variableFillAngle * (1 - m_Image.fillAmount)) || (!m_Image.fillClockwise && positionAngle <= startFillAngle + variableFillAngle * m_Image.fillAmount);
                    }
                }
                break;
            case Image.Type.Sliced:
                {
                    Vector4 border = m_Sprite.border;
                    isValid = true;

                    // x slicing
                    if (localPosition.x < border.x) pixelPosition.x = Mathf.FloorToInt(spriteRect.x + localPosition.x);
                    else if (localPosition.x > maskRect.width - border.z) pixelPosition.x = Mathf.FloorToInt(spriteRect.x + spriteRect.width - (maskRect.width - localPosition.x));
                    else pixelPosition.x = Mathf.FloorToInt(spriteRect.x + border.x + ((localPosition.x - border.x) / (maskRect.width - border.x - border.z)) * (spriteRect.width - border.x - border.z));

                    // y slicing
                    if (localPosition.y < border.y) pixelPosition.y = Mathf.FloorToInt(spriteRect.y + localPosition.y);
                    else if (localPosition.y > maskRect.height - border.w) pixelPosition.y = Mathf.FloorToInt(spriteRect.y + spriteRect.height - (maskRect.height - localPosition.y));
                    else pixelPosition.y = Mathf.FloorToInt(spriteRect.y + border.y + ((localPosition.y - border.y) / (maskRect.height - border.y - border.w)) * (spriteRect.height - border.y - border.w));
                }
                break;
            case Image.Type.Simple:
            default:
                {
                    isValid = true;
                    pixelPosition = new Vector2Int(Mathf.FloorToInt(spriteRect.x + spriteRect.width * ratioPosition.x), Mathf.FloorToInt(spriteRect.y + spriteRect.height * ratioPosition.y));
                }
                break;
        }

        try
        {
            isValid &= m_Sprite.texture.GetPixel(pixelPosition.x,pixelPosition.y).a > 0;
            return isValid;
        }
        catch (UnityException e)
        {
            Debug.LogError("Mask texture not readable, set your sprite to Texture Type 'Advanced' and check 'Read/Write Enabled'");
            return false;
        }
    }
    #endregion

    #region Private Methods
    void Start ()
    {
        m_Image = GetComponent<Image>();
    }
    #endregion

}
8 Likes

thanks that’s working for me

2 Likes

Theese scripts are so long and difficult to understand… I dig out 1 solution from some guy. Easy in 2 steps - add script to button object; setup your custom image import settings.

Enjoy your custom button :slight_smile:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ButtonClickToVisibleOnly : MonoBehaviour
{
    public float alphaTreshold = 0.1f;

    /*
     * IMAGE MUST HAVE IN SETTINGS
     *          TEXTURE TYPE - SPRITE (2D AND UI)
     *          READ/WRITE ENABLED
     */


    void Start()
    {
        this.GetComponent<Image>().alphaHitTestMinimumThreshold = alphaTreshold;
    }

}

7 Likes

This works so perfectly and doesn’t require any complex script or asset purchase.

Any better solution than that yet?

LEGEND