Unity不规则按钮实现

Unity不规则按钮实现的方式有很多,这里给出一个不会炸电脑的方案。家里的老电脑使用PolygonColiider2D的方案做不规则按钮检测,300个按钮直接自动重启了,所以写了这个,贴脚本:

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


#if UNITY_EDITOR
using UnityEditor;
#endif
namespace BZAssets
{
    [RequireComponent(typeof(RectTransform))]
    public class IrregularButton : Button, IPointerDownHandler, IPointerUpHandler
    {
        [Header("坐标点设置")]
        [Tooltip("多边形顶点坐标(本地坐标系)")]
        public List<Vector2> polygonPoints = new List<Vector2>();

        [Header("Performance")]
        [Tooltip("顶点数量警告阈值")]
        public int vertexWarningThreshold = 50;

        private RectTransform _rectTransform;
        Action<IrregularButton> onClickCall;

        // 当次点击有效物体集合
        static List<IrregularButton> validBtn = new();
        static int id;
        public static IrregularButton target;
        
        // 模拟事件系统
        static (PointerEventData, IrregularButton) onInValidPointDown
        {
            set
            {
                foreach (var obj in btnData)
                {
                    if (obj.Value != value.Item2)
                    {
                        obj.Value.OnInValidClick(value.Item1);
                    }
                }

                // 检查层级最高的,设置为有效点击
                int maxLayer = -250;
                for (int i = 0; i < validBtn.Count; i++)
                {
                    int index = validBtn[i].sId;
                    if (maxLayer < index && validBtn[i].gameObject.activeInHierarchy)
                    {
                        maxLayer = index;
                        target = validBtn[i];
                    }
                }
                target?.Find();
            }
        }

        static bool onPointUp
        {
            set
            {
                foreach (var obj in btnData)
                {
                    obj.Value.UpClear();
                }
            }
        }

        static Dictionary<int, IrregularButton> btnData = new();
        public int sId;

        protected override void Awake()
        {
            base.Awake();
            _rectTransform = GetComponent<RectTransform>();
            ValidatePolygon();
            sId = id ++;
            btnData.Add(sId, this);
        }

        // 验证多边形有效性
        private void ValidatePolygon()
        {
            if (polygonPoints.Count < 3)
                Debug.LogWarning($"按钮 {name} 多边形顶点数不足3个");
            
            if (polygonPoints.Count > vertexWarningThreshold)
                Debug.LogWarning($"按钮 {name} 顶点数超过建议阈值({vertexWarningThreshold})");
        }

        public void OnInValidClick(PointerEventData eventData)
        {
            // 触发点击事件
            if (IsValidClick(eventData))
            {
                validBtn.Add(this);
                isFind = true;
            }
        }

        public void UpClear()
        {
            ClickColor(false);
            validBtn.Clear();
        }

        // 重写
        public override void OnPointerDown(PointerEventData eventData)
        {
            target = null;
            validBtn.Clear();
            // 触发点击事件
            if (IsValidClick(eventData))
            {
                Find();
                target = this;
            }
            else
            {
                onInValidPointDown = (eventData, this);
            }
        }

        bool isFind = false;
        public void Find()
        {
            ClickColor(true);
        }

        public void BindCallEvent(Action<IrregularButton> call)
        {
            onClickCall = call;
        }

        public override void OnPointerUp(PointerEventData eventData)
        {
            onPointUp = true;
            if (target != null)
            {
                onClickCall?.Invoke(target);
            }
        }

        public void ClickColor(bool iClick)
        {
            Color color = iClick ? Color.red : Color.white;
            gameObject.GetComponent<Image>().color = color;
        }

        private bool IsValidClick(PointerEventData eventData)
        {
            Vector2 localPoint;
            RectTransformUtility.ScreenPointToLocalPointInRectangle(
                _rectTransform,
                eventData.position,
                eventData.pressEventCamera,
                out localPoint);

            return IsPointInPolygon(localPoint);
        }
        private bool IsPointInPolygon(Vector2 point)
        {
            int j = polygonPoints.Count - 1;
            bool inside = false;
            
            for (int i = 0; i < polygonPoints.Count; i++)
            {
                Vector2 a = polygonPoints[i];
                Vector2 b = polygonPoints[j];
                
                if ((a.y > point.y) != (b.y > point.y) &&
                    (point.x < (b.x - a.x) * (point.y - a.y) / (b.y - a.y) + a.x))
                {
                    inside = !inside;
                }
                j = i;
            }
            return inside;
        }

        public void OnDispose()
        {
            btnData.Remove(sId);
            onClickCall = null;
        }

