【Unity】 原生Unity写法替换Dotween插件

DOTween 替换为 Unity 原生写法说明

文档创建日期:2026-01-16


1. 修改背景

为减少第三方插件依赖、降低包体大小、提高项目可维护性,将项目中的 DOTween 插件调用全部替换为 Unity 原生协程实现。

核心实现文件:Assets/MyAssets/Scripts/UnityDoTween.cs

核心代码

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace MyAssets.Scripts {
    public class UnityDoTween : MonoBehaviour {
        
        // 存储所有运行中的协程,用于清理
        private static List<Coroutine> activeCoroutines = new List<Coroutine>();
        private static MonoBehaviour coroutineRunner;
        
        // 初始化方法(兼容 DOTween.Init 调用)
        public static void Init(bool recycleAllByDefault = false, bool useSafeMode = true) {
            // Unity 原生实现不需要特殊初始化
            activeCoroutines.Clear();
        }
        
        // 清理方法(兼容 DOTween.Clear 调用)
        public static void Clear(bool destroy = false) {
            // 停止所有记录的协程
            if (coroutineRunner != null) {
                foreach (var coroutine in activeCoroutines) {
                    if (coroutine != null) {
                        coroutineRunner.StopCoroutine(coroutine);
                    }
                }
            }
            activeCoroutines.Clear();
        }
        
        // 获取协程运行器
        private static MonoBehaviour GetCoroutineRunner() {
            if (coroutineRunner == null) {
                // 尝试找到一个 MonoBehaviour 来运行协程
                coroutineRunner = UnityEngine.Object.FindFirstObjectByType<MonoBehaviour>();
            }
            return coroutineRunner;
        }
        
        // 延迟调用方法(兼容 DOVirtual.DelayedCall)
        public static void DelayedCall(float delay, Action callback) {
            var runner = GetCoroutineRunner();
            if (runner != null) {
                var coroutine = runner.StartCoroutine(DODelay(delay, callback));
                activeCoroutines.Add(coroutine);
            }
        }
        // 缓动类型枚举
        public enum EaseType {
            Linear,
            InQuad,
            OutQuad,
            InOutQuad,
            InCubic,
            OutCubic,
            InOutCubic
        }
        
        // 获取缓动值
        private static float GetEaseValue(float t, EaseType easeType) {
            switch (easeType) {
                case EaseType.InQuad:
                    return t * t;
                case EaseType.OutQuad:
                    return t * (2 - t);
                case EaseType.InOutQuad:
                    return t < 0.5f ? 2 * t * t : -1 + (4 - 2 * t) * t;
                case EaseType.InCubic:
                    return t * t * t;
                case EaseType.OutCubic:
                    return (--t) * t * t + 1;
                case EaseType.InOutCubic:
                    return t < 0.5f ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
                case EaseType.Linear:
                default:
                    return t;
            }
        }
        
        // 移动动画
        public static IEnumerator DOMove(Transform target, Vector3 endValue, float duration ,float delay = 0f, bool from  = false,Action onComplete = null, EaseType easeType = EaseType.Linear) {
            if(delay >0)
                yield return new WaitForSeconds(delay);
            Vector3 startValue = target.position;
            float elapsedTime = 0f;
 
            if (from) {
                startValue = endValue;
                endValue = target.position;
            }
            while (elapsedTime < duration) {
                float t = GetEaseValue(elapsedTime / duration, easeType);
                target.position = Vector3.Lerp(startValue, endValue, t);
                elapsedTime += Time.deltaTime;
                yield return null;
            }
 
            target.position = endValue;
            onComplete?.Invoke();
        }
 
        // 本地移动动画
        public static IEnumerator DOLocalMove(Transform target, Vector3 endValue, float duration ,float delay = 0f, bool from = false,Action onComplete = null, EaseType easeType = EaseType.Linear) {
            if(delay >0)
                yield return new WaitForSeconds(delay);
            Vector3 startValue = target.localPosition;
            float elapsedTime = 0f;

            if (from) {
                startValue = endValue;
                endValue = target.localPosition;
            }
        
            while (elapsedTime < duration) {
                float t = GetEaseValue(elapsedTime / duration, easeType);
                target.localPosition = Vector3.Lerp(startValue, endValue, t);
                elapsedTime += Time.deltaTime;
                yield return null;
            }

            target.localPosition = endValue;
            onComplete?.Invoke();
        }
        
        // 本地Y轴移动动画
        public static IEnumerator DOLocalMoveY(Transform target, float endValue, float duration, float delay = 0f, Action onComplete = null, EaseType easeType = EaseType.Linear) {
            if(delay > 0)
                yield return new WaitForSeconds(delay);
            float startValue = target.localPosition.y;
            float elapsedTime = 0f;
        
            while (elapsedTime < duration) {
                float t = GetEaseValue(elapsedTime / duration, easeType);
                float currentY = Mathf.Lerp(startValue, endValue, t);
                target.localPosition = new Vector3(target.localPosition.x, currentY, target.localPosition.z);
                elapsedTime += Time.deltaTime;
                yield return null;
            }

            target.localPosition = new Vector3(target.localPosition.x, endValue, target.localPosition.z);
            onComplete?.Invoke();
        }
 
        // 旋转动画
        public static IEnumerator DORotate(Transform target, Vector3 endValue, float duration) {
            Quaternion startRotation = target.rotation;
            Quaternion endRotation = Quaternion.Euler(endValue);
            float elapsedTime = 0f;
 
            while (elapsedTime < duration) {
                target.rotation = Quaternion.Lerp(startRotation, endRotation, elapsedTime / duration);
                elapsedTime += Time.deltaTime;
                yield return null;
            }
 
            target.rotation = endRotation;
        }
    
        public static IEnumerator DORotate(Transform target, Quaternion endValue, float duration) {
            Quaternion startRotation = target.rotation;
            float elapsedTime = 0f;
 
            while (elapsedTime < duration) {
                target.rotation = Quaternion.Lerp(startRotation, endValue, elapsedTime / duration);
                elapsedTime += Time.deltaTime;
                yield return null;
            }
 
            target.rotation = endValue;
        }
    
        // 本地旋转动画
        public static IEnumerator DOLocalRotate(Transform target, Vector3 endValue, float duration) {
            Quaternion startRotation = target.localRotation;
            Quaternion endRotation = Quaternion.Euler(endValue);
            float elapsedTime = 0f;
 
            while (elapsedTime < duration) {
                target.localRotation = Quaternion.Lerp(startRotation, endRotation, elapsedTime / duration);
                elapsedTime += Time.deltaTime;
                yield return null;
            }
 
            target.localRotation = endRotation;
        }
 
        // 缩放动画
        public static IEnumerator DOScale(Transform target, Vector3 endValue, float duration,float delay = 0f, bool from = false,Action onComplete = null, EaseType easeType = EaseType.Linear) {
            if(delay >0)
                yield return new WaitForSeconds(delay);
            Vector3 startValue = target.localScale;
            float elapsedTime = 0f;

            if (from) {
                startValue = endValue;
                endValue = target.localScale;
            }
            while (elapsedTime < duration) {
                float t = GetEaseValue(elapsedTime / duration, easeType);
                target.localScale = Vector3.Lerp(startValue, endValue, t);
                elapsedTime += Time.deltaTime;
                yield return null;
            }

            target.localScale = endValue;
            onComplete?.Invoke();
        }
 
        // UI透明度动画
        public static IEnumerator DOFade(CanvasGroup canvasGroup, float endValue, float duration, float delay = 0f, bool from = false,Action onComplete = null) {
            if(delay >0)
                yield return new WaitForSeconds(delay);
            float startValue = canvasGroup.alpha;
            float elapsedTime = 0f;
            if (from) {
                startValue = endValue;
                endValue = canvasGroup.alpha;
            }
            while (elapsedTime < duration) {
                canvasGroup.alpha = Mathf.Lerp(startValue, endValue, elapsedTime / duration);
                elapsedTime += Time.deltaTime;
                yield return null;
            }
 
            canvasGroup.alpha = endValue;
        }
 
        // Image颜色动画
        public static IEnumerator DOColor(Image image, Color endValue, float duration) {
            Color startValue = image.color;
            float elapsedTime = 0f;
 
            while (elapsedTime < duration) {
                image.color = Color.Lerp(startValue, endValue, elapsedTime / duration);
                elapsedTime += Time.deltaTime;
                yield return null;
            }
            image.color = endValue;
        }
 
        // 数值动画
        public static IEnumerator DOFloat(System.Action<float> onValueChanged, float startValue, float endValue,
            float duration) {
            float elapsedTime = 0f;
 
            while (elapsedTime < duration) {
                float currentValue = Mathf.Lerp(startValue, endValue, elapsedTime / duration);
                onValueChanged?.Invoke(currentValue);
                elapsedTime += Time.deltaTime;
                yield return null;
            }
 
            onValueChanged?.Invoke(endValue);
        }
 
        // 带缓动函数的移动
        public static IEnumerator DOMoveEase(Transform target, Vector3 endValue, float duration,
            AnimationCurve easeCurve = null) {
            Vector3 startValue = target.position;
            float elapsedTime = 0f;
 
            if (easeCurve == null) {
                easeCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
            }
 
            while (elapsedTime < duration) {
                float t = easeCurve.Evaluate(elapsedTime / duration);
                target.position = Vector3.Lerp(startValue, endValue, t);
                elapsedTime += Time.deltaTime;
                yield return null;
            }
 
            target.position = endValue;
        }
 
        public static IEnumerator DODelay(float delay, Action onComplete = null) {
            yield return new WaitForSeconds(delay);
            onComplete?.Invoke();
        }
    }
}

