Unity 3D大作业 | 基于协程和扩展方法对DoTween插件进行仿写及实践

本文介绍了如何使用C#协程和扩展方法仿写Unity中的DoTween插件,以实现补间动画效果。通过Tween和DoTween类的管理,以及对Transform的扩展方法,可以方便地控制游戏对象的移动、缩放和旋转。仿写实现允许开发者在不依赖DoTween原生库的情况下,自定义动画行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DoTween插件仿写与实践

DoTween插件

DoTween是一个用于实现插值动画(补间动画)的Unity插件。插值动画(补间动画)就是在时间轴上定义两个不同时间的动画关键帧状态后,自动生成两个时间点之间所有帧动画的动画制作方法。

DoTween官网首页给出了它的基本结构及其作用:
dotween_struct
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实现插值动画的效果。参考官方的架构设计,我们可以将项目简化并分成TweenDoTweenExtension三个部分。UML图如下:
UML

其中补间动画类Tween实现动作属性的储存和管理以及实现回调。在扩展方法中,每次调用Transform的扩展方法,就会产生一个对应的Tween和一个协程,并且Tween在调用构造函数时会自动添加到动作管理类DoTween工厂的列表中管理。

Tween对象对应的是对应动作的协程,通过改变其成员变量中的isPauseautoKill可以决定对应的协程是否暂停、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对象状态,以及决定是否暂停等,并负责在动作结束后调用回调函数。

通过对MonoBehaviourTransform类扩展控制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着色:
CubeMat

运行游戏,画面如下:

DoTween插件仿写与实践

参考资料:
DoTween官方Github开源项目:DoTween官方Github
参考博客:参考CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值