7.Unity进阶Timeline总结

  1. 7.Timeline系列总结
    1. 7.1 知识点
      1. 系列回顾
        1. 基础和轨道
        2. 自定义轨道的四件套
        3. PlayableDirector
        4. 建图源码
        5. 过场编辑器
        6. 自研表现编辑器
      2. Timeline 做过场 vs 自研做技能
      3. 容易踩的坑
    2. 7.2 核心要点速览
      1. Timeline 核心概念
      2. Timeline 窗口结构
      3. 轨道类型一览
      4. Animation Track 关键参数
      5. Signal 工作流
      6. PlayableDirector 常用 API
      7. 运行时绑定:SetGenericBinding
      8. Timeline 建图源码要点
      9. 自定义轨道四件套
      10. 过场编辑器架构
      11. 自研表现序列系统
      12. 两套方案对比
      13. 常见坑点
    3. 7.3 面试题精选
      1. 基础题
        1. 1. Timeline 中 TimelineAsset 和 PlayableDirector 的关系是什么?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. PlayableDirector 的 WrapMode 有哪几种?None 模式有什么特殊行为?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. Signal Track 的工作流程是怎样的?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
      2. 进阶题
        1. 1. Play()、Pause()、Stop() 三个方法的区别是什么?Stop() 有什么需要注意的?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. 如何在运行时动态绑定 Timeline 轨道?遍历 outputs 有什么优势?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. Timeline 运行时如何高效判断哪些 Clip 处于激活状态?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
      3. 深度题
        1. 1. Timeline 和自研表现序列系统分别适合什么场景?为什么技能表现不适合用 Timeline?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. 自定义 Timeline 轨道需要写哪些类?如果不需要 Clip 混合,Mixer 可以怎么简化?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章

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() 把所有轨道重新绑一遍。


7.2 核心要点速览

Timeline 核心概念

Timeline 是 Unity 的时间轴多轨道编排工具,本质是 Playable 系统的上层封装。

概念 角色 职责
TimelineAsset 剧本 静态资产,存储轨道、片段、绑定需求,本身不执行
PlayableDirector 导演 运行时入口,读剧本、建图、控制播放
PlayableGraph 舞台 运行时图,由 Director 根据 Asset 编译生成
Playable 图中节点,每帧执行实际逻辑

执行链路:Timeline 窗口编辑 → 保存为 .playable → Director 引用并设 Binding → Play() 时实例化 Asset、建 Graph、每帧评估。

Timeline 窗口结构

窗口分五块:播放/预览区、当前 Timeline 标题栏、窗口选项、轨道列表、时间轴内容区。

常用操作

  • 添加轨道:左上角 + 下拉选择
  • 放片段:拖 Clip 进时间轴,或轨道右键添加
  • 调时间:拖播放头、缩放标尺、放大时间轴精细对齐

轨道类型一览

轨道 绑定对象 用途 特点
Activation Track GameObject 控制激活/禁用 Clip 覆盖区间内激活,区间外禁用
Animation Track Animator 播动画、录关键帧 支持 Extrapolation、Blend Curves
Audio Track AudioSource 播音频 支持淡入淡出、音量调节
Control Track GameObject 控粒子、嵌套子 Timeline 可配 Prefab 实例化时机
Signal Track SignalReceiver 时间点事件 通过 SignalAsset → UnityEvent 触发回调
Track Group - 纯分组容器 不参与播放,仅整理轨道

Animation Track 关键参数

Clip 级别

参数 作用 常用场景
Clip In 从源动画第几秒开始截取 跳过动画开头
Speed Multiplier 播放速度倍率 加速/减速动画
Pre/Post Extrapolate 片段外如何补姿势 Post=Hold 让角色停在最后一帧
Ease In/Out 淡入淡出时间 片段重叠混合时调柔和度
Blend Curves 权重曲线 手动控制混合过渡形状

Track 级别

参数 作用
Track Offsets 给整条轨道加位置/旋转偏移
Apply Avatar Mask 只影响指定骨骼(上半身/下半身分层)
Apply Foot IK 轨道级别脚 IK 开关

Signal 工作流

