【Unity博客节选】Playable系统 UML类图与结构分析
注:软件版本Unity 6.0 + Timeline 1.8.7
作者:CSDN @ RingleaderWang
原文:《Unity第25期——Timeline结构及其源码浅析》
文章首发Github👍:《Timeline结构及其源码浅析》
Bilibili 视频版👍👍
:《Timeline结构及其源码解析》https://www.bilibili.com/video/BV1bHjYzNE35
Playable系统
前文介绍了Timeline的静态结构,为了更好地理解运行时Timeline,需要先了解底层的Playable系统。Playable 系统包括PlayableGraph、Playable和PlayableOutput。
下面依次介绍。
PlayableGraph
PlayableGraph管理Playable和PlayableOutput节点,控制整个节点树的运行。
PlayableGraph 与 PlayableDirector 类
Timeline 运行时会根据PlayableDirector的Playable参数创建PlayableGraph。
PlayableDirector可以用PlayableDirector.playableGraph
属性获得创建的graph;
PlayableGraph也可以用PlayableGraph.GetResolver() as PlayableDirector
解析出PlayableDirector。PlayableGraph的很多方法都可以直接在PlayableDirector中调用。
PlayableGraph 和 PlayableDirector UML类图如下:
PlayableGraph的结构
对于前面的 timeline 资产:
在运行时形成的Graph结构如下:
PlayableGraph的构建过程:
- PlayableDirector
RebuildGraph
触发构建graph动作 - 根据director的TimelineAsset 创建
TimelinePlayable
- 然后依次 编译所有track和clip,创建对应的MixerPlayable、PlayableOutput和Playable。
- TimelinePlayable作为根节点,下一层是MixerPlayable或LayerMixerPlayable,叶子节点是包含clip的Playable。
- Playable之间是通过
Connect
连接,PlayableOutput通过setSourcePlayable
与TimelinePlayable连接。
- 编译clips期间会build
IntervalTree
,为后续graph play作准备。
大体过程图如下:
Playable 的结构与类
PlayableGraph 中的 Playable 节点负责 数据的生产、传递与处理,包含叶子节点、mixer节点和layer节点等。
Playable结构
Playable是一个多输入多输出的有向节点,拥有speed、time、duration、isDone、PlayState等参数标识自身的运行状态,并对外提供控制自身运行、暂停与销毁的接口。
节点属性/方法
- Playable节点拥有多个input port、多个output port用于连接其他Playable节点。
- 节点连接前需保证port存在,否则报错
Connecting invalid input/output
,需要用SetInputCount()/SetOutputCount()
申请端口,或者create时指明inputCount参数。 - 同一port不能连接多个节点,否则报错
Cannot connect input/output port, it is already connected, the tree topology will be invalid. Disconnect it first
- Input有权重weight的概念,默认是0,可以在connect时指定weight或者用
Playable.SetInputWeight(inputIndex, weight)
设定输入权重 - Playable节点间连接,可以使用
PlayableGraph.Connect(...)
或者扩展方法的Playable.ConnectInput(...)
(默认权重为0)
PlayableGraph.Connect(...)bool Connect<U, V>( U source, int sourceOutputPort, V destination, int destinationInputPort) where U : struct, IPlayable where V : struct, IPlayable
允许的结构:
不允许的结构(三个节点首尾相接)(出现死循环,如果使用graph visulizer,unity会奔溃)
一些特殊的属性/方法
GetScriptInstance()
,指明playable生命周期的回调方法GetClip()/SetClip
,一些包含Clip内容的Playable如 AnimationClipPlayable、AudioClipPlayable、AnimatorControllerPlayable、MaterialEffectPlayable、CameraPlayable、VideoPlayable 拥有存取此Playable绑定的可播放片段的方法。这些方法名不固定,根据Playable类型,也可以叫 Get/SetMaterial()、GetAnimatorController、Get/SetCamera()、GetAnimationClip()等。
运行属性/方法
- Play state:
Puase/Playing
,标识此节点运行状态 Speed
:playable的播放速度Play()
:运行playablePause()
:停止自身playable运行IsDone
:标识此节点运行完毕Destroy()
:利用所属graph销毁自身Playable- TraversalMode:
Mix/Passthrough
,表示遍历节点input的方式,Mix
就是将各输入进行加权;passthrough
就是匹配input与output,第几个output来请求数据,就从第几个input获取数据。Passthrough目前只用于TimelinePlayable,使之成为graph的统一入口。
Playble UML类图
PlayableAsset创建的playable有两种,一种是不带生命周期管理的Playable,另一种是带生命周期管理的(实现IPlayableBehaviour接口)。第二种其实就是Timeline模块中基于ScriptPlayable实现的一种官方自定义playable。
基础Playable 的 UML 类图
注意这些Playable都是Struct,没有像class一样的继承能力,但这些子Playable都重写了隐/显式操作符,所以功能上等价于存在继承关系。比如下面AudioClipPlayable例子:
public static implicit operator Playable(AudioClipPlayable playable)
{ return new Playable(playable.GetHandle());
} public static explicit operator AudioClipPlayable(Playable playable)
{ return new AudioClipPlayable(playable.GetHandle());
}// 自动转换
AudioClipPlayable audioClipPlayable = ...;
Playable playable = audioClipPlayable;// 需要显式转换
Playable playable = ...;
AudioClipPlayable audioClipPlayable = (AudioClipPlayable)playable;
- 这些Playable都拥有
Create
方法,供TrackAsset执行CompileClips方法或CompileTrackPlayable方法期间调用。 - Playable按功能大概分成两类:
- 一类如上面蓝色(AnimationClipPlayable等)和绿色(ScriptPlayable)的Playable,在graph中常作为叶子节点的,其内部是含有类似clip的属性,能getClip、setClip(有的没暴露set,只能在create时传入)。
- 一类是上面黄底色的Playable,在graph中作为中间节点,可能包含多个输入,所以命名一般叫MixerPlayable、LayerPlayable,对多个输入的权重或其他数据进行处理。
- 上面结构图并没有列出所有Playable,详细的可以查看IPlayable的所有实现。
ScriptPlayable
比较特殊,能够接收PlayableBehaviour
来控制Playable的生命周期,这样形成的就是自定义Playable(Timeline里的很多Playable本质上就是这种官方自定义Playable)。
下面就详细介绍这些所谓的“官方自定义Playable”。
PlayableBehaviour 的 UML 类图
继承PlayableBehaviour
的类分为几种:
TimelinePlayable
:作为graph中所有其他playable的根父节点,控制所有clip的激活与否。- Audio的两个名字比较特别,叫
AudioMixerProperties
和AudioClipProperties
,AudioClipProperties甚至连一个回调都没有,这就涉及到PlayableBehaviour的第二种作用:传递PlayableAsset/TrackAsset参数给运行时的各生命周期。这种参数传递有两种用法,后面会介绍。 - Activation有关的 ActivationControlPlayable 和 ActivationMixerPlayable 。
比较好玩的是 ActivationControlPlayable 只用在 ControlTrack ,而 ActivationMixerPlayable 只用在 ActivationTrack 。 ControlTrack
的三个playable:- 控制粒子的
ParticleControlPlayable
- 控制prefab的
PrefabControlPlayable
- 控制PlayableDirector(也就是控制子Timeline)的
DirectorControlPlayable
。
- 控制粒子的
PlayableOutput 结构与类
PlayableOutput
作为 PlayableGraph 中的数据输出节点,在graph Play 时负责发起 Playable节点的遍历(没连PlayableOutput 对应的Playable不运行),并将Playable节点传递的数据交由对应的target
运行。
PlayableOutput 结构
PlayableOutput 的结构相当简单。
- ReferenceObject:对应的TrackAsset
- SourcePlayable:PlayableOutput连接的Playable,使用扩展方法
PlayableOutput.SetSourcePlayable(Playable value, int port)
连接 - 注意连接的SourcePlayable和port不能都一样,否则报错
Cannot set multiple PlayableOutputs to the same source playable and output port
- weight:PlayableOutput也拥有权重,默认为1,可以通过
PlayableOutput.SetWeight(float value)
设置。 - target:AnimationPlayableOutput、AudioPlayableOutput、TexturePlayableOutput拥有这个属性,表示Track所绑定的对象,用于后续处理Playable传过来的数据。
UML 类图
相比Playable的类图,PlayableOutput就显得眉清目秀了。
- 跟Playable一样,PlayableOutput也是struct结构体,它的继承也是重载显隐操作符。
- 每种PlayableOutput都有个create方法来创建一个具体的PlayableOutput。
- 除了ScriptPlayableOutput外,都有个target参数用于指明track binding的target对象。