环境
- Unity 2020、Unity 2021
1、TML对象(Playable)的创建过程
时序图
1.1
个人推测,整个创建过程是由PlayableDirector发起的,依据如下:
- 在
Play前要先设置好playableAsset -
playableAsset是直接存储在Navitve层的(也可以序列化存储起来直接用)
/*PlayableDirector反编译的代码*/
public PlayableAsset playableAsset
{
get => this.Internal_GetPlayableAsset() as PlayableAsset;
set => this.SetPlayableAsset((ScriptableObject) value);
}
public void Play(PlayableAsset asset)
{
if ((UnityEngine.Object) asset == (UnityEngine.Object) null)
throw new ArgumentNullException(nameof (asset));
this.Play(asset, this.extrapolationMode);
}
public void Play(PlayableAsset asset, DirectorWrapMode mode)
{
this.playableAsset = !((UnityEngine.Object) asset == (UnityEngine.Object) null) ? asset : throw new ArgumentNullException(nameof (asset));
this.extrapolationMode = mode;
this.Play();
}
[NativeThrows]
[MethodImpl(MethodImplOptions.InternalCall)]
public extern void Play();
1.2
通过上面的时序图,可以大致看出TimelineAsset、Track、Clip、Mixer、Playable这些元素间的关系。
- TimelineAsset是一个容器,它记录了Track、Clip的信息,并且会根据这些信息创建它们相关的Playable
- TimelineAsset本身会创建一个Playable(ScriptPlayable<TimelinePlayable>),它是根Playable,由它关联着各个Track、Clip对应的Playable
- TrackAsset负责创建Mixer的Playable,在
public virtual Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)中实现。同时也一定程度上管理Clip的创建。 - XxxClipAsset负责最终的Clip Playable创建,在
Playable CreatePlayable(PlayableGraph graph, GameObject owner)里实现。
有个小点值得注意,与Track不同Clip的Asset并没有一个专用的基类,它是直接继承自PlayableAsset。这么做大概是因为它足够简单,只是一个用来创建Clip Playable的小工厂,没有其他的管理职责。 - RuntimeClip、TimelineClip这两个类在结构上很重要,但是跟创建Playable没太大关系。放在时序图里,是为了表明它们出场的时机,更多的介绍在下面结构部分。
2、TML的(类)结构
TML类图-Asset部分
TML类图-Clip部分
为了理解TML的机制,以及TML跟Playable的关系,下面几个类需要重点关注:RuntimeClip、TimelineClip、TimelineAsset、TrackAsset、TimelinePlayable 这些类可以大致分为三组:
- 序列化存储:TimelineAsset、TrackAsset、TimelineClip、XxxClipAsset(类图中没有)
- 播放(时间)控制:RuntimeClip、其他IInterval的实现类
- Playable结构:TimelinePlayable
1)TimelineAsset
- 它继承自PlayableAsset,所以会创建一个自己的Playable
- 它是一个ScriptableObject,所以它用于序列化存储的信息
-
m_Tracks记录了这一个TML里所有的一级(Root)TrackAsset信息。也就是在【Timeline】视图中,把所有轨道折叠起来后,最外层的部分。
GroupTrack - 只有分组的作用
PlayableTrack - 有实际功能的轨道
自定义的TrackAsset类 - 实现自定义轨道时,可以继承TrackAsset记录自己的轨道信息 -
IEnumerable<TrackAsset> GetOutputTracks()取有Output的TrackAsset,也就是有实际功能的轨道Asset。
在Unity实现中,是在所有的TrackAsset中里把GroupTrack排除掉之后,返回结果。 -
public override Playable CreatePlayable(PlayableGraph graph, GameObject go)代码注释写的是 “Creates an instance of the timeline”,对返回值的说明是 “The Root Playable of the Timeline”。
从代码中看到,它就是创建了一个ScriptPlayable<TimelinePlayable>的实例,并将所有轨道的输入、输出关联到这个实例上。关联关系见下图:
TML的Graph图
这里我有个很疑惑的点,TimelinePlayable的输出只有一个(playable.GetOutputCount()=0),但它却关联了多个PlayableOutput的实例。从图中能看到,它应该是通过“SourceOutputPort”来区分的,但这个Port的概念是什么无从得知。从PlayableTraversalMode.Passthrough这个枚举值的注释中可以推测:OutputPort跟InputPort是一一对应的,若[SourceOutputPort=3],它的输入应该是,TimelinePlayable中[Input 3]对应的Playable。
Causes the Playable to act as a passthrough for PrepareFrame and ProcessFrame.
If the PlayableOutput being processed is connected to the n-th input port of the Playable, the Playable only propagates the n-th output port.
Use this enum value in conjunction with PlayableOutput SetSourceOutputPort.
这种只有一个Output却产生多个PlayableOutput结果的设定,让我很不解,如果有大佬明白这里的设计思路,还请不吝赐教!!!!
2)TrackAsset
- 在序列化存储、Playable创建这两方面,它跟TimelineAsset差不多,只是它只负责轨道相关的部分
- 在Timelin的设计中,轨道、分组都是Track,所以能看到它有两个非常简单的子类:PlayableTrack、GroupTrack
- 观察YAML序列化文件发现
m_Children是GroupTrack在用,它记录着分组下的所有TrackAsset引用;
m_Clips是PlayableTrack(含自定义的TrackAsset)在用,它记录每个轨道的Clip引用。 - 特别要注意,
m_Clips记录的元素对象是TimelineClip并不是ClipAsset -
start、end提供轨道的开始、结束时间,这两个时间并不是序列化存储下来的,而是每次取的时候根据所有Clip的起止时间计算得到。
Unity通过CombineHash对比,规避了每次都算时间的消耗,但每次算Hash也是有开销的。 - 子类可以重载
public virtual Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)创建自定义的Mixer Playable - 在自定义Timeline轨道的时候,TrackAsset的主要用途就是存储轨道的数据,并创建自定义的Mixer Playable实例
3)TimelineClip
-
它只是一个记录Clip时间数据的C#类(Serializable),并不是ClipAsset
TimelineClip主要记录的信息
- 同时它也负责处理各种时间的转换,如
double ToLocalTime(double time)根据TimelinePlayable的当前时间,计算出Clip自身的当前时间。TimelinePlayable的当前时间,应该等于整个TML的当前播放时间,因为没有地方改变它的播放进度。 - 真正的ClipAsset由
Object m_Asset引用着,通常它的使用方式非常简单,就是创建一个Playable(clip.asset as IPlayableAsset).CreatePlayable(graph, gameObject);。当然,也可以转型为具体的ClipAsset,在里面取一些特定信息。 - 在自定义Timeline轨道的时候,ClipAsset的主要用途是存储Clip相关的数据,并创建自定义的Clip Playable实例
4)RuntimeClip
- 它在每次Evaluate的时候,计算当前Clip的时间,并更新到Playable
- 它在每次Evaluate的时候,根据融合参数,计算当前Clip在父级Mixer中的权重
5)TimelinePlayable
- 它是一个PlayableBehaviour
- 它的主要作用是在PlayableGraph播放的过程中,调度TML相关Playable(主要是Clip)的播放进度
-
m_CurrentListOfActiveClips是每次PrepareFrame取到的,当前时刻处于激活状态的Clip列表。通常它里面的元素是RuntimeClip -
m_ActiveClips是m_CurrentListOfActiveClips的副本,用于在下次执行时,判断哪些Clip应该被禁用(Disable) -
m_PlayableCache、m_IntervalTree这两个字段里分别持有TrackAsset、ClipAsset的信息,在调试的时候可以通过反射的方式,在它们里面找到需要的信息。
(我在调试TML的时候,多次想要在Mixer里输出一些Asset的数据,发现当前对象是PlayableBehaviour的实例,根本访问不到Asset的内容)










网友评论