2. UnityDoTween.cs 功能清单

2.1 静态方法(兼容 DOTween API)

方法 说明 对应 DOTween
Init(bool, bool) 初始化(兼容调用,无实际操作) DOTween.Init()
Clear(bool) 清理所有运行中的动画协程 DOTween.Clear()
DelayedCall(float, Action) 延迟调用回调函数 DOVirtual.DelayedCall()

2.2 动画协程方法

方法 说明 参数
DOMove 世界坐标位置移动 target, endValue, duration, delay, from, onComplete, easeType
DOLocalMove 本地坐标位置移动 target, endValue, duration, delay, from, onComplete, easeType
DOLocalMoveY 本地坐标 Y 轴移动 target, endValue, duration, delay, onComplete, easeType
DOScale 缩放动画 target, endValue, duration, delay, from, onComplete, easeType
DORotate 旋转动画 (Vector3/Quaternion) target, endValue, duration
DOLocalRotate 本地旋转动画 target, endValue, duration
DOFade CanvasGroup 透明度动画 canvasGroup, endValue, duration, delay, from, onComplete
DOColor Image 颜色动画 image, endValue, duration
DOFloat 数值动画 onValueChanged, startValue, endValue, duration
DOMoveEase 带 AnimationCurve 的移动 target, endValue, duration, easeCurve
DODelay 延迟等待 delay, onComplete

