最近项目需要设置围绕路点进行弧线运动,并且每个弧线的都不相同。
首先想到了椭圆形的弧线运动,试做了一版,策划认为弧线的弧度还是达不到要求。
于是,思考贝塞尔曲线运动,网上搜了一下贝塞尔曲线的方法
参考:Unity 工具类 之 贝塞尔 Bezier 曲线_仙魁XAN-CSDN博客
在此基础上只能说是完成关键的路径信息,但是如何运动以及如何配置还不能够完善。
第一步:生成曲线
贝塞尔曲线,通过四个点(包括起始点、终点)生成曲线
如图-四条曲线:
每个Cube对应路径的曲线
其中:point1/2/3/4各对应cube/1/2/3的四个点的位置信息,目的是为了可视化操作(这里完全可以直接填入四个关键点)
第二步:正向运动
借助DoTween的DoPath(当然,已知这条路径上的路径点,完全可以update下完成操作)
我这里为了方便:
Tween pathTween = temp.moveObj.DOPath(temp.points, m_duration, PathType.CatmullRom); pathTween.SetEase(temp.pathData.speedCure); pathTween.SetLoops(-1, LoopType.Restart); pathTween.onComplete = delegate { Debug.LogError("pathTween Complete"); };
效果是这样:
第三步:运动的同时加入缩放比例
Sequence mSquence = DOTween.Sequence(); Tween pathTween = temp.moveObj.DOPath(temp.points, m_duration, PathType.CatmullRom); pathTween.SetEase(temp.pathData.speedCure); pathTween.SetLoops(-1, LoopType.Restart); pathTween.onComplete = delegate { Debug.LogError("pathTween Complete"); }; Tween scaleTween1 = temp.moveObj.DOScale(temp.pathData.scale, temp.pathData.timeNode); scaleTween1.onComplete = delegate { Debug.LogError("scaleTween1 Complete"); }; Tween scaleTween2 = temp.moveObj.DOScale(Vector3.one, m_duration-temp.pathData.timeNode); scaleTween2.onComplete = delegate { Debug.LogError("scaleTween2 Complete"); }; mSquence.Insert(0,pathTween); mSquence.Insert(0, scaleTween1); mSquence.Insert(1, scaleTween2); mSquence.SetAutoKill(false); mSquence.Pause(); mSquence.onComplete = delegate { Debug.LogError("Squence Complete"); }; sequenceList.Add(mSquence);
这里用到了:Dotween的Sequence 参考:DOTween Sequence 使用图解_J.J.Cat's Blog-CSDN博客
这里面的timeNode是一个时间节点,指的是在某一个时间点或者说到达某时间点干什么事。
效果:
第五步:运动过去之后还能反向操作
直接在Sequence上去调用PlayBackward()
注意:必须有mSquence.SetAutoKill(false);这一句,默认是true,会直接kill掉
效果:
所有代码:
[Serializable]
public class PathData
{
public AnimationCurve speedCure;
public float timeNode;
public Vector3 scale;
}
[Serializable]
public class BezierData
{
public Transform moveObj;
public PathData pathData;
public Transform[] controlPoints;
public Vector3[] points;
}
public class BezierMotion : MonoBehaviour
{
public List<BezierData> data;
public float m_duration = 0.8f;
private List<Sequence> sequenceList;
// 设置贝塞尔插值个数
private int _segmentNum = 50;
void OnEnable()
{
sequenceList = new List<Sequence>();
RefreshPathData();
}
void Update()
{
//updatePoints();
//UpdatePosition();
}
private void OnDrawGizmos()
{
Color defaultColor = Gizmos.color;
int idx = 0;
foreach (var temp in data)
{
switch (idx)
{
case 0:
Gizmos.color = Color.green;
break;
case 1:
Gizmos.color = Color.blue;
break;
case 2:
Gizmos.color = Color.black;
break;
case 3:
Gizmos.color = Color.red;
break;
default:
Gizmos.color = Color.green;
break;
}
idx++;
int posIndex = 0;
Vector3 posTemp = Vector3.zero;
foreach (var pos in temp.points)
{
if (posIndex == 0)
{
posTemp = pos;
}
else
{
Gizmos.DrawLine(posTemp, pos);
}
posTemp = pos;
posIndex = 1;
}
Gizmos.color = defaultColor;
}
// 恢复默认颜色
Gizmos.color = defaultColor;
}
void updatePoints()
{
foreach (var temp in data)
{
temp.points = null;
temp.points = BezierUtils.GetThreePowerBezierList(temp.controlPoints[0].position, temp.controlPoints[1].position, temp.controlPoints[2].position, temp.controlPoints[3].position, _segmentNum);
}
}
private int temp = 0;
private int index = 0;
void UpdatePosition()
{
foreach (var temp in data)
{
if (index >= temp.points.Length)
continue;
temp.moveObj.position = temp.points[index];
}
temp++;
if (temp > 60)
{
index--;
if (index == 0)
{
temp = 0;
}
}
else
{
index++;
}
}
public void DoPathStart()
{
for (int i=0; i < sequenceList.Count; i++)
{
sequenceList[i].PlayForward();
}
}
public void DoPathBack()
{
for (int i=0; i < sequenceList.Count; i++)
{
sequenceList[i].PlayBackwards();
}
}
public void DoPathEnd()
{
for (int i=0; i < sequenceList.Count; i++)
{
sequenceList[i].Kill();
}
sequenceList.Clear();
}
public void RefreshPathData()
{
DoPathEnd();
foreach (var temp in data)
{
if (temp.moveObj == null)
{
continue;
}
temp.moveObj.position = temp.controlPoints[0].position;
}
updatePoints();
foreach (var temp in data)
{
Sequence mSquence = DOTween.Sequence();
Tween pathTween = temp.moveObj.DOPath(temp.points, m_duration, PathType.CatmullRom);
pathTween.SetEase(temp.pathData.speedCure);
pathTween.SetLoops(-1, LoopType.Restart);
pathTween.onComplete = delegate { Debug.LogError("pathTween Complete"); };
Tween scaleTween1 = temp.moveObj.DOScale(temp.pathData.scale, temp.pathData.timeNode);
scaleTween1.onComplete = delegate { Debug.LogError("scaleTween1 Complete"); };
Tween scaleTween2 = temp.moveObj.DOScale(Vector3.one, m_duration-temp.pathData.timeNode);
scaleTween2.onComplete = delegate { Debug.LogError("scaleTween2 Complete"); };
mSquence.Insert(0,pathTween);
mSquence.Insert(0, scaleTween1);
mSquence.Insert(1, scaleTween2);
mSquence.SetAutoKill(false);
mSquence.Pause();
mSquence.onComplete = delegate
{
Debug.LogError("Squence Complete");
};
sequenceList.Add(mSquence);
}
}
}
[CustomEditor(typeof(BezierMotion), true)]
public class BezierMotionEditor : Editor
{
private BezierMotion montion;
private void OnEnable()
{
montion = (BezierMotion)target;
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (GUILayout.Button("开始运动"))
{
montion.DoPathStart();
}
if (GUILayout.Button("反向运动"))
{
montion.DoPathBack();
}
if (GUILayout.Button("停止运动"))
{
montion.DoPathEnd();
}
if (GUILayout.Button("刷新路径"))
{
montion.RefreshPathData();
}
}
}
一些坑:
[CustomEditor(typeof(BezierMotion), true)] 如果不填true的话会提示:Multi-object editing not supported
squence Insert必须从0开始 append不能同时多个动画 join也不能同时多个动画
sequence Insert 第一个参数为时间,第二个参数为tween
join 在上一个tween末尾加进来,与接下来的并行执行