实验任务:
这次我们要实现对一个动画插件Dotween的仿写。 它在 Specific settings 中 transform.DoMove 返回 Tween 对象,而我们要实现该对象,实现对动作的持续管理。在仿写前,让我们先了解一下什么是Dotween。Dotween是一款unity插值动画插件,unity里面做插值动画的插件有许多,比较常见的有itween、hotween、dotween。dotween插件在灵活性、稳定性、易用性上都十分突出。
实验需求分析:
在这次实验中我们通过协程和扩展方法来实现插值动画的实现。其中,tween对象实现动作属性的储存,pause,kill,play,complete,restart动作的管理以及实现回调,而后通过DOTween实现tween对象的管理以及用户调用PauseAll,PlayAll等方法的实现。tween对象与动作协程是一一对应关系,对tween对象的管理也就实现了对动作协程的管理。
根据官方文档的描述,我们知道实现DoMove等动作,需要两个基本类,动作类tween以及动作管理类DOTween。
其中tween类描述如下:
动作管理类控制方法描述如下:
UML图:
代码:
1.tween类:
tween类储存了动作的各种信息,如目标位置,动作限时等。每一个tween对象对应一个动作协程,通过tween内的Pause,Play等方法来控制动作的播放与暂停。我们只需在transform扩展方法中新建动作协程时,new一个tween对象管理动作对应协程,再把tween放进DOTween中进行管理。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class tween {
public string tweenType; //记录tween动作类型
public string id; //用于过滤
public int loops; //记录循环次数
public int currentLoop; //记录tween已经执行了多少次循环
public Transform transform; //记录动作对象的当前transform属性
public Vector3 originalPosition; //记录对象起始位置
public Vector3 originalRotation; //记录对象起始rotation
public Vector3 originalScale; //记录对象起始scale
public Vector3 target; //记录目标position,rotation或者scale
public float time; //记录动作限时
public bool isPause; //记录tween是否停止
public bool autoKill; //记录tween是否自动被杀死
public Coroutine coroutine; //记录tween对应的协程
public delegate void Callback(); //回调函数的委托
public Callback onComplete; //完成时的回调
public Callback onKill; //被杀死时的回调
public Callback onPause; //停止时的回调
public tween(string type, Transform trans, Vector3 tar, float ti) {
tweenType = type;
transform = trans;
target = tar;
time = ti;
//设置特殊值
originalPosition = new Vector3 (trans.position.x, trans.position.y, trans.position.z);
originalRotation = new Vector3 (trans.rotation.x, trans.rotation.y, trans.rotation.z);
originalScale = new Vector3 (trans.localScale.x, trans.localScale.y, trans.localScale.z);
//设置起始transform,在Restart等时候用
id = type;
loops = 1;
currentLoop = 0;
isPause = false;
autoKill = true;
coroutine = null;
onComplete = null;
//设置默认值
DOTween.getInstance ().Add (this);
//加进队列用于管理
}
//一下Set前缀的方法用于设置属性,为了能够像官方一样链式调用,就返回tween自己
public tween SetLoops(int l) {
loops = l;
return this;
}
//设置循环次数
public tween SetId(string i) {
id = i;
return this;
}
//设置过滤信息
public tween SetCoroutine(Coroutine c) {
coroutine = c;
return this;
}
//设置对应协程
public tween SetAutoKill(bool auto) {
autoKill = auto;
return this;
}
//设置是否被自动杀死
public tween SetOnComplete(Callback c) {
onComplete += c;
return this;
}
//设置完成时的回调函数
public tween SetOnKill(Callback c) {
onKill += c;
return this;
}
//设置被杀死时的回调函数
public tween SetOnPause(Callback c) {
onPause += c;
return this;
}
//设置停止时的回调函数
public void Pause() {
isPause = true;
}
//停止
public void Play() {
isPause = false;
}
//播放
public void Restart() {
ResetPosition ();
ResetRotation ();
ResetScale();
Play ();
}
//重启所有
public void ResetPosition() {
transform.position = new Vector3 (originalPosition.x, originalPosition.y, originalPosition.z);
}
//重启position的动作
public void ResetRotation() {
transform.rotation = Quaternion.Euler(new Vector3 (originalRotation.x, originalRotation.y, originalRotation.z));
}
//重启rotation的动作
public void ResetScale() {
transform.localScale = new Vector3 (originalScale.x, originalScale.y, originalScale.z);
}
//重启scale的动作
public void Complete() {
if (tweenType == "DoMove") {
transform.position = target;
} else if (tweenType == "DoRotate") {
transform.rotation = Quaternion.Euler (target);
} else if (tweenType == "DoScale") {
transform.localScale = target;
} else {
Debug.Log ("Wrong typeName!");
}
OnComplete ();
}
//立即完成tween动作
public void Kill() {
MonoBehaviour mono = transform.GetComponent<MonoBehaviour> ();
mono.StopCoroutine (coroutine);
DOTween.getInstance ().Remove (this);
}
//杀死tween
public void OnComplete() {
if (onComplete != null) {
onComplete ();
}
if (autoKill) {
Kill ();
}
}
//完成时的回调函数
public void OnKill() {
if (onKill != null) {
onKill ();
}
}
//杀死时的回调函数
public void OnPause() {
if (onPause != null) {
onPause ();
}
}
//停止时的回调函数
}
2.DOTween类:
DOTween类是管理整个场景tween对象的地方,每一次new一个tween,都要将其放进DOTween的单实例中,进行统一管理。当动作完成时,根据autoKill可以自动释放一个tween,删除完成的动作。用户也可以显示调用DOTween的静态函数,如PauseAll停止所有动作,PlayAll播放所有动作等。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DOTween {
private static DOTween _instance; //DOTween类的单实例
private static List<tween> tweenList = new List<tween>(); //管理tween对象的链表
//初始化一个DOTween的基本属性
public static void Init() {
_instance = new DOTween ();
}
//获得DOTween的单实例
public static DOTween getInstance() {
if (_instance == null) {
Init ();
}
return _instance;
}
//为tween的链表加入新的tween
public void Add(tween newTween) {
tweenList.Add (newTween);
}
//从链表中删除kill的tween
public void Remove(tween oldTween) {
tweenList.Remove (oldTween);
}
/*public static int getTweenSize() {
return tweenList.Count;
}*/
//停止所有的tween
public static void PauseAll() {
foreach (tween t in tweenList) {
t.Pause ();
}
}
//通过filter过滤停止指定的tween
public static void Pause(string filter) {
foreach (tween t in tweenList) {
if (t.id == filter) {
t.Pause ();
}
}
}
//通过transform停止指定位置的tween
public static void Pause(Transform trans) {
foreach (tween t in tweenList) {
if (t.transform == trans) {
t.Pause ();
}
}
}
//杀死所有tween
public static void KillAll() {
foreach (tween t in tweenList) {
t.Kill ();
}
}
//通过filter过滤杀死指定的tween
public static void Kill(string filter) {
foreach (tween t in tweenList) {
if (t.id == filter) {
t.Kill ();
}
}
}
//通过transform停止指定位置的tween
public static void Kill(Transform trans) {
foreach(tween t in tweenList) {
if (t.transform == trans) {
t.Kill ();
}
}
}
//播放所有tween
public static void PlayAll() {
foreach (tween t in tweenList) {
t.Play ();
}
}
//通过filter过滤播放制定的tween
public static void Play(string filter) {
foreach (tween t in tweenList) {
if (t.id == filter) {
t.Play ();
}
}
}
//通过transform播放指定位置的tween
public static void Play(Transform trans) {
foreach (tween t in tweenList) {
if (t.transform == trans) {
t.Play ();
}
}
}
//反转所有tween的isPause属性
public static void TogglePauseAll() {
foreach (tween t in tweenList) {
if (t.isPause) {
t.Play ();
} else {
t.Pause ();
}
}
}
//通过filter过滤反转指定的tween的isPause
public static void TogglePause(string filter) {
foreach (tween t in tweenList) {
if (t.id == filter) {
if (t.isPause) {
t.Play ();
} else {
t.Pause ();
}
}
}
}
//通过transform反转指定位置的tween属性
public static void TogglePause(Transform trans) {
foreach (tween t in tweenList) {
if (t.transform == trans) {
if (t.isPause) {
t.Play ();
} else {
t.Pause ();
}
}
}
}
//重启所有tween
public static void RestartAll() {
foreach (tween t in tweenList) {
t.currentLoop = 0;
t.Restart ();
}
}
//通过filter过滤重启指定的tween
public static void Restart(string filter) {
foreach (tween t in tweenList) {
if (t.id == filter) {
t.currentLoop = 0;
t.Restart ();
}
}
}
//通过transform重启指定位置的tween
public static void Restart(Transform trans) {
foreach (tween t in tweenList) {
if (t.transform == trans) {
t.currentLoop = 0;
t.Restart ();
}
}
}
//立即完成所有tween
public static void CompleteAll() {
foreach (tween t in tweenList) {
t.Complete ();
}
}
//通过filter过滤完成指定的tween
public static void Complete(string filter) {
foreach (tween t in tweenList) {
if (t.id == filter) {
t.Complete ();
}
}
}
//通过transform完成指定位置的tween
public static void Complete(Transform trans) {
foreach (tween t in tweenList) {
if (t.transform == trans) {
t.Complete ();
}
}
}
}
3.扩展方法:
根据老师给的样例代码,我们可以利用写好的tween与DOTween进行方法扩展。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ExtensionMethods {}
namespace MyExtensionMethods {
public static class MyExtensions {
public static IEnumerator DoMove(this MonoBehaviour mono, tween myTween) {
//外层循环决定tween的循环次数
for (; myTween.currentLoop < myTween.loops; myTween.currentLoop++) {
Debug.Log ("i = " + myTween.currentLoop);
Vector3 distance = (myTween.target - myTween.transform.position) / myTween.time;
//计算每帧移动的距离
for (float f = myTween.time; f >= 0.0f; f -= Time.deltaTime) {
//just like call update()
Debug.Log ("Move");
myTween.transform.Translate (distance * Time.deltaTime);
//移动
yield return null;
while (myTween.isPause == true) {
Debug.Log ("Move Pause");
yield return null;
}
//如果停止,将停在while中,下一帧不移动
}
if (myTween.currentLoop < myTween.loops - 1) {
myTween.ResetPosition ();
}
}
myTween.OnComplete ();
//完成后执行回调函数。
}
public static tween DoMove(this Transform transform, Vector3 target, float time)
{
MonoBehaviour mono = transform.GetComponents<MonoBehaviour> () [0];
tween myTween = new tween ("DoMove", transform, target, time);
Coroutine coroutine = mono.StartCoroutine (mono.DoMove(myTween));
myTween.SetCoroutine (coroutine);
return myTween;
}
/*
以下的DoRotate和DoScale方法的思想与DoMove类似,代码很相似,主要在要改变的属性上有所不用,
如果还要扩展其他方法,步骤也类似。
*/
public static IEnumerator DoRotate(this MonoBehaviour mono, tween myTween) {
for (; myTween.currentLoop < myTween.loops; myTween.currentLoop++) {
Vector3 angle = (myTween.target - myTween.transform.rotation.eulerAngles) / myTween.time;
//Debug.Log ("angle = " + angle);
for (float f = myTween.time; f >= 0.0f; f -= Time.deltaTime) {
//just like call update()
Debug.Log ("Rotate");
myTween.transform.Rotate (angle * Time.deltaTime);
yield return null;
while (myTween.isPause == true) {
Debug.Log ("Rotate Pause");
yield return null;
}
}
if (myTween.currentLoop < myTween.loops - 1) {
myTween.ResetRotation ();
}
}
myTween.OnComplete ();
}
public static tween DoRotate(this Transform transform, Vector3 target, float time)
{
MonoBehaviour mono = transform.GetComponents<MonoBehaviour> () [0];
tween myTween = new tween ("DoRotate", transform, target, time);
Coroutine coroutine = mono.StartCoroutine (mono.DoRotate(myTween));
myTween.SetCoroutine (coroutine);
return myTween;
}
public static IEnumerator DoScale(this MonoBehaviour mono, tween myTween) {
for (; myTween.currentLoop < myTween.loops; myTween.currentLoop++) {
Vector3 scale = (myTween.target - myTween.transform.localScale) / myTween.time;
for (float f = myTween.time; f >= 0.0f; f -= Time.deltaTime) {
//just like call update()
Debug.Log ("Scale");
myTween.transform.localScale += scale * Time.deltaTime;
yield return null;
while (myTween.isPause == true) {
Debug.Log ("Scale Pause");
yield return null;
}
}
if (myTween.currentLoop < myTween.loops - 1) {
myTween.ResetScale ();
}
}
myTween.OnComplete ();
}
public static tween DoScale(this Transform transform, Vector3 target, float time)
{
MonoBehaviour mono = transform.GetComponents<MonoBehaviour> () [0];
tween myTween = new tween ("DoScale", transform, target, time);
Coroutine coroutine = mono.StartCoroutine (mono.DoScale(myTween));
myTween.SetCoroutine (coroutine);
return myTween;
}
}
}
4.测试函数:
测试函数主要用来新建动作以及进行DOTween的函数控制。
using UnityEngine;
using System.Collections;
using MyExtensionMethods;
public class dotweenTest : MonoBehaviour {
private tween myTween;
// Use this for initialization
void Start () {
DOTween.Init ();
myTween = transform.DoMove (new Vector3 (15.0f, 0f, 0f), 3.0f).SetLoops(3);
transform.DoRotate (new Vector3(150f, 0f, 0f), 3.0f).SetLoops(1);
transform.DoScale (new Vector3 (3f, 3f, 3f), 3.0f).SetLoops(1);
}
// Update is called once per frame
void Update () {
//Debug.Log ("tween Size = " + DOTween.getTweenSize ());
if (Input.GetKeyDown ("f")) {
Debug.Log ("f down PauseAll");
DOTween.PauseAll ();
}
if (Input.GetKeyDown ("g")) {
Debug.Log ("g down PlayAll");
DOTween.PlayAll ();
}
if (Input.GetKeyDown ("h")) {
Debug.Log ("h down RestartAll");
DOTween.RestartAll ();
}
if (Input.GetKeyDown ("j")) {
Debug.Log ("j down CompleteAll");
DOTween.CompleteAll ();
}
if (Input.GetKeyDown ("a")) {
Debug.Log ("a down Pause DoScale");
DOTween.Pause("DoScale");
}
}
}
实验效果:
将测试代码挂在一个Cube上进行实验,其中位移为循环执行,扩大与旋转是一次执行。