Unity 使用Dotween实现金币飞行动画
提供Unity中实现金币飞行的解决方案
·
需求描述:实现一个获取金币动画,要求金币先在领取点四散爆开,然后沿着平滑曲线飞到目标点。
理清思路
需求 |
对策 |
金币先在领取点四散爆开 |
1、生成一定数量的图标对象 2、在以领取点为中心的指定半径的圆中随机一个目标点,使用Dotween的DOMove方法实现四散爆开的效果 |
从领取点到目标点生成一条平滑曲线路径 |
使用二阶贝塞尔曲线生成路径节点的数组 |
实现图标曲线移动 |
使用Dotween的DOPath实现曲线移动 |
生成图标对象数量较多时,可能影响性能 |
使用对象池进行优化 |
提高曲线路径随机性,优化显示效果 |
在每次生成路径前,随机更新贝塞尔路径参考点bezierControlPoint |
代码实现
using DG.Tweening;
using UnityEngine;
using UnityEngine.Pool;
using UnityEngine.UI;
using Random = UnityEngine.Random;
//资源获取时,飞入动画逻辑
public class FlyResManager : MonoBehaviour
{
[SerializeField] private GameObject m_goMainRoot;
[SerializeField] private GameObject m_goPoolRoot;//对象池节点
[SerializeField] private GameObject m_goResObj;//图标预制体模板
[SerializeField] private GameObject m_goStart;//领取点
[SerializeField] private GameObject m_goEnd;//目标点
//resolution为int类型,表示要取得路径点数量,值越大,取得的路径点越多,曲线最后越平滑
private int resolution = 16;
private float offset = 5;
private int maxCount = 15; //最大显示图标数
private int initCount = 30; //对象池初始化的数量
private Transform mainTrans;
private Transform poolTrans;
private ObjectPool<GameObject> iconPool;
private Vector2 bezierControlPoint;
private void Start()
{
mainTrans = m_goMainRoot.transform;
poolTrans = m_goPoolRoot.transform;
InitPool();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
SingleFly(1,10);
}
}
//测试用
public void SingleFly(int id, int count)
{
var startRect = m_goStart.GetComponent<RectTransform>();
var endRect = m_goEnd.GetComponent<RectTransform>();
// startRect.anchoredPosition = startPos;
// endRect.anchoredPosition = endPos;
BezierMove(id, count, m_goStart.transform.position, m_goEnd.transform.position);
}
/// <summary>
/// 一种资源飞向目标
/// </summary>
/// <param name="id">资源id</param>
/// <param name="count">资源数量</param>
/// <param name="startPos">飞行开始位置</param>
/// <param name="endPos">飞行结束位置</param>
public void SingleFly(int id, int count, Vector2 startPos, Vector2 endPos)
{
var startRect = m_goStart.GetComponent<RectTransform>();
var endRect = m_goEnd.GetComponent<RectTransform>();
startRect.anchoredPosition = startPos;
endRect.anchoredPosition = endPos;
BezierMove(id, count, m_goStart.transform.position, m_goEnd.transform.position);
}
//贝塞尔曲线移动
private void BezierMove(int id, int count, Vector3 startPos, Vector3 endPos)
{
CalculateBezierControlPoint(startPos, endPos);
var showCount = count >= maxCount ? maxCount : count;
var playerArray = new GameObject[showCount];
var startPoint = startPos;
var sequence = DOTween.Sequence();
sequence.AppendCallback(() =>
{
for (var i = 0; i < showCount; i++)
{
var obj = iconPool.Get();
var randomPos = Random.insideUnitCircle*20;
var pos = new Vector3(startPoint.x + randomPos.x, startPoint.y + randomPos.y, startPoint.z);
var trans = obj.transform;
trans.position = startPoint;
var img = obj.GetComponent<Image>();
//此处处理图标更改加载逻辑
trans.DOMove(pos, 0.4f).SetEase(Ease.OutCirc).SetDelay(i * 0.02f);
playerArray[i] = obj;
}
});
sequence.AppendInterval(showCount * 0.05f);
sequence.AppendCallback(() =>
{
for (var i = 0; i < showCount; i++)
{
var trans = playerArray[i].transform;
var delayTime = i * 0.05f;
var path = GetBezierPath(trans.position, endPos);
trans.DOPath(path, 0.8f)
.SetEase(Ease.InCubic)
.SetOptions(false, AxisConstraint.Z) // 锁定Z轴,强制在XY平面运动
.SetDelay(delayTime).OnComplete(() =>
{
iconPool.Release(trans.gameObject);
});
}
});
}
//随机更新贝塞尔曲线参考点
private void CalculateBezierControlPoint(Vector3 startPos, Vector3 endPos)
{
//随机生成[1,4)
var value = Random.Range(1, 4);
if (value == 1)
{
bezierControlPoint = (startPos + endPos) * 0.5f;
}
else if (value == 2)
{
bezierControlPoint = new Vector2(endPos.x, startPos.y);
}
else if (value == 3)
{
bezierControlPoint = new Vector2(startPos.x, endPos.y);
}
}
/// <summary>
/// 二阶贝塞尔曲线
/// </summary>
/// <param name="start">起点</param>
/// <param name="center">控制点</param>
/// <param name="end">终点</param>
/// <param name="t">[0,1]</param>
/// <returns></returns>
private Vector3 GetBezierPoint(Vector3 start, Vector3 center, Vector3 end, float t)
{
var p0p1 = (1 - t) * start + t * center;
var p1p2 = (1 - t) * center + t * end;
return (1 - t) * p0p1 + t * p1p2;
}
/// <summary>
/// 获取贝塞尔曲线路径点
/// </summary>
/// <returns></returns>
private Vector3[] GetBezierPath(Vector3 startPos, Vector3 endPos)
{
var path = new Vector3[resolution];
for (var i = 0; i < resolution; i++)
{
var t = (i + 1) / (float)resolution; //归化到0~1范围
//使用贝塞尔曲线的公式取得t时的路径点
var data = GetBezierPoint(startPos, bezierControlPoint, endPos, t);
path[i] = data;
}
return path;
}
#region 对象池逻辑
//初始化对象池
private void InitPool()
{
iconPool = new ObjectPool<GameObject>(() =>
{
var obj = Instantiate(m_goResObj, poolTrans);
obj.SetActive(false);
obj.transform.localPosition = Vector3.zero;
return obj;
},
(go) =>
{
go.SetActive(true);
var trans = go.transform;
trans.DOKill();
trans.SetParent(mainTrans);
trans.localPosition = Vector3.zero;
},
(go) =>
{
go.SetActive(false);
var trans = go.transform;
trans.SetParent(poolTrans);
trans.localPosition = Vector3.zero;
},
(go) =>
{
Destroy(go);
}, true, 20);
}
#endregion
}
踩坑提醒
由于本实现方案主要使用Dotween,在使用tween动画数量过多时,可能会报错和警告,或者导致动画停止。可能是Dotween默认有固定容量的序列和补间池,操作超过了Dotween的默认最大容量。
解决方案
1. 增加Dotween容量
在项目启动时(如Awake或Start方法中)增加容量:
using DG.Tweening;
void Awake()
{
// 设置最大补间数量
DOTween.SetTweensCapacity(2000, 100); // 第一个参数是最大补间数,第二个是最大序列数
}
2. 正确管理动画
在对象销毁时终止相关动画
void OnDestroy()
{
transform.DOKill(); // 终止该transform上的所有Dotween动画
}
更多推荐
所有评论(0)