    #if UNITY_EDITOR
        private void OnDrawGizmosSelected()
        {
            if (!Application.isPlaying)
            {
                Handles.color = Color.cyan;
                Vector3[] worldPoints = new Vector3[polygonPoints.Count];

                for (int i = 0; i < polygonPoints.Count; i++)
                {
                    worldPoints[i] = transform.TransformPoint(polygonPoints[i]);
                    if (i > 0)
                    {
                        Handles.DrawLine(worldPoints[i - 1], worldPoints[i]);
                    }
                }
                if (polygonPoints.Count > 1)
                    Handles.DrawLine(worldPoints[0], worldPoints[worldPoints.Length - 1]);
            }
        }
    #endif
    }



    #if UNITY_EDITOR
    [CustomEditor(typeof(IrregularButton))]
    public class IrregularButtonEditor : Editor
    {
        private IrregularButton _target;
        private const float ClickThreshold = 2f; // 像素偏移阈值
        private Vector2 _mouseDownPos;
        private static Vector2 _startMousePos;
        private void OnEnable() => _target = (IrregularButton)target;
        private static SceneView _currentSceneView;
        private bool _isDraggingScene;


        private static void MoveSceneView(Vector2 delta)
        {
            if (_currentSceneView == null) return;

            Camera cam = _currentSceneView.camera;
            
            Vector3 viewportDelta = new Vector3(
                -delta.x / cam.pixelWidth,
                delta.y / cam.pixelHeight,
                0);
            
            Vector3 worldMove = cam.ViewportToWorldPoint(viewportDelta) - 
                            cam.ViewportToWorldPoint(Vector3.zero);
            
            _currentSceneView.pivot += worldMove;
            _currentSceneView.Repaint();
        }

        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();
            if (GUILayout.Button("Clear all point"))
            {
                _target.polygonPoints.Clear();
            }
        }

        private void OnSceneGUI()
        {
            Event e = Event.current;
            int controlID = GUIUtility.GetControlID(FocusType.Passive);
            HandleUtility.AddDefaultControl(controlID);
            _currentSceneView = SceneView.lastActiveSceneView;

            // 鼠标按下记录初始位置
            if (e.type == EventType.MouseDown && e.button == 1)
            {
                _mouseDownPos = e.mousePosition;
                _isDraggingScene = false;
                e.Use();
            }

            // 鼠标释放时判断是否拖动
            if (e.type == EventType.MouseUp && e.button == 1)
            {
                Vector2 offset = e.mousePosition - _mouseDownPos;
                if (_isDraggingScene) return;
                if (offset.magnitude < ClickThreshold)
                {
                    // 坐标转换
                    Ray ray = HandleUtility.GUIPointToWorldRay(e.mousePosition);
                    Vector3 worldPos = ray.origin;
                    Vector2 localPos = _target.transform.InverseTransformPoint(worldPos);

                    // 添加顶点
                    Undo.RecordObject(_target, "Add Polygon Point");
                    _target.polygonPoints.Add(localPos);
                    EditorUtility.SetDirty(_target);
                }
                e.Use();
            }

            if (e.type == EventType.MouseDrag && e.button == 1)
            {
                _isDraggingScene = true;
                // 计算偏移量
                Vector2 delta = e.mousePosition - _mouseDownPos;
                MoveSceneView(delta);
                _mouseDownPos = e.mousePosition;
                e.Use();
                return; 
            }


            for (int i = 0; i < _target.polygonPoints.Count; i++)
            {
                Vector3 worldPos = _target.transform.TransformPoint(_target.polygonPoints[i]);
                EditorGUI.BeginChangeCheck();
                Handles.color = Color.red;
                Vector3 newPos = Handles.FreeMoveHandle(
                    worldPos,
                    Quaternion.identity,
                    HandleUtility.GetHandleSize(worldPos) * 0.1f,
                    Vector3.zero,
                    Handles.SphereHandleCap
                );
                if (EditorGUI.EndChangeCheck())
                {
                    Undo.RecordObject(_target, "Move Polygon Point");
                    _target.polygonPoints[i] = _target.transform.InverseTransformPoint(newPos);
                }
            }
        }
    }
#endif
}




如果PolygonCollider2D的方式,对于有重叠的还要继续筛选,所以使用采用了UGUI的Button,直接解决了多重检测的问题,剩下的就是判定当前点是否在Button的有效点击区域内部。所有通过编辑器添加点围成一个封闭区域,然后获得鼠标点在Button的内部坐标,然后判定是否在多边形内部就可以实现不规则按钮点击检测。(以后老电脑一炸就知道需要优化了)
工具使用方法,选中物体后,右击添加点,鼠标左键拖动调整位置,暂时不支持内部带孔的。
更新,解决点击重叠的问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值