在游戏中,我们可能需要这样的效果,将一个物体放大 2 倍,在 2s 之内完成;或者在点击 UI 时将 UI 逐渐消失。这样需要多作用效果进行逐步展示的效果被称之为补间。
为了引出我们今天的主角 DOTween
, 我们先来看看用 unity 的 animation 系统制作一个帧动画序列。
# 普通的动画
我们需要以下几个步骤:
- 创建并录制动画
- 在 Animator 中设置
- 绑定按钮事件
代码部分:
private static readonly int Play = Animator.StringToHash("Play"); | |
public void StartAni() | |
{ | |
ui.GetComponent<Animator>().SetTrigger(Play); | |
print("播放动画"); | |
} |
我们需要既需要创建资源,有需要绑定设置,操作过程略微麻烦。
# DoTween 动画
在 DoTween 中我们仅仅需要绑定好按钮事件即可,不要创建动画资源,设置动画播发器等麻烦事。
这在轻度使用补间动画里是非常不错的。
public void StartAni() | |
{ | |
var rec = GetComponent<RectTransform>(); | |
rec.DOLocalMoveX(0, 2); | |
} |
RectTransform 组件,这是 ui 所特有的描述 transform 的组件,第二行代码我们使用扩展方法
让我们来分析一下这 2 行代码,第一行 我们找到 UI 的 RectTransform 组件,这是 ui 所特有的描述 transform 的组件,第二行代码我们使用扩展方法 DOLocalMoveX 对组件位置进行补间,目的地位置为 0(此时也就是锚点的位置,我这里以中心作为锚点),第二个参数是完成动画的时间我选择 2。也就是说在 2s 内让这个 UI 从飞到他 x 轴的锚点位置。
# 通用方法
上面的扩展方法我们也可以使用如下的通用方法来替代。
# From 动画
不论是我们使用扩展方法创建的动画还是通用方法创建的动画,我们都可以添加 .Form()
来设定为逆动画。
var rec = GetComponent<RectTransform>(); | |
rec.DOLocalMoveX(0, 2).From(); |
# 知识速记(扩展方法)
扩展方法使您能够向现有类型 “添加” 方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型
如果之前的代码较多,在考虑重构时,扩展方法可比接口好用哦。
# 小案例 - 打字机效果
public Text info; | |
[ResizableTextArea]public string infoData; | |
info.DOText(infoData, 3); |
# DoTween 的约定
在详细讲解 DoTween 之前,我们先来了解一下 dotween 的设计原理和约定。
0. dotween 使用 tween(补间)来描述一段动画或缓动:rec.DOLocalMoveX(0, 2).From()
就是一个 tween。
使用 Sequence(序列)来表述一组 tween,在下面介绍 Sequence。
- 和我们平时使用 unity 一样,动态变化的部分防止在 update 或 fiexedUpdate 中,dotween 也是这样的,在游戏启动时他会将所有的 tween 的放置在一个容器中(使用了
对象池
)。 - 不论是 tween 或者是 Sequence 默认都将在执行完成后自动杀死。
- tween 默认使用 timescale(也就是说我们常用的 timescale=0 暂停游戏也将暂停 tween)
# On 事件监听
我们可以对一个序列或者一个补间注册事件监听状态。
以下是一些事件:
名称 | 描述 |
---|---|
onComplete | 注册完成委托 |
onKill | 注册杀死委托 |
onPlaye | 注册开始委托 |
onPause | 注册暂停委托 |
onUpdate | 注册更新委托 |
onRewind | 注册倒带委托 |
OnStepComplete | 每次循环完成时 |
每个委托都有对应的方法类型可使用。
transform.DOLocalMoveX(0, 2).onKill += () => { print("hi"); }; | |
transform.DOLocalMoveX(0, 2).OnStart(() => print("开始了")); |
# Set 设定
我们可以对一个序列或者一个补间设定细节。
# setLoop 设定循环
- 参数一: 循环次数(设定为 - 1 为无限循环)
- 参数二: 循环类型
transform.DOLocalMoveX(0, 2).SetLoops(-1, LoopType.Incremental); |
循环类型 | 说明 |
---|---|
Incremental | 递增,每次播放结束 star 和 end 都会加入到差异值中 |
Restart | 重来,播放结束时从起点重新播放 |
Yoyo | 摇摆播放 |
# SetEase 设定曲线
# 其他常用方法
- setLink(Gameobject) :
将 tween 链接到游戏物体,在物体被销毁时自动销毁对象(在序列中无效) - setId(object):
设定 tween 的 id,用于找到 tween,使用数字时效率最高,字符串次之 - SetAutoKill(bool):
设定 tween 执行完是否自动杀死,复用 twenn 设为 false - SetDelay(float):
设定开始播放前的延迟 - SetUpdate(UpdateType):
设定更新模式 - SetAs(tween):
使用其他 tween 的设定模板
# Sequence 序列
将很多 tween 放在一起称为一个序列 Sequence。
var mySeq = DOTween.Sequence(); | |
mySeq.Append(rec.DOLocalMoveX(0, 2)); | |
mySeq.Append(info.DOText(infoData, 3)); | |
// 在所有补间执行完时回调 | |
mySeq.AppendCallback(() => { print("动画播放完成!"); }); | |
// 插入 | |
/* 在给定的时间位置插入给定的补间,从而使您可以重叠补间,而不仅仅是将它们一个接一个地放置。*/ | |
mySeq.Insert(0.5f, rec.DOScaleX(2f, 2f)); | |
// 给定时间插入回调 | |
mySeq.InsertCallback(2f, () => print("移动动画结束了")); | |
// 在序列开头插入补间 | |
mySeq.Prepend(info.DOText("在开头插入的补间。。。", 3)); | |
mySeq.PrependCallback((() => print("开头插入开始/动画开始播放"))); | |
// 序列开始延迟 | |
mySeq.PrependInterval(2); | |
// 暂停序列 | |
// mySeq.Pause(); | |
// 可以写在一起 | |
mySeq.Append(rec.DOLocalMoveX(0, 2)) | |
.Append(info.DOText(infoData, 3)) | |
.AppendCallback(() => { print("动画播放完成!"); }); |
# DoTween 和协程
看个例子:协程在完成动画之后执行
IEnumerator Wait() | |
{ | |
var tt = transform.DOMoveX(2, 2f); | |
yield return tt.WaitForCompletion(); | |
print("动画完成"); | |
} |
方法 | 说明 |
---|---|
WaitForElapsedLoops(int) | 在循环指定次数或 tween 被杀死后 |
WaitForKill | 在 tween 被杀死后 |
WaitForPosition | 到达给定位置或被杀死 |
WaitForRewind | 被杀死或重新播放 |
WaitForStart | 开始播放 |
# 全局设定
我们可以使用 DoTween
类的静态字段来设置对应的默认行为。
如设定是否自动播放,是否自动销毁,对象池大小,默认曲线方式,所有的 tween 和 Sequence 序列在没有设定参数时默认取全局设定的值。
在 Pro 版本我们也可以在可视化面板中设置这些参数。
在运行游戏后我们可以发现场景中多了一个 twenn 的对象,上面可以在游戏中实时查看 tween 的信息。
# 复用动画
2021 年 12 月 9 日 18:37:03 更新
// 创建一个序列 | |
private Sequence seq; | |
// 设置序列 | |
private void Awake() | |
{ | |
seq = DOTween.Sequence(); | |
seq.Append(titleTxt.rectTransform.DOLocalMoveX(-1500, 1f).From()); | |
seq.Append(titleTxt.DOFade(0, 0.5f).SetDelay(0.5f)); | |
seq.SetLink(titleTxt.gameObject).AppendCallback(() => { endBtn.gameObject.SetActive(true); }) | |
.PrependCallback( | |
() => { endBtn.gameObject.SetActive(false); }).SetAutoKill(false); | |
} | |
// 第二次启用时需要这个 | |
seq.Restart(); |