废话不多说,直接上代码!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using Unity.VisualScripting;
public class RotationChart2D : MonoBehaviour, IDragHandler, IEndDragHandler//继承拖拽中接口,结束拖拽接口
{
public Image prefab; // 预制体
public float max = 1; // 缩放最大值
public float min = 0.5f; // 缩放最小值
public int n = 10; // 个数
public float spacing = 100; // UI间距的单位是像素
public float cut = 100; // 减速度
float c; // 周长
float r; // 半径
float ang; // 每个角的弧度
float moveAng = 0; // 移动的弧度
List<Image> list = new List<Image>(); // 实例化存储的Image集合
List<Transform> sortList = new List<Transform>(); // 存储实例化Image的Transform集合
void Start()
{
// 计算周长
c = (prefab.rectTransform.rect.width + spacing) * n;
// 计算半径
r = c / (2 * Mathf.PI);
// 计算每个角的弧度
ang = 2 * Mathf.PI / n;
Move();
}
public void Move()
{
for (int i = 0; i < n; i++) // n 实例化的图片个数
{
// 判断集合中有就不创建,没有再创建
if (list.Count <= i)
{
list.Add(Instantiate(prefab, transform)); // 实例化预制体,作为当前对象的子物体
list[i].transform.GetChild(0).GetComponent<Image>().sprite = Resources.Load<Sprite>("Pictures/" + i); // 设置预制体子物体的 Image 组件的 sprite
list[i].transform.name = i.ToString(); // 设置预制体的名称
sortList.Add(list[i].transform); // 将预制体的 Transform 组件添加到 sortList 中
}
float x = Mathf.Sin(i * ang + moveAng) * r; // 根据角度计算 x 坐标
float z = Mathf.Cos(i * ang + moveAng) * r; // 根据角度计算 z 坐标
// 通过 z 计算近大远小
// 计算 z 0 到 1 的比值,跟雷达图计算 nv 坐标时相同
float p = (z + r) / (2 * r);
// 线性插值公式
float scale = (max - min) * p + min;
// 设置预制体位置和缩放
list[i].rectTransform.anchoredPosition = new Vector2(x, 0);
list[i].transform.localScale = Vector3.one * scale;
}
// 对 sortList 进行排序,根据缩放大小
sortList.Sort((a, b) =>
{
if (a.localScale.x < b.localScale.x) //短一些的放后面
{
return -1;
}
else if (a.localScale.x == b.localScale.x)
{
return 0;
}
else
{
return 1;
}
});
// 对 sortList 中的元素按序重新设置 siblingIndex
for (int i = 0; i < sortList.Count; i++)
{
sortList[i].SetSiblingIndex(i);//设置对象在父节点中的顺序
}
}
public void OnDrag(PointerEventData eventData)//拖拽中
{
// 每帧拖动的距离
float dis = eventData.delta.x;
// 通过距离计算弧度
float dragAng = dis / r;
moveAng += dragAng;
Move(); //重新排序
}
public void OnEndDrag(PointerEventData eventData) //结束拖拽
{
float speed = eventData.delta.x;//速度
float time = Mathf.Abs(speed) / cut;//事件
DOTween.To((a) =>
{
// 通过距离计算弧度
float dragAng = a / r;
moveAng += dragAng;
Move();//重新排序
}, speed, 0, time).OnComplete(() =>//DoTween.To执行完毕之后 运行一下方法
{
Align();//对齐
});
}
public void Align()
{
float AlignAng = Mathf.Asin(sortList[n - 1].GetComponent<RectTransform>().anchoredPosition.x / r); // 计算对齐的弧度
float Aligndis = AlignAng * r; // 计算对齐的距离
float time = Mathf.Abs(Aligndis) / cut; // 计算对齐需要的时间
DOTween.To((a) =>
{
moveAng = a;
Move();
}, moveAng, moveAng - AlignAng, time);
}
}
注意事项:1.图片资源的路径要正确
2.脚本挂载空对象身上(空对象是Canvas的子对象)
3.如果想要改变数据有一点要注意,比如说Max图片缩放的最大值
因为我这里是public(公共)的意思,你可以把public删掉再修改数值。如果不想删除 public,还想修改数值的话,那就在脚本中改变数值,再去挂载脚本对象那里,手动修 改数值。为什么这么麻烦?因为脚本数值和检视面板上的数值不是同步的,如果你只 修改了脚本里的,你会发现检视面板那里脚本的数值没有发生变化。运行后你会看到 它的数值还是检视面板的数值。所以数值一般都以检视面板的显示为准,public类型每 次改值只要改面板的就行,不用管代码里是多少。所以,也可以直接就在挂载脚本的 那里直接修改数值,但是如果担心后期扩展的时候数据混乱的话,那我还是建议让两 个数值同步一下。
效果展示:
效果动图后期,图片变大,就是我在运行的时候,在检视面板上直接修改了Max这个公共数值