Unity使用Dotween实现一个快捷的指令圆盘

玩方舟生存进化时总是用到这个圆盘功能,最近在用Blender时也有这个功能,心里觉得用在某些游戏上确实挺好用的,草草写了一个功能,主要在数学上有点麻烦,应该可以再改进运算方法
using DG.Tweening;
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.InputSystem;
using Sirenix.OdinInspector;
using System.Linq;
using JetBrains.Annotations;
using Extraerrestrial.Tools;
using UnityEngine.Profiling;
namespace Extraerrestrial.Gameplay
{
public class CommandDial : MonoBehaviour
{
public Sequence CommandDialIn;
public float AnimationDuration = 0.5f;
public AnimationCurve AnimationCurve;
public GameObject CommandItemPrefab;
public float Distance;
public int CommandNumbers;

    protected List<GameObject> _list;
    protected GameObject _root;
    public void Start()
    {
        CommandDialIn = DOTween.Sequence();
        InputManager.Instance.CallCommandDial.performed += OnCallDial;
        InputManager.Instance.CallCommandDial.canceled += OnHideDial;
    }
    public void Update()
    {

        ChooseDial();
    }
    public void ChooseDial()
    {
        Vector2 mouseWorldPos = ARTools.GetMouseWorldPosition();
        if (_root == null || _list == null || _list.Count == 0) return;
        Vector2 rootPos = _root.transform.position;
        Vector2 dir = mouseWorldPos - rootPos;
        int selectedIndex=GetPointSector(dir, CommandNumbers); 
        // 响应选中的指令项
        for (int i = 0; i < _list.Count; i++)
        {
            var item = _list[i];
            if (item == null) continue;
            // 这里可以高亮选中的item,或做其他反应
            if (i == selectedIndex)
            {
                item.GetComponent<SpriteRenderer>().color = Color.yellow;
            }
            else
            {
                item.GetComponent<SpriteRenderer>().color = Color.white;
            }
        }
    }
    public void OnCallDial(InputAction.CallbackContext callbackContext)
    {
        CallCommandDial();
    }
    public void OnHideDial(InputAction.CallbackContext callbackContext)
    {
        HideCommandDial();
    }
    public void CallCommandDial()
    {
        Vector2 mouseWorldPos = ARTools.GetMouseWorldPosition();
        GameObject root = new GameObject("CommandDialRoot");
        _root = root;
        root.transform.position = mouseWorldPos;
        _list = new List<GameObject>();
        for (int i = 0; i < CommandNumbers; i++)
        {
            GameObject CommandItem = Instantiate(CommandItemPrefab, root.transform);
            CommandItem.transform.position = root.transform.position;
            _list.Add(CommandItem);
        }
        float degree = 360f / CommandNumbers;
        CommandDialIn.Kill();
        CommandDialIn = DOTween.Sequence();
        int j = 0;
        foreach (var item in _list)
        {
            if (item == null) continue;
            float currentDegree = degree * j++;
            CommandDialIn.Join((item.GetComponent<Transform>().DOMove(item.GetComponent<Transform>().position + Quaternion.Euler(new Vector3(0, 0, -currentDegree)) * Vector3.up * Distance, AnimationDuration))).SetEase(AnimationCurve).SetAutoKill(false).Pause();
        }

        CommandDialIn.Play();
    }
    public void HideCommandDial()
    {
        CommandDialIn.OnRewind(null);
        CommandDialIn.OnRewind(() =>
        {
            Destroy(_list[0].transform.parent.gameObject);
            _root = null;
        }).PlayBackwards();

    }
    public void OnDestroy()
    {
        InputManager.Instance.CallCommandDial.performed -= OnCallDial;
        InputManager.Instance.CallCommandDial.canceled -= OnHideDial;

    }
    public int GetPointSector(Vector2 targetVector, int n)
    {
        // 1. 基础参数计算(核心不变)
        float totalAngle = 360f;
        float anglePerPoint = totalAngle / n; // 每个顶点的中心夹角x
        float sectorHalfAngle = anglePerPoint / 2; // 领域半角x/2

        // 2. 将目标向量转换为极角(以Y轴正方向为0°,顺时针为正,范围[0,360))
        float targetPolarAngle = NormalizeAngle(GetPolarAngle(targetVector));
        KeyValuePair<float,float> zeroRange= new KeyValuePair<float,float>(90-sectorHalfAngle,90+sectorHalfAngle);
        // 3. 遍历所有顶点(0~n-1),计算每个顶点的角度区间
        for (int i = 0; i < n; i++)
        {
            // 核心修正:0号顶点=90°,i号顶点=90° + i*每个顶点的夹角(顺时针排列)
            float pointCenterAngle = NormalizeAngle(90f + i * anglePerPoint);

            float sectorStart = NormalizeAngle(zeroRange.Key - i * anglePerPoint);
            float sectorEnd = NormalizeAngle(zeroRange.Value - i * anglePerPoint);
            // 计算当前顶点的领域区间(处理跨0°的边界情况)
            if((zeroRange.Key - i * anglePerPoint)* (zeroRange.Value - i * anglePerPoint)<0)
            {
                if(zeroRange.Value-i*anglePerPoint>targetPolarAngle||NormalizeAngle(zeroRange.Key-i*anglePerPoint)<targetPolarAngle)
                {
                    return i;
                }
            }
            else
            {
            
                // 4. 判断目标角度是否在当前顶点的领域内
                if (targetPolarAngle >= sectorStart && targetPolarAngle <=sectorEnd)
                {
                    return i;
                }
            }
        }
    }
    private float GetPolarAngle(Vector2 vector)
    {
        return Mathf.Atan2(vector.y, vector.x) * Mathf.Rad2Deg; // 转换为以Y轴为0°,顺时针为正
    }

    private float NormalizeAngle(float angle)
    {
        angle = angle % 360f;
        if (angle < 0)
        {
            angle += 360f;
        }
        return angle;
    }

}

}

posted on 2026-03-04 23:23  Atheon  阅读(0)  评论(0)    收藏  举报