Signal 是 Timeline 里做”时间点事件”的标准方案,链路如下:

SignalAsset(消息类型)→ Signal Emitter(时间点发信)→ SignalReceiver(收信)→ Reaction(UnityEvent 回调)

实现步骤

  1. Project 右键 Create → Signal 创建 SignalAsset
  2. Signal Track 上放置 Signal Emitter,Inspector 选要发的 SignalAsset
  3. 轨道绑定对象上挂 SignalReceiver 组件
  4. SignalReceiver 里配置 SignalAsset → UnityEvent 映射

运行时换绑注意:Reaction 存在 SignalReceiver 组件上,换绑定对象后新对象上也要配同一条 Reaction,否则收到信号不知道调哪个函数。

PlayableDirector 常用 API

播控方法

方法 作用 注意
Play() 开始播放 必要时构建 Graph
Pause() 暂停 Graph 保留,时间停在当前帧
Stop() 停止 会销毁 Graph,下次 Play 重建
Evaluate() 立即评估一帧 Manual 模式必用,跳时间后刷新用

关键属性

属性 作用
time 当前时间(秒),可读写
duration 总时长
state 播放状态(Playing / Paused / Stopped)
playableGraph 当前 Graph 句柄,Stop 后无效

Wrap Mode

模式 播完行为
Hold 停在最后一帧
Loop 从头循环
None 播完结束,属性恢复播前状态

运行时绑定:SetGenericBinding

Timeline 资产是静态的,运行时要动态绑定场景对象。

遍历 outputs 绑定

foreach (PlayableBinding binding in director.playableAsset.outputs)
{
    // binding.sourceObject 是轨道对象(当 key 用)
    // binding.outputTargetType 是期望的绑定类型
    if (binding.outputTargetType == typeof(Animator))
    {
        director.SetGenericBinding(binding.sourceObject, myAnimator);
    }
}

优势:按对象引用绑定,不受轨道改名、顺序调整影响。

Timeline 建图源码要点

入口TimelineAsset.CreatePlayable()TimelinePlayable.Create()

核心流程

  1. 创建 TimelinePlayable 作为根节点
  2. 遍历 GetOutputTracks() 取所有输出轨道
  3. 每条轨道调 CreateTrackMixer() 建 Mixer 节点
  4. 轨道内部用 IntervalTree 存所有 Clip 的时间区间
  5. 每帧 PrepareFrame() 按当前时间查区间,算权重写到输入端口

IntervalTree 的作用:把”每帧遍历所有 Clip 判断是否活跃”从 O(n) 降到区间查询,Clip 数量多时性能提升明显。

自定义轨道四件套

写自定义轨道需要四个类:

职责 关键方法
TrackAsset 轨道定义 CreateTrackMixer() 创建 Mixer
PlayableAsset Clip 数据描述 CreatePlayable() 创建运行时节点
PlayableBehaviour Clip 运行时逻辑 ProcessFrame() 每帧执行
TrackMixerBehaviour 轨道混合逻辑 处理多 Clip 重叠时的权重混合

不需要混合时:Mixer 可简化为遍历输入找权重最大的执行,但四个类的壳子仍需写。

过场编辑器架构

核心思路:一个过场 = 一个 Prefab。

CutsceneRoot 职责

  • 持有 masterDirector(主 PlayableDirector)
  • 收集所有 CutsceneElement(相机、角色、特效等参与者)
  • 管理 SubCutsceneGroup(子过场拆分)

播放生命周期

加载 Prefab → 实例化 → StartLoading() 预加载资源 → Play() 播放 → stopped 事件 → 清理销毁

预加载策略:所有角色不管第几秒出场,启动前全部加载完毕,避免播放中途异步卡顿。

自研表现序列系统

技能表现场景不适合用 Timeline,原因:

  • 不需要 Clip 混合
  • 节点类型多但逻辑简单
  • 配置要轻量可热更
  • 新增节点不想写四件套

三层架构

组成 职责
编辑器 UIToolkit + IMGUI 可视化编排
数据层 Lua table / JSON 纯文本配置,人眼可读
运行时 ActionSequencePlayer → ActionSequence → ActionNode Update 循环驱动节点

ActionNode 生命周期

