【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 |
新增 Init、Clear、DelayedCall、DOLocalMoveY、EaseType 枚举 |
Player.cs |
替换 DOLocalMove + DOLocalMoveY + SetEase |
DraggableHole.cs |
替换 DOScale + OnComplete |
MagnetPowerUp.cs |
替换 DOMove + SetEase + OnComplete |
GameManager.cs |
替换 DOTween.Init、DOTween.Clear、DOVirtual.DelayedCall |
Main.cs |
替换 DOTween.Init、DOTween.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 (来回)
- 循环次数:无限
替换方案:
- 移除 Square 节点上的
DOTween Animation组件 - 添加
TutorialHandAnimation脚本组件 - 参数默认值已与原配置一致,无需额外调整
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 协作自动生成
本文来自博客园,作者:星空探险家,转载请注明原文链接:https://www.cnblogs.com/PuppetLazy/p/19172054

浙公网安备 33010602011771号