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的内部坐标,然后判定是否在多边形内部就可以实现不规则按钮点击检测。(以后老电脑一炸就知道需要优化了)
工具使用方法,选中物体后,右击添加点,鼠标左键拖动调整位置,暂时不支持内部带孔的。
更新,解决点击重叠的问题