1. Tween System
Tween(补间) 是指在两个状态之间,通过时间插值自动生成中间帧(in-between),从而产生平滑过渡的动画或变化。
因为在游戏中,我们很多时候需要进行插值来表示过渡动画。比如物体从一个点移动到目的地,只需要设定初始值和目的地值,还有所用的时间,那么中间的过程也就确定了,只需要通过计算去获取每个时刻的值。
最简单的方式可以通过 Lerp 函数来进行插值,在归一化时间的表示下(0表示开始,1表示结束),每个中间时刻的值都可以通过 Lerp 插值出来。
最简易的写法肯定是每个过程都自己编写,在 Update 中计算中间过程,比如:
public bool Update(float deltaTime)
{
if (_isCompleted) return true;
_elapsed += deltaTime;
float t = Mathf.Clamp01(_elapsed / _duration);
float value = Mathf.Lerp(_startValue, _endValue, easedT);
_onUpdate?.Invoke(value);
_isCompleted = _elapsed >= _duration;
return _isCompleted;
}
1.1 Tween Manager
不过这种写法肯定不是一个好的写法,更好的方式是使用“协程”来进行处理,同时我们把整个类提取出来作为一个公共单例,这样插值和调用过程就简化了。只需要通过回调参数 onUpdate 和 onComplete 来决定中间态和结束时的回调。
public class TweenManager : MonoBehaviour
{
private static TweenManager _instance;
public static TweenManager Instance
{
get
{
if (_instance == null)
{
var go = new GameObject("TweenManager");
_instance = go.AddComponent<TweenManager>();
DontDestroyOnLoad(go);
}
return _instance;
}
}
public Coroutine TweenFloat(float from, float to, float duration, Action<float> onUpdate,
Action onComplete = null)
{
return StartCoroutine(TweenFloatCoroutine(from, to, duration, onUpdate, onComplete));
}
private IEnumerator TweenFloatCoroutine(float from, float to, float duration, Action<float> onUpdate, Action onComplete)
{
float time = 0f;
while (time < duration)
{
time += Time.deltaTime;
float t = Mathf.Clamp01(time / duration);
float value = Mathf.Lerp(from, to, t);
onUpdate?.Invoke(value);
yield return null;
}
onUpdate?.Invoke(to);
onComplete?.Invoke();
}
public Coroutine TweenColor(Color from, Color to, float duration, Action<Color> onUpdate,
Action onComplete = null)
{
return StartCoroutine(TweenColorCoroutine(from, to, duration, onUpdate, onComplete));
}
private IEnumerator TweenColorCoroutine(Color from, Color to, float duration, Action<Color> onUpdate, Action onComplete)
{
float time = 0f;
while (time < duration)
{
time += Time.deltaTime;
float t = Mathf.Clamp01(time / duration);
Color value = Color.Lerp(from, to, t);
onUpdate?.Invoke(value);
yield return null;
}
onUpdate?.Invoke(to);
onComplete?.Invoke();
}
}
1.2 插值颜色和 Float
在写好了 TweenManager 以后,调用的时候只需要编写回调参数即可。
比如对颜色进行插值:
public class ColorFadeExample : MonoBehaviour
{
public Image target;
private void OnGUI()
{
if (GUI.Button(new Rect(10, 10, 100, 30), "TweenColor"))
{
Color start = Color.white;
Color end = Color.red;
float duration = 2f;
TweenManager.Instance.TweenColor(start, end, duration, color => target.color = color, ()=> Debug.Log("Color Tween Done!"));
}
}
}

对浮点数进行插值,实现一个计时器:
public class TextExample : MonoBehaviour
{
public Text target;
private void OnGUI()
{
if (GUI.Button(new Rect(10, 50, 100, 30), "TweenFloat"))
{
float start = 0f;
float end = 10f;
float duration = 5f;
TweenManager.Instance.TweenFloat(start, end, duration, value => target.text = $"Time: {value:F4}", ()=> Debug.Log("Float Tween Done!"));
}
}
}

2.3 利用 C# 扩展方法简化写法
扩展方法(Extension Method)是 C# 提供的一种语法糖,它允许你给现有类型添加方法,而无需修改原来的类。
也就是说,你可以“扩展”别人的类,包括 Unity 的 Image/string/List<T> 等。
语法特点:
- 方法必须是
static。 - 第一个参数要用
this修饰,表示这个方法扩展的是哪个类型。 - 必须放在
static class中。
public static class TweenManagerExtension
{
public static Coroutine TweenColor(this Image image, Color from, Color to, float duration, Action<Color> onUpdate, Action onComplete = null)
{
return TweenManager.Instance.TweenColor(from, to, duration, onUpdate, onComplete);
}
}
使用的时候就可以直接当作一个方法来使用:
public class ColorFadeExample : MonoBehaviour
{
public Image target;
private void OnGUI()
{
if (GUI.Button(new Rect(10, 10, 100, 30), "TweenColor"))
{
Color start = Color.white;
Color end = Color.aquamarine;
float duration = 2f;
target.TweenColor(start, end, duration, color => target.color = color, () => Debug.Log("Color Tween Done!!"));
}
}
}
这样就简化的调用方式,使得代码写起来更优雅了。