回调 触发时机 用途
OnCreate 序列实例化时 预加载资源
OnPlay 时间进入节点区间 播动画/特效/音效
OnUpdate 节点激活期间每帧 更新位置、检查条件
OnEnd 时间离开节点区间 停止、销毁
OnDestroy 序列销毁时 释放资源

两套方案对比

维度 Timeline 过场 自研表现序列
底层 PlayableGraph 求值 自己的 Update 循环
混合 有(权重求值) 无,纯并行
数据格式 .playable(重型序列化) Lua/JSON(扁平文本)
新增节点 四件套 继承基类重写方法
适用场景 过场演出、CG 技能、交互动作

共存方案:自研编辑器里加 PlayCutsceneNode,在表现序列中触发 Timeline 过场,两套打通。

常见坑点

表现 解决
WrapMode.None 播完角色跳回原位 想停在最后一帧用 Hold
Stop() 销毁 Graph 之后访问 playableGraph 无效 想停在当前帧用 Pause
ExposedReference.Resolve() 返回 null 没有 Director 上下文 确保在 Director 生命周期内调用
ControlTrack Prefab 实例化卡顿 播放开头卡一下 过场编辑器预加载所有角色
Binding 丢失 Prefab 模式换场景后绑定消失 运行时 Play 前用 SetGenericBinding 重绑

7.3 面试题精选

基础题

1. Timeline 中 TimelineAsset 和 PlayableDirector 的关系是什么?

题目

请简述 TimelineAsset 和 PlayableDirector 各自的角色,以及它们在 Timeline 播放流程中的关系。

深入解析

TimelineAsset 是静态资产(剧本),存储轨道、片段、绑定需求等配置数据,本身不执行任何逻辑。PlayableDirector 是运行时组件(导演),负责读取 TimelineAsset、构建 PlayableGraph、控制播放进度。

两者的关系可以用”剧本和导演”来类比:

  • TimelineAsset 定义”什么时候做什么”(剧本内容)
  • PlayableDirector 负责”把剧本搬上舞台执行”(导演调度)

一个 TimelineAsset 可以被多个 PlayableDirector 引用,通过不同的 Binding 配置实现复用——比如同一份过场 Timeline,绑不同角色就能让不同角色演同一套动作。

答题示例

TimelineAsset 是静态剧本,存储轨道和片段配置;PlayableDirector 是运行时导演,负责读剧本、建图、控制播放。

一个 TimelineAsset 可被多个 Director 复用,通过不同 Binding 实现同一套 Timeline 让不同对象执行。

参考文章
  • 1.概述.md
  • 3.PlayableDirector.md

2. PlayableDirector 的 WrapMode 有哪几种?None 模式有什么特殊行为?

题目

PlayableDirector 的 WrapMode 有哪些选项?None 模式播完后会发生什么?

深入解析

WrapMode 决定 Timeline 播到末尾后的行为:

  • Hold:停在最后一帧,角色保持最后姿势、镜头停在最后位置
  • Loop:从头循环播放
  • None:播完即结束,被 Timeline 修改过的属性会恢复成播前状态

None 模式的”属性恢复”行为容易被误解为 Bug。比如过场里角色从 A 点走到 B 点,播完后角色突然跳回 A 点——这是因为 None 模式下 PlayableGraph 销毁时会还原所有被修改的属性。想让角色停在 B 点,应该用 Hold 模式。

答题示例

WrapMode 有三种:Hold 停在最后一帧,Loop 循环播放,None 播完结束。

None 模式的特殊行为是播完后会恢复被 Timeline 修改过的属性到播前状态。想让角色停在最后位置应该用 Hold。

参考文章
  • 3.PlayableDirector.md

3. Signal Track 的工作流程是怎样的?

题目

请描述 Timeline 中 Signal 的工作流程,包括 SignalAsset、Signal Emitter、SignalReceiver、Reaction 各自的作用。

深入解析

