You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity径向菜单图标居中定位问题排查

径向菜单图标无法居中问题排查

问题背景

在Unity中通过修改Image填充量+旋转的方式制作径向菜单,已成功生成数量、尺寸、间距正确的插槽,但图标无法在对应插槽内居中。

相关代码

GenerateSlots方法

private void GenerateSlots()
{
        ClearSlots();

        // Angle per slot, accounting for spacing
        float angleStep = 360f / slotCount;
        float angleOffset = slotSpacing / 2f;

        for (int i = 0; i < slotCount; i++)
        {
            GameObject slotObject = Instantiate(slotPrefab, transform);
            UI_RadialSlot slot = slotObject.GetComponent<UI_RadialSlot>();

            // Calculate the fill amount based on the total slot count
            float fillAmount = (360f - slotSpacing * slotCount) / 360f / slotCount;
            slot.SetFillAmount(fillAmount);

            // Calculate the angle for the current slot (adjusted for spacing)
            float slotStartAngle = (angleStep * i) + angleOffset;
            float slotEndAngle = (angleStep * (i + 1)) - angleOffset;

            slotObject.transform.localRotation = Quaternion.Euler(0, 0, slotStartAngle);

            // Calculate the midpoint angle of the slot for icon placement
            float centerSlotAngle = (slotStartAngle + slotEndAngle) / 2f;

            // Calculate the radians for the midpoint of the slot
            float radians = centerSlotAngle * Mathf.Deg2Rad;

            Vector2 iconPosition = new Vector2(
                Mathf.Cos(radians) * radius,
                Mathf.Sin(radians) * radius
            );

            // Set the icon position at the calculated center of the slot
            slot.SetIconPosition(iconPosition);

            _slots.Add(slot);
        }
        
        // Select the first slot by default
        selectedEntry = _slots[0];
}

UI_RadialSlot脚本

using System;
using UnityEngine;
using UnityEngine.UI;

public class UI_RadialSlot : MonoBehaviour
{
    [SerializeField] private Image _selectedImage;
    [SerializeField] private Image _unselectedImage;
    [SerializeField] private Image _iconImage;
    private void Awake()
    {
        _unselectedImage.gameObject.SetActive(true);
        _selectedImage.gameObject.SetActive(false);
    }
    
    public void SetFillAmount(float fillAmount)
    {
        _unselectedImage.fillAmount = fillAmount;
        _selectedImage.fillAmount = fillAmount;
    }

    public void SelectSlot()
    {
        _unselectedImage.gameObject.SetActive(false);
        _selectedImage.gameObject.SetActive(true);
    }
    
    public void DeselectSlot()
    {
        _unselectedImage.gameObject.SetActive(true);
        _selectedImage.gameObject.SetActive(false);
    }
    
    public void SetIcon(Sprite icon)
    {
        _iconImage.sprite = icon;
    }
    
    public void SetIconPosition(Vector3 position)
    {
        _iconImage.rectTransform.pivot = new Vector2(0.5f, 0.5f);
        
        // Ensure the icon's anchors are set to the center to prevent stretching issues
        _iconImage.rectTransform.anchorMin = new Vector2(0.5f, 0.5f);
        _iconImage.rectTransform.anchorMax = new Vector2(0.5f, 0.5f);
        
        _iconImage.rectTransform.localPosition = position;
    }
    
    public RectTransform GetIconRectTransform()
    {
        return _iconImage.rectTransform;
    }
}

问题现象

图标(截图中模糊圆形)无法居中在对应插槽内:
10个插槽的径向菜单
11个插槽的径向菜单

问题核心:centerSlotAngle的计算错误

你计算centerSlotAngle时使用的是全局坐标系下的角度,但图标是插槽的子物体,而插槽自身已经被旋转了slotStartAngle——这导致用全局角度算出的位置,放到插槽的局部坐标系中时,叠加了插槽的旋转偏移,最终偏离了插槽中心区域。

具体逻辑冲突:

  1. 插槽被设置了localRotation = Quaternion.Euler(0,0,slotStartAngle),其局部坐标系随插槽同步旋转。
  2. 你用全局角度centerSlotAngle计算出iconPosition后,直接赋值给图标的localPosition——相当于在已旋转的插槽局部坐标系中,错误使用了全局角度的位置参数,自然会出现偏移。

修复方案

将图标位置的计算转换为插槽局部坐标系下的角度,有两种简洁实现方式:

方式一:计算局部中点角度

// 保留原全局中点角度计算
float centerSlotAngle = (slotStartAngle + slotEndAngle) / 2f;
// 转换为插槽局部坐标系的中点角度:全局角度减去插槽自身的旋转角度
float localCenterAngle = centerSlotAngle - slotStartAngle;
float radians = localCenterAngle * Mathf.Deg2Rad;

Vector2 iconPosition = new Vector2(
    Mathf.Cos(radians) * radius,
    Mathf.Sin(radians) * radius
);

slot.SetIconPosition(iconPosition);

方式二:利用Transform转换方向

// 计算全局方向向量
float centerSlotAngle = (slotStartAngle + slotEndAngle) / 2f;
float radians = centerSlotAngle * Mathf.Deg2Rad;
Vector2 globalDir = new Vector2(Mathf.Cos(radians), Mathf.Sin(radians));

// 将全局方向转换为插槽局部坐标系下的方向
Vector2 localDir = slotObject.transform.InverseTransformDirection(globalDir);
Vector2 iconPosition = localDir * radius;

slot.SetIconPosition(iconPosition);

额外验证点

  • 确认radius的值是图标到插槽中心的正确距离,不要包含插槽自身的尺寸偏移。
  • 检查插槽预制体中_iconImage的初始localPosition是否为(0,0),避免初始位置干扰。

内容的提问来源于stack exchange,提问作者Isaac Manzi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 16:59:51