DoTween插件仿写与实践
DoTween插件
DoTween是一个用于实现插值动画(补间动画)的Unity插件。插值动画(补间动画)就是在时间轴上定义两个不同时间的动画关键帧状态后,自动生成两个时间点之间所有帧动画的动画制作方法。
DoTween官网首页给出了它的基本结构及其作用:
Tweener是Dotween的核心组件,负责实现游戏对象的动画效果,而Sequence组件则是组件的集合,包含若干Tweener和Sequence组件。
如果要让某个游戏对象在5秒内从坐标(0,0,0)移动到坐标(0,10,0)的位置,则只需要在该对象的Transform中调用DoMove函数就可以实现:transform.DoMove(new Vector3(0,10,0),5);
,这一函数会生成一个Tweener来实现运动动画,无需考虑Time.deltaTime去编写Update函数来管理每一帧的运动改变。
实验分析
我们可以通过协程和C#扩展方法来仿写DoTween实现插值动画的效果。参考官方的架构设计,我们可以将项目简化并分成Tween
、DoTween
、Extension
三个部分。UML图如下:
其中补间动画类Tween
实现动作属性的储存和管理以及实现回调。在扩展方法中,每次调用Transform的扩展方法,就会产生一个对应的Tween和一个协程,并且Tween在调用构造函数时会自动添加到动作管理类DoTween
工厂的列表中管理。
Tween对象对应的是对应动作的协程,通过改变其成员变量中的isPause
和autoKill
可以决定对应的协程是否暂停、Tween对象是否在执行完之后自动销毁。通过使用协程,我们的动画函数就可以实现不在一帧里完成,而是逐帧调用,每帧执行一部分。
Tween类代码如下:
namespace myTween
{
public class Tween
{
internal string tid;
internal Vector3 target;
internal float duration;
internal bool isPause = false;
internal bool isAutoKill = true;
internal Coroutine coroutine = null;
internal Transform transform = null;
public delegate void OnComplete();
internal OnComplete onComplete = null;
/*
internal Color targetColor;
internal Material mat = null;
*/
//internal MeshRender msr;
//public Quaternion original_rotation;
//public Quaternion target_rotation;
//constructor
public Tween(string tid, Vector3 target, float duration, Transform transform, Coroutine coroutine)
{
this.tid = tid;
this.target = target;
this.duration = duration;
this.transform = transform;
this.coroutine = coroutine;
DoTween.getInstance().Add(this);
}
//set the coroutine of tween
public void setCoroutine(Coroutine coroutine)
{
this.coroutine = coroutine;
}
public void setAutoKill(bool isAutoKill)
{
this.isAutoKill = isAutoKill;
}
public void Play()
{
isPause = false;
}
public void Pause()
{
isPause = true;
}
public void Kill()
{
MonoBehaviour mono = transform.GetComponent<MonoBehaviour>();
//Renderer rr = mat.GetComponent<Renderer>();
mono.StopCoroutine(coroutine);
//rr.StopCoroutine(coroutine);
/*
MeshRenderer msr = mono.GetComponent<MeshRenderer>();
msr.material.color = Color.white;
*/
DoTween.getInstance().Remove(this);
}
public void runOnComplete()
{
if (onComplete != null)
{
onComplete();
}
if (isAutoKill)
{
Kill();
}
}
public void setOnComplete(OnComplete fun)
{
onComplete += fun;
}
}
}
而DoTween
类实现对Tween对象的管理和PlayAll()
、PauseAll()
的用户调用。
通过把Tween对象保存在列表中可以让同一名字的动作一起暂停/播放/销毁。又或者让一个对象的所有动作停止/播放/销毁。
DoTween类代码如下:
namespace myTween {
public class DoTween
{
private static List<Tween> dotweenList = new List<Tween>();
private static DoTween dotween = null;
//控制单实例
public static DoTween getInstance()
{
if (dotween == null)
{
dotween = new DoTween();
}
return dotween;
}
public void Add(Tween d)
{
dotweenList.Add(d);
}
public void Remove(Tween d)
{
dotweenList.Remove(d);
}
public static void Play(string id)
{
foreach (Tween t in dotweenList)
{
if (t.tid == id)
{
t.Play();
}
}
}
public static void Play(Transform transform)
{
foreach (Tween d in dotweenList)
{
if (d.transform == transform)
{
d.Play();
}
}
}
public static void PlayAll()
{
foreach (Tween d in dotweenList)
{
d.Play();
}
}
public static void Pause(string id)
{
for (int i = dotweenList.Count - 1; i >= 0; i--)
{
if (dotweenList[i].tid == id)
{
dotweenList[i].Pause();
}
}
}
public static void Pause(Transform transform)
{
for (int i = dotweenList.Count - 1; i >= 0; i--)
{
if (dotweenList[i].transform == transform)
{
dotweenList[i].Pause();
}
}
}
public static void PauseAll()
{
for (int i = dotweenList.Count - 1; i >= 0; i--)
{
dotweenList[i].Pause();
}
}
public static void Kill(string id)
{
for (int i = dotweenList.Count - 1; i >= 0; i--)
{
if (dotweenList[i].tid == id)
{
dotweenList[i].Kill();
}
}
}
public static void Kill(Transform transform)
{
for (int i = dotweenList.Count - 1; i >= 0; i--)
{
if (dotweenList[i].transform == transform)
{
dotweenList[i].Pause();
}
}
}
public static void KillAll()
{
for (int i = dotweenList.Count - 1; i >= 0; i--)
{
dotweenList[i].Kill();
}
}
}
}
扩展方法用来创建Tween对象和协程、给Tween对象设置它控制的对应协程、在协程中确认Tween对象状态,以及决定是否暂停等,并负责在动作结束后调用回调函数。
通过对MonoBehaviour
与Transform
类扩展控制Tween对象的一系列方法,用户只需导入已定义的命名空间myTween
就可以扩展这些类方法。
Extension类代码如下:
namespace myTween {
public static class Extension
{
//MonoBehaviour的DoMove方法拓展
public static IEnumerator DoMove(this MonoBehaviour mono, Tween tween)
{
Vector3 speed = (tween.target - tween.transform.position) / tween.duration;
for (float f = tween.duration; f >= 0.0f; f -= Time.deltaTime)
{
tween.transform.Translate(speed * Time.deltaTime);
yield return null;
while (tween.isPause == true)
{
yield return null;
}
}
tween.runOnComplete();
}
//Transform的DoMove方法拓展
public static Tween DoMove(this Transform transform, Vector3 target, float duration)
{
MonoBehaviour mono = transform.GetComponents<MonoBehaviour>()[0];
Tween tween = new Tween("DoMove",target, duration, transform,null);
Coroutine coroutine = mono.StartCoroutine(mono.DoMove(tween));
tween.setCoroutine(coroutine);
return tween;
}
//MonoBehaviour的DoScale方法拓展
public static IEnumerator DoScale(this MonoBehaviour mono, Tween tween)
{
Vector3 speed = (tween.target - tween.transform.localScale) / tween.duration;
for (float f = tween.duration; f >= 0.0f; f -= Time.deltaTime)
{
tween.transform.localScale = tween.transform.localScale + speed * Time.deltaTime;
yield return null;
while (tween.isPause == true)
{
yield return null;
}
}
tween.runOnComplete();
}
//Transform的DoScale方法拓展
public static Tween DoScale(this Transform transform, Vector3 target, float time)
{
MonoBehaviour mono = transform.GetComponents<MonoBehaviour>()[0];
Tween tween = new Tween("DoScale", target, time, transform, null);
Coroutine coroutine = mono.StartCoroutine(mono.DoScale(tween));
tween.setCoroutine(coroutine);
return tween;
}
//MonoBehaviour的DoRotate方法拓展
public static IEnumerator DoRotate(this MonoBehaviour mono, Tween tween)
{
Vector3 angle = (tween.target - tween.transform.rotation.eulerAngles) / tween.duration;
for (float f = tween.duration; f >= 0.0f; f -= Time.deltaTime)
{
tween.transform.Rotate (angle * Time.deltaTime);
//tween.transform.rotation = Quaternion.Lerp(tween.original_rotation, tween.target_rotation, 1.0f-f/tween.duration);
yield return null;
while (tween.isPause == true)
{
yield return null;
}
}
tween.runOnComplete();
}
//Transform的DoRotate方法拓展
public static Tween DoRotate(this Transform transform, Vector3 target, float time)
{
MonoBehaviour mono = transform.GetComponents<MonoBehaviour>()[0];
Tween tween = new Tween("DoRotate", target, time, transform,null);
Coroutine coroutine = mono.StartCoroutine(mono.DoRotate(tween));
tween.setCoroutine(coroutine);
return tween;
}
/*
//MonoBehaviour的DoColor方法拓展
public static IEnumerator DoColor(this MonoBehaviour mono, Tween tween)
{
float lerp = Mathf.PingPong(Time.time, tween.duration) / tween.duration;
tween.mat.color = Color.Lerp(tween.mat.color, tween.targetColor, lerp);
yield return null;
while (tween.isPause == true)
{
yield return null;
}
//Color colorSwitch = (tween.target - tween.material.)
//Vector3 speed = (tween.target - tween.transform.localScale) / tween.duration;
for (float f = tween.duration; f >= 0.0f; f -= Time.deltaTime)
{
mono.GetComponent<Renderer>.material.color = Color.Lerp(colorStart, colorEnd, lerp);
//tween.transform.localScale = tween.transform.localScale + speed * Time.deltaTime;
yield return null;
while (tween.isPause == true)
{
yield return null;
}
}
tween.runOnComplete();
}
//Material的DoColor方法拓展
public static Tween DoColor(this Material mat, Transform transform, Color targetColor, float time)
{
float lerp = Mathf.PingPong(Time.time, time)/time;
mat.color = Color.Lerp(mat.color, targetColor, lerp);
MonoBehaviour mono = transform.GetComponents<MonoBehaviour>()[0];
Tween tween = new Tween("DoColor", targetColor, time, mono.GetComponent<Renderer>().material,null);
Coroutine coroutine = mono.StartCoroutine(mono.DoColor(tween));
tween.setCoroutine(coroutine);
return tween;
}
*/
}
}
其中DoMove()
扩展方法控制游戏对象的移动,DoScale()
方法控制游戏对象的缩放,DoRotate()
方法控制游戏对象的旋转。
for (float f = tween.duration; f >= 0.0f; f -= Time.deltaTime)
即在协程中进行的插值运算,表示每帧递减运动时间。
接着编写测试脚本TestDemo.cs
调用对应的扩展方法(注意引入myTween命名空间):
using myTween;
public class TestDemo : MonoBehaviour
{
Tween tween;
// Start is called before the first frame update
void Start()
{
//一起运行
transform.DoMove(new Vector3(5f, 5f, 5f), 5f);
transform.DoScale(new Vector3(5f, 5f, 5f), 5f);
transform.DoRotate(new Vector3(150f, 0f, 0f), 5f);
//GetComponent<Renderer>().material.DoColor(transform, Color.blue, 5);
transform.DoMove(new Vector3(-10f, -10f, -10f), 10f);
transform.DoScale(new Vector3(1f, 1f, 1f), 10f);
transform.DoRotate(new Vector3(0f, 150f, 150f), 10f);
}
// Update is called once per frame
void Update()
{
}
}
最后创建一个Cube对象,将测试脚本挂载到Cube对象上。为了更加美观可以创建一个Material为Cube着色:
运行游戏,画面如下:
DoTween插件仿写与实践
参考资料:
DoTween官方Github开源项目:DoTween官方Github
参考博客:参考CSDN博客