Signal 是 Timeline 里做”时间点事件”的标准方案,链路如下:

  1. SignalAsset:消息类型/主题,在 Project 里创建(Create → Signal
  2. Signal Emitter:时间轴上的 Marker,持有 SignalAsset 引用,时间走到该点时发出信号
  3. SignalReceiver:挂在绑定对象上的组件,接收信号
  4. Reaction:SignalReceiver 里配置的 SignalAsset → UnityEvent 映射,决定收到信号后调哪个函数

换绑定时要注意:Reaction 存在 SignalReceiver 组件上,换绑定对象后新对象上也要配同一条 Reaction,否则收到信号不知道调什么。

答题示例

Signal 工作流程:SignalAsset 定义消息类型 → Signal Emitter 在时间点发信 → SignalReceiver 收信 → Reaction(UnityEvent)执行回调。

换绑定时要注意 Reaction 存在 Receiver 组件上,新绑定对象也要配 Reaction。

参考文章
  • 2.Timeline轨道.md

进阶题

1. Play()、Pause()、Stop() 三个方法的区别是什么?Stop() 有什么需要注意的?

题目

PlayableDirector 的 Play()、Pause()、Stop() 有什么区别?调用 Stop() 后再访问 playableGraph 会怎样?

深入解析

三个方法的区别:

方法 行为 Graph 状态 时间
Play() 开始/继续播放 构建或保持 从当前时间继续
Pause() 暂停 保留 停在当前帧
Stop() 停止 销毁 重置到 InitialTime

关键点是 Stop() 会销毁 PlayableGraph。调用 Stop() 后再访问 director.playableGraph 会拿到无效句柄,可能导致空引用或崩溃。

需要”停在当前帧但保留 Graph”的场景,应该用 Pause() 而不是 Stop()。Stop() 适合彻底结束播放、需要重建 Graph 的场景。

答题示例

Play() 开始播放并构建 Graph;Pause() 暂停但保留 Graph 和当前时间;Stop() 停止并销毁 Graph。

Stop() 后访问 playableGraph 会拿到无效句柄。想停在当前帧应该用 Pause(),Stop() 用于彻底结束需要重建的场景。

参考文章
  • 3.PlayableDirector.md

2. 如何在运行时动态绑定 Timeline 轨道?遍历 outputs 有什么优势?

题目

如何在运行时动态修改 Timeline 的轨道绑定?遍历 playableAsset.outputs 相比按轨道名查找有什么优势?

深入解析

运行时绑定使用 SetGenericBinding(key, value),其中 key 是 PlayableBinding.sourceObject(轨道对象引用),value 是要绑定的场景对象。

遍历 outputs 的标准写法:

foreach (PlayableBinding binding in director.playableAsset.outputs)
{
    if (binding.outputTargetType == typeof(Animator))
    {
        director.SetGenericBinding(binding.sourceObject, myAnimator);
    }
}

相比按轨道名查找的优势:

  • 不受改名影响:key 是对象引用,不是字符串
  • 不受顺序影响:轨道顺序调整不影响绑定逻辑
  • 类型安全:可以通过 outputTargetType 判断期望的绑定类型
答题示例

运行时用 SetGenericBinding(binding.sourceObject, target) 绑定,遍历 outputs 获取所有 PlayableBinding。

优势是按对象引用绑定,不受轨道改名、顺序调整影响,且可通过 outputTargetType 判断期望类型。

参考文章
  • 3.PlayableDirector.md

3. Timeline 运行时如何高效判断哪些 Clip 处于激活状态?

题目

Timeline 每帧需要判断哪些 Clip 在当前时间区间内,源码中是如何高效实现的?

深入解析

Timeline 使用 IntervalTree(区间树) 数据结构来存储所有 Clip 的时间区间。

建图时,每条轨道把自己的 Clip 按 [start, end] 区间存入 IntervalTree。每帧 PrepareFrame() 时,用当前时间作为查询条件,在 IntervalTree 中快速找出所有覆盖该时间的 Clip。

时间复杂度对比:

  • 遍历数组判断:O(n),n 为 Clip 数量
  • IntervalTree 区间查询:O(log n + k),k 为结果数量

Clip 数量多时(比如长过场有上百个片段),性能差距明显。这就是为什么 Timeline 能高效处理复杂时间轴编排。

答题示例

Timeline 使用 IntervalTree 存储 Clip 的时间区间,每帧 PrepareFrame() 用当前时间做区间查询。

时间复杂度从遍历数组的 O(n) 降到 O(log n + k),Clip 数量多时性能提升明显。

参考文章
  • 4.Timeline源码浅析.md

深度题

1. Timeline 和自研表现序列系统分别适合什么场景?为什么技能表现不适合用 Timeline?

题目

请对比 Timeline 过场编辑器和自研表现序列系统的适用场景,分析技能表现为什么不适合用 Timeline。

深入解析

Timeline 适合的场景:过场演出、CG

核心原因是需要混合

  • 两段动画之间的 MixIn/MixOut 过渡
  • Cinemachine 镜头之间的 Blend
  • 多轨道的时间对齐和权重求值

这些都依赖 PlayableGraph 的权重求值体系,自己从头写一套成本很高。Timeline 窗口本身是成熟的可视化编辑器,策划美术拖 Clip 就能出效果。

技能表现不适合 Timeline 的原因

特点 Timeline 技能需求
混合 有权重求值体系 不需要,纯并行触发
配置 .playable 重型序列化 需要 Lua/JSON 轻量热更
新增节点 四件套样板代码 继承基类重写方法即可
运行时 PlayableGraph 生命周期 一个 Update 循环足够

技能表现本质是”第几毫秒触发什么”,节点之间纯并行,不存在混合需求。用 PlayableGraph 那套显得过重。

两套方案可以共存:过场用 Timeline,技能用自研。自研编辑器里加 PlayCutsceneNode 就能在表现序列里触发 Timeline 过场。

答题示例

Timeline 适合过场演出,核心是需要 Clip 混合和权重求值,PlayableGraph 提供了这套能力。

技能表现不需要混合,节点纯并行触发;配置要轻量可热更;新增节点不想写四件套;一个 Update 循环就够了。用 Timeline 显得过重,自研更合适。

两套可以共存,过场用 Timeline,技能用自研。

参考文章
  • 5.过场编辑器设计.md
  • 6.自研表现序列系统设计.md

2. 自定义 Timeline 轨道需要写哪些类?如果不需要 Clip 混合,Mixer 可以怎么简化?

题目

自定义 Timeline 轨道需要实现哪四个类?如果 Clip 之间不需要混合(比如纯事件触发轨道),Mixer 可以如何简化?

深入解析

自定义轨道需要四个类:

职责 关键点
TrackAsset 轨道定义 声明绑定类型、CreateTrackMixer() 创建 Mixer
PlayableAsset Clip 数据 CreatePlayable() 创建运行时节点
PlayableBehaviour Clip 运行时逻辑 ProcessFrame() 每帧执行
TrackMixerBehaviour 轨道混合 处理多 Clip 重叠时的权重混合

不需要混合时的简化

很多游戏逻辑轨道(对话触发、特效播放、事件通知)不需要 Clip 之间的权重混合——同一时刻只有一个 Clip 在起作用。这种情况下,Mixer 可以简化为:

public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
    int inputCount = playable.GetInputCount();
    int maxWeightIndex = -1;
    float maxWeight = 0f;
    
    // 找权重最大的 Clip
    for (int i = 0; i < inputCount; i++)
    {
        float weight = playable.GetInputWeight(i);
        if (weight > maxWeight)
        {
            maxWeight = weight;
            maxWeightIndex = i;
        }
    }
    
    // 只执行权重最大的那个
    if (maxWeightIndex >= 0)
    {
        // 执行该 Clip 的逻辑
    }
}

但四个类的壳子仍需写,这也是自研编辑器想绕过 Timeline 的原因之一——节点类型多时,每种写四件套很繁琐。

答题示例

四件套:TrackAsset 定义轨道、PlayableAsset 描述 Clip 数据、PlayableBehaviour 执行运行时逻辑、TrackMixerBehaviour 处理混合。

不需要混合时,Mixer 可简化为遍历输入找权重最大的 Clip 执行。但四个类的壳子仍需写,节点类型多时样板代码较繁琐。

参考文章
  • 2.Timeline轨道.md
  • 4.Timeline源码浅析.md


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

×

喜欢就点赞,疼爱就打赏