7.Unity进阶Timeline总结

7.Timeline系列总结

7.1 总结

系列回顾

这个系列 6 篇,前四篇讲 Timeline 本身,后两篇讲怎么拿它做东西、以及什么时候该绕过它自己做。

基础和轨道

Timeline 的基础用法不算复杂。TimelineAsset 是剧本,PlayableDirector 是导演,运行时 Director 把剧本编译成一张 PlayableGraph 交给 Playable 系统去跑——所以之前花那么多篇幅讲 Playable 的建图和求值,到 Timeline 这里就全对上了。

轨道那篇把常用的几种 Track 都过了一遍:

  • Activation Track:控显隐
  • Animation Track:播动画和录关键帧
  • Audio Track:放音效
  • Control Track:管粒子和嵌套子 Timeline
  • Signal Track:打时间点事件

每种轨道的 Inspector 参数都过了一遍,重点说了 Animation Clip 的 Extrapolation(Pre/Post)和 Blend Curves——过场里调时间对齐经常要碰这些。

自定义轨道的四件套

写 Timeline 自定义轨道绕不开这四个类:

  • TrackAsset:轨道本身,声明绑定类型、支持哪些 Clip,建图时创建 Mixer
  • PlayableAsset(ClipAsset):Clip 的数据描述,CreatePlayable() 创建运行时节点
  • PlayableBehaviour:Clip 对应的运行时逻辑,ProcessFrame() 里做实际的事
  • TrackMixerBehaviour:轨道的 Mixer 逻辑,处理多 Clip 重叠时的权重混合

Clip 之间不需要混合的情况(很多游戏逻辑轨道都是这样),Mixer 可以简化成”遍历输入、找权重最大的那个执行”。但四个类的壳子还是得写,这也是自研编辑器想绕过的一个原因——节点类型一多,每种都要写四个文件就很繁琐。

PlayableDirector

PlayableDirector 是运行时操控 Timeline 的入口,常用 API:

  • Play() / Pause() / Stop():基础播控
  • Evaluate():Manual 模式下的手动刷新,也可以用来跳帧预览
  • SetGenericBinding() / GetGenericBinding():动态改轨道绑定——同一份 Timeline 换不同角色就靠这个

WrapMode 的三种模式(Hold / Loop / None)要注意,None 模式播完后会把 Timeline 修改过的属性恢复成播前状态,第一次碰到容易懵。

建图源码

源码那篇拆了 Timeline 建图的内部流程。TimelineAsset.CreatePlayable() 是入口,内部创建 TimelinePlayable 作为根节点,然后遍历所有轨道,每条轨道建自己的 Mixer,把 Clip 对应的 Playable 接到 Mixer 下面。

最关键的数据结构是 IntervalTree——把所有 clip 按时间区间存进去,每帧 PrepareFrame() 按当前时间查哪些 clip 活跃,再算权重写到对应输入端口上。”资产 → 建图 → 每帧查区间写权重”,这就是 Timeline 运行时干的全部事情。

过场编辑器

过场编辑器在 Timeline 之上搭了一层:CutsceneRoot 做总控、CutsceneElement 抽象参与者(相机、角色、特效、路径……)、自定义轨道处理对话/字幕/幕帘等游戏逻辑、SubCutscene 把长过场拆成可独立编辑的片段。

核心思路是”一个过场 = 一个 Prefab”,运行时加载 → 实例化 → 预加载资源 → Director.Play() → 播完销毁,干干净净。

自研表现编辑器

最后一篇讲了为什么技能表现没有用 Timeline,而是自研了一套轻量编辑器。技能不需要 Clip 混合、节点类型多但逻辑简单、配置要轻量可热更——这些特点让 PlayableGraph 那套显得太重了。

自研方案分三层:

  • 编辑器层:UIToolkit + IMGUI
  • 数据层:Lua table / JSON 纯文本
  • 运行时ActionSequencePlayerActionSequenceActionNode

一个 Update 循环按时间驱动节点就够了。两套方案不互斥,过场用 Timeline,技能用自研,各管各的。

Timeline 做过场 vs 自研做技能

选哪个取决于需求特点。

过场演出离不开 Timeline,核心原因就是混合。两段动画之间的 MixIn / MixOut、Cinemachine 镜头之间的 Blend、多轨道的时间对齐——这些都依赖 PlayableGraph 的权重求值体系,自己从头写一套不现实。再加上 Timeline 窗口本身是个成熟的可视化编辑器,策划和美术拖 Clip 就能出效果。

技能表现的情况不一样——需求就是”第几毫秒触发什么”,节点之间纯并行,不存在混合。配置用纯文本(Lua table / JSON),人眼可读、手改方便、热更友好。新增节点类型继承基类重写几个方法就行,不用写四件套。一个 Update 循环够了,不需要建 Graph。所以技能这边用自研更合适。

两套在同一个项目里共存没问题。自研编辑器里加一种 PlayCutsceneNode,就能在表现序列里触发 Timeline 过场,两边打通。

Timeline 过场编辑器 自研表现编辑器
底层 PlayableGraph 求值框架 自己的 Update 循环
Clip 混合 有(MixIn / MixOut / 权重) 无,纯并行
数据格式 .playable(ScriptableObject 序列化) Lua table / JSON(纯文本)
新增节点成本 TrackAsset 四件套 继承基类重写几个方法
适用场景 过场演出、CG 技能表现、交互动作、通用表现序列

容易踩的坑

WrapMode.None 播完会恢复属性。 Director 的 Wrap Mode 设成 None,Timeline 播完后被修改过的属性会自动恢复成播前的值。第一次碰到会以为是 Bug——“我的角色播完过场怎么又跳回原位了”。想让角色停在最后一帧用 Hold,想循环用 Loop。

自定义轨道的 ExposedReference 需要 Director 才能 Resolve。 ExposedReference<T>Resolve() 需要传 IExposedPropertyTable,运行时就是 PlayableDirector。在没有 Director 上下文的地方调 Resolve 拿到的是 null,排查的时候容易漏掉这个前提。

Stop() 会销毁 Graph。 Director.Stop() 之后 PlayableGraph 会被销毁,再访问 director.playableGraph 会拿到无效句柄。需要”停在当前帧但保留 Graph”的话,应该用 Pause() 而不是 Stop()

ControlTrack 的 Prefab 实例化时机。 ControlTrack 配了 Prefab 后,实例化发生在 Timeline 开始播放的时候,不是 Clip 开始的时候。Prefab 比较重的话可能导致播放开头卡一下。过场编辑器里的做法是全部预加载——不管角色是第 1 秒出场还是第 50 秒才出场,启动前就全部加载好,播放过程中就不会有异步卡顿了。

Binding 和场景对象的生命周期不同步。 Timeline 资源是 Asset,Binding 里引用的是场景对象。Prefab 模式下编辑完保存、换场景再打开,Binding 可能丢失。运行时动态创建角色的项目,一般会在 Director.Play() 之前用 SetGenericBinding() 把所有轨道重新绑一遍。



转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