2.3 缓动类型枚举 (EaseType)

public enum EaseType {
    Linear,      // 线性
    InQuad,      // 二次方缓入
    OutQuad,     // 二次方缓出
    InOutQuad,   // 二次方缓入缓出
    InCubic,     // 三次方缓入
    OutCubic,    // 三次方缓出
    InOutCubic   // 三次方缓入缓出
}

3. 修改文件清单

3.1 文件修改汇总

文件 修改内容
UnityDoTween.cs 新增 InitClearDelayedCallDOLocalMoveYEaseType 枚举
Player.cs 替换 DOLocalMove + DOLocalMoveY + SetEase
DraggableHole.cs 替换 DOScale + OnComplete
MagnetPowerUp.cs 替换 DOMove + SetEase + OnComplete
GameManager.cs 替换 DOTween.InitDOTween.ClearDOVirtual.DelayedCall
Main.cs 替换 DOTween.InitDOTween.Clear
LevelSpawner.cs 替换 DOVirtual.DelayedCall
Animal animation.cs 替换 DOVirtual.DelayedCall
TutorialHandAnimation.cs 新增脚本,替代 Square 节点的 DOTween Animation 组件

3.2 引用替换

所有文件的 using DG.Tweening 替换为:

using MyAssets.Scripts;

4. 用法对照表

