此方法只需要一个空节点 脚本挂上即可
DOTWeen可以去官网下载 不用的只能拖动 没有结尾的惯性效果和对齐
using DG.Tweening;
using System.Collections;
using System.Collections.Generic;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
/// <summary>
/// 轮转图
/// </summary>
public class RationChart : MonoBehaviour, IDragHandler, IEndDragHandler
{
int num = 4;
public Image prefab;
float spacing=100;
float custSpeed=100;
float max = 1;
float min = 0.05f;
float c;
float r;
float ang;
float distance;
List<GameObject> list = new List<GameObject>();
List<Transform> sorts = new List<Transform>();
/// <summary>
/// 换装的脚本
/// </summary>
public Loading loading;
/// <summary>
/// 类型
/// </summary>
public static int chartindex=-1;
// Start is called before the first frame update
void Start()
{
c = (spacing + prefab.rectTransform.rect.width) * num;
r = c / (Mathf.PI * 2);
ang = (Mathf.PI * 2) / num;
Move();
}
protected void Move()
{
float moveAng = distance / r;
for (int i = 0; i < num; i++)
{
if (list.Count <= i)
{
GameObject go = Instantiate(Resources.Load<GameObject>("Image"),transform);
go.name = i.ToString();
list.Add(go);
sorts.Add(go.transform);
go.GetComponent<Image>().sprite = Resources.Load<Sprite>(i.ToString());
}
float x = Mathf.Sin(i * ang + moveAng) * r;
float z = Mathf.Cos(i * ang + moveAng) * r;
list[i].transform.localPosition = Vector3.right * x;
float y = (z + r) / (r + r);
float scale = (max - min) * y + min;
list[i].transform.localScale = Vector3.one * scale;
}
sorts.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;
}
});
for (int i = 0; i < sorts.Count; i++)
{
sorts[i].SetSiblingIndex(i);
}
}
/// <summary>
/// 拖拽中
/// </summary>
/// <param name="eventData"></param>
public void OnDrag(PointerEventData eventData)
{
distance += eventData.delta.x;
Move();
}
/// <summary>
/// 拖拽结束
/// </summary>
public void OnEndDrag(PointerEventData eventData)
{
float speed = eventData.delta.x;
float time = Mathf.Abs(speed) / custSpeed;
DOTween.To((a) =>
{
distance += a;
Move();
}, speed, 0, time).OnComplete(() =>
{
Align(list.IndexOf(sorts[num - 1].gameObject));
});
}
private void Align(int v)
{
int n = v;
loading.ChangeLoading(chartindex,n);
float aliAng = Mathf.Asin(list[n].transform.localPosition.x/r);
float aliDis=aliAng*r;
float time = Mathf.Abs(aliDis) / custSpeed;
DOTween.To((a) =>
{
distance = a;
Move();
}, distance, distance - aliDis, time).OnComplete(() =>
{
transform.GetChild(0).gameObject.SetActive(true);
});
}
}
为了使拖动效果和对齐过程更加平滑,你可以对以下几个方面进行优化:
- 降低速度变化的突兀性:调整拖动结束时惯性动画的插值函数,使得拖动和对齐更加自然和平滑。
- 减少插值的生硬感:使用更加平滑的缓动函数 (Easing Functions) 来调整动画速度。
具体优化方案:
- 修改惯性动画的插值:
- 使用
Ease.OutCubic
或者Ease.OutQuint
来代替默认的线性插值,使动画开始快速,然后逐渐减慢到目标位置。
- 使用
- 对齐时的缓动函数:
- 同样使用
Ease.OutCubic
或者其他更加平滑的缓动函数来对齐,使图片缓慢到达指定位置。
- 同样使用
优化后的代码:
using DG.Tweening;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
/// <summary>
/// 轮转图的控制器类,实现拖拽功能
/// </summary>
public class RationChart : MonoBehaviour, IDragHandler, IEndDragHandler
{
int num = 4; // 轮播项的数量
public Image prefab; // 预制的图片项
float spacing = 100; // 图片之间的间距
float custSpeed = 100; // 惯性动画速度
float max = 1; // 图片的最大缩放比例
float min = 0.05f; // 图片的最小缩放比例
float c; // 圆的周长
float r; // 圆的半径
float ang; // 每个图片项之间的角度差
float distance; // 控制拖动距离的变量
List<GameObject> list = new List<GameObject>(); // 存储生成的图片项
List<Transform> sorts = new List<Transform>(); // 存储图片项的 Transform,用于排序
public static int chartindex = -1; // 当前选择的图片项索引
void Start()
{
// 计算圆的周长和半径
c = (spacing + prefab.rectTransform.rect.width) * num;
r = c / (Mathf.PI * 2);
ang = (Mathf.PI * 2) / num;
// 初始化图片项并调用 Move 方法进行布局
Move();
}
/// <summary>
/// 根据拖动距离重新布局图片项的位置和缩放
/// </summary>
protected void Move()
{
float moveAng = distance / r; // 根据拖动距离计算移动角度
for (int i = 0; i < num; i++)
{
if (list.Count <= i)
{
GameObject go = Instantiate(Resources.Load<GameObject>("Image"), transform);
go.name = i.ToString();
list.Add(go);
sorts.Add(go.transform);
go.GetComponent<Image>().sprite = Resources.Load<Sprite>(i.ToString());
}
// 计算图片项的 x 和 z 坐标
float x = Mathf.Sin(i * ang + moveAng) * r;
float z = Mathf.Cos(i * ang + moveAng) * r;
list[i].transform.localPosition = Vector3.right * x; // 设置图片项的局部位置
// 调整缩放比例
float y = (z + r) / (r + r);
float scale = (max - min) * y + min;
list[i].transform.localScale = Vector3.one * scale;
}
// 对图片项按缩放比例排序,确保正确的显示层次
sorts.Sort((a, b) => a.localScale.x.CompareTo(b.localScale.x));
// 根据排序设置图片项的显示层级
for (int i = 0; i < sorts.Count; i++)
{
sorts[i].SetSiblingIndex(i);
}
}
/// <summary>
/// 拖拽过程中实时更新图片项的位置
/// </summary>
/// <param name="eventData">拖拽事件的数据</param>
public void OnDrag(PointerEventData eventData)
{
distance += eventData.delta.x; // 更新拖动距离
Move(); // 实时更新图片项位置
}
/// <summary>
/// 拖拽结束后,计算惯性动画并进行对齐
/// </summary>
public void OnEndDrag(PointerEventData eventData)
{
float speed = eventData.delta.x; // 拖拽速度
float time = Mathf.Abs(speed) / custSpeed; // 动画持续时间
// 使用 DOTween 进行惯性动画,使用更平滑的缓动函数
DOTween.To((a) =>
{
distance += a;
Move();
}, speed, 0, time).SetEase(Ease.OutCubic) // 改为 Ease.OutCubic
.OnComplete(() =>
{
Align(list.IndexOf(sorts[num - 1].gameObject)); // 动画结束后对齐
});
}
/// <summary>
/// 对齐到最近的图片项
/// </summary>
/// <param name="v">最近图片项的索引</param>
private void Align(int v)
{
int n = v;
float aliAng = Mathf.Asin(list[n].transform.localPosition.x / r); // 计算对齐角度
float aliDis = aliAng * r; // 对齐距离
float time = Mathf.Abs(aliDis) / custSpeed; // 动画持续时间
// 使用 DOTween 进行对齐动画,使用更加平滑的缓动函数
DOTween.To((a) =>
{
distance = a;
Move();
}, distance, distance - aliDis, time).SetEase(Ease.OutCubic) // 改为 Ease.OutCubic
.OnComplete(() =>
{
transform.GetChild(0).gameObject.SetActive(true); // 对齐完成后显示最上层图片
});
}
}
主要改进点:
- 惯性动画更平滑:使用
SetEase(Ease.OutCubic)
,这个缓动函数使得动画在开始时速度较快,结束时逐渐减速,模拟真实物理效果。 - 对齐效果更平滑:在对齐时同样使用
Ease.OutCubic
减缓对齐过程,让图片移动的过渡更柔和。
你还可以尝试其他的缓动函数,比如 Ease.OutQuint
、Ease.OutExpo
等,根据实际需求来选择合适的效果。
最后 Inspector改变参数 值不是固定的