2. DOTween 框架
在前一小节的最后一步,通过 C# 扩展方法,已经可以实现便捷的补间操作了。
但是一个问题在于,实际上还有很多游戏开发中的场景需要进行适配,比如:
- 循环多次地播放一段动画
- 逆向播放一段动画
- 将多个动画连起来播放
- 不仅仅是浮点数和颜色,还有文字等各种场景
一个好消息是,已经有人这么做了,并且性能上也进行了优化,它就是 DOTween 。
DOTween 它的全名是 Demigiant DOTween,作者是 Daniele Giardini,是目前 Unity 最主流的 Tween System 之一。
参考文档:DOTween Documentation1
2.1 DOTween 安装
安装它非常容易,就按照正常的插件安装方式,在 Unity Asset Store 中找到它并安装即可:Unity Asset Store DOTWeen

2.2 振动、文字、移动缩放
DOTween 不仅仅是简单的插值,也包括很多特效动画。
比如,可以振动位置来实现抖动镜头的效果:
private void OnGUI()
{
if (GUI.Button(new Rect(10, 10, 100, 30), "Shake"))
{
Camera.main.transform.DOShakePosition(2, shakeStrength);
}
}

可以逐个输出一段文字:
private void OnGUI()
{
if (GUI.Button(new Rect(10, 50, 100, 30), "TextPrint"))
{
outputTextCanvas.gameObject.SetActive(true);
outputText.text = "";
outputText.DOText(" Hello world \n !!!!!!!!!!!!!!!!!!!!!!!!!!", 4);
}
}

能够将物体从当前位置移动到另一个地方。
private void OnGUI()
{
if (GUI.Button(new Rect(10, 130, 100, 30), "MoveTo"))
{
targetTransform.DOMove(new Vector3(0, 2, 0), 1f);
}
}

2.3 Sequence
移动的同时进行缩放。
这里的 Sequence 将会等待前一个动画运行完毕再开始下一个动画。
private void OnGUI()
{
if (GUI.Button(new Rect(10, 170, 100, 30), "MoveToAndScale"))
{
var s = DOTween.Sequence();
s.Append(targetTransform.DOMove(new Vector3(0, 2, 0), 1f));
s.Append(targetTransform.DOScale(new Vector3(0.5f, 0.5f, 0.5f), 1f));
s.OnComplete((() => Debug.Log("MoveToAndScale Done!")));
}
}

2.4 相对值、OnComplete
不仅仅可以移动绝对值,还可以移动相对值,也就是相对于动画启动的位置:
if (GUI.Button(new Rect(10, 130, 100, 30), "MoveTo"))
{
targetTransform.DOMove(new Vector3(0, 2, 0), 1f).SetRelative();
}

通过组合序列,可以实现连贯的动画效果,并在结束的时候执行隐藏文本的效果:
if (GUI.Button(new Rect(10, 90, 100, 30), "TextColor"))
{
outputTextCanvas.gameObject.SetActive(true);
outputText.text = "";
var s = DOTween.Sequence();
// 文字打字机动画
s.Append(outputText
.DOText("Hello world \n !!!!!!!!!!!!!!!!!!!!!!!!!!", 4)
.SetEase(Ease.Linear));
// 颜色变化
s.Append(outputText
.DOColor(new Color(0.8f, 0.2f, 0.3f), 1f)
.SetEase(Ease.InOutSine));
// 闪烁:反复改变透明度
s.AppendCallback(() =>
{
outputText.DOFade(0f, 0.3f)
.SetLoops(6, LoopType.Yoyo) // 闪烁3次 Yoyo是反复模式
.SetEase(Ease.InOutSine).OnComplete(() =>
{
outputTextCanvas.gameObject.SetActive(false);
});
});
}

2.5 取消、暂停、恢复
直接调用 Kill(),这会让补间立即停止,不再更新。
Tween tween = transform.DOMove(new Vector3(5, 0, 0), 2f);
tween.Kill(); // 立即取消并移除补间
你可以用目标对象作为参数,批量取消:
transform.DOMove(...);
transform.DOScale(...);
DOTween.Kill(transform); // 杀掉与该 transform 相关的所有 Tween
常用于物体被销毁前:
void OnDestroy()
{
DOTween.Kill(transform);
}
同时还可以临时暂停动画与恢复动画:
Tween t = transform.DOMove(...);
t.Pause(); // 暂停
t.Play(); // 继续
DOTween.PauseAll();
DOTween.PlayAll();
2.6 曲线、延迟、循环
补间曲线(Easing)控制动画随时间变化的速度,默认情况下动画是线性的(Linear),即速度恒定。
但通过设置不同的缓动曲线(Ease),可以获得更自然的运动效果。
transform.DOMoveY(3f, 1.5f)
.SetEase(Ease.OutBack); // 弹性移动效果
有时我们希望动画稍后再播放,这时候通过 SetDelay 设置延迟播放时间:
image.DOFade(1f, 1f)
.SetDelay(0.5f); // 动画在0.5秒后开始
循环定义了动画重复播放的次数与模式,通过 SetLoops(int loops, LoopType type) 实现。
- loops:循环次数。-1 表示无限循环。
- type:循环方式。
Restart是循环,Yoyo是从起点到终点,再返回。Incremental在上一次的基础上增加。
transform.DOScale(1.5f, 0.8f)
.SetEase(Ease.InOutSine)
.SetLoops(3, LoopType.Yoyo);
demigiant, DOTween Documentation, https://dotween.demigiant.com/documentation.php ↩︎