4.1 基础移动动画

DOTween 原写法:

transform.DOMove(targetPos, 0.5f).OnComplete(() => Debug.Log("完成"));

Unity 原生写法:

StartCoroutine(UnityDoTween.DOMove(transform, targetPos, 0.5f, 0f, false, () => Debug.Log("完成")));

4.2 带缓动的移动

DOTween 原写法:

transform.DOMove(targetPos, 0.5f).SetEase(Ease.InQuad).OnComplete(callback);

Unity 原生写法:

StartCoroutine(UnityDoTween.DOMove(transform, targetPos, 0.5f, 0f, false, callback, UnityDoTween.EaseType.InQuad));

4.3 本地 Y 轴移动

DOTween 原写法:

transform.DOLocalMoveY(targetY, 0.5f).SetEase(Ease.InQuad);

Unity 原生写法:

StartCoroutine(UnityDoTween.DOLocalMoveY(transform, targetY, 0.5f, 0f, null, UnityDoTween.EaseType.InQuad));

4.4 缩放动画

DOTween 原写法:

transform.DOScale(Vector3.zero, 0.3f).OnComplete(() => Destroy(gameObject));

Unity 原生写法:

StartCoroutine(UnityDoTween.DOScale(transform, Vector3.zero, 0.3f, 0f, false, () => Destroy(gameObject)));

4.5 延迟调用

DOTween 原写法:

DOVirtual.DelayedCall(1.5f, () => DoSomething());

Unity 原生写法:

UnityDoTween.DelayedCall(1.5f, () => DoSomething());

4.6 初始化与清理

DOTween 原写法:

DOTween.Init(false, true);
DOTween.Clear(true);

Unity 原生写法:

UnityDoTween.Init(false, true);
UnityDoTween.Clear(true);

5. 场景组件替换

5.1 Square 节点 (引导手指动画)

原配置 (DOTween Animation 组件):

  • 动画类型:Move
  • 起始位置:(4.25, 1.5, 3)
  • 目标位置:(2.5, 1.5, 3)
  • 持续时间:1秒
  • 循环类型:Yoyo (来回)
  • 循环次数:无限

替换方案:

  1. 移除 Square 节点上的 DOTween Animation 组件
  2. 添加 TutorialHandAnimation 脚本组件
  3. 参数默认值已与原配置一致,无需额外调整

TutorialHandAnimation 特性:

  • OnEnable() 时自动播放动画
  • OnDisable() 时自动停止并重置位置
  • GameManager.tutorialHand1.SetActive() 完美配合

6. 注意事项

6.1 协程生命周期

  • 动画协程依赖于 MonoBehaviour,对象销毁时协程自动停止
  • 如需手动停止,需保存协程引用并调用 StopCoroutine()

6.2 嵌套动画回调

  • onComplete 回调中启动新协程时,确保对象仍然有效
  • 示例(Player.cs 的下落动画):
StartCoroutine(UnityDoTween.DOLocalMove(transform, Vector3.zero, hopDuration, 0f, false, () => {
    StartCoroutine(UnityDoTween.DOLocalMoveY(transform, targetY, fallDuration, 0f, () => {
        Destroy(gameObject);
    }, UnityDoTween.EaseType.InQuad));
}));

6.3 性能考虑

  • 协程实现与 DOTween 性能相当,适用于常规动画需求
  • 大量同时运行的动画建议使用对象池管理

7. 后续可删除的文件/目录

完成替换后,可考虑删除以下 DOTween 相关文件(建议先备份):

Assets/Plugins/Demigiant/DOTween/
Assets/Plugins/Demigiant/DOTweenPro/ (如有)

⚠️ 删除前请确保项目中不再有任何 using DG.Tweening 引用


8. 验证清单


文档维护:AI 协作自动生成

posted @ 2026-01-16 13:43  星空探险家  阅读(1)  评论(0)    收藏  举报