Table of Contents

Unity Timeline

文档:

参考代码:


Overview

timeline.bmind

组成部分

具体的代码实现,参考之前写过的例子

BehaviourBase : PlayableBehaviour

描述一个Clip的Behaviour

MixerBase : PlayableBehaviour

描述一个Track上所有Clip的融合之后的Behaviour

ClipBase : PlayableAsset

描述一个Clip

TrackBase : TrackAsset (PlayableAsset)

描述一个Track

  1. 接受什么类型的Binding
  2. 支持什么类型的Clip

TimelineAsset : PlayableAsset

Timeline资源本身,无需拓展

TimelineAsset在Timeline Editor中有一些组织管理工具,比如分组、命名等,都是为了方便管理,不影响程序功能,对程序而言TimelineAsset就是Track的集合

Timeline Asset: Stores the tracks, clips, and recorded animations without links to the specific GameObjects being animated. The Timeline Asset is saved to the Project.

关于Playable Director

动画的执行者

Timeline instance: Stores links to the specific GameObjects being animated or affected by the Timeline Asset. These links, referred to as bindings, are saved to the Scene.

关于Playable API

创建Playable是在做什么?

All non-abstract playables have a public static method Create() that creates a playable of the corresponding type. The ‘Create()’ method always takes a PlayableGraph as its first parameter, and that graph owns the newly created playable. Additional parameters may be required for some type of playables. Non-abstract playable outputs also expose a Create() method.

Event / Signal / CustomMarker

Timeline代表的是线性的时间,默认的Clip都是有长度的,有明确的开始和结束范围,但是在实际应用中,需要一些没有长度的“代表”(两者就像“时间”和“时刻”的关系),这就是Timeline中Event的概念

三者的关系

Event就是上述所说的“没有长度的代表”这一概念。Signal和CustomMarker是实现Event的方式,Signal是引擎默认提供的功能,它也是一种Marker吧,底层都是一样的机制,都是Marker

Event

  1. 需要明确发送方和接受方,两者并没有解耦,并不是通用的Event系统,整个Event还是运作在Timeline内部

Signal

  1. Signal不能带参数
  2. 使用Signal需要创建Asset
  3. 打开Signal的Asset,里面基本就是只存储了一个m_Name

CustomMarker

  1. You can place a marker on a track, or in the Markers area under the Timeline ruler.
  2. 可以在两个位置创建Marker(见上述),在Markers area创建的Marker,Recver就是PlayableDirector
  3. [待验证]绑定的目标类型是GameObject的Track上面,可以添加Marker,Recver就是绑定的GameObject
  4. 发送方继承:INotification
  5. 接受方继承:INotificationReceiver
  6. 参考:How to create custom Timeline Markers https://blog.unity.com/technology/how-to-create-custom-timeline-markers

Binding和Reference

Timeline Asset就像一个模板,描述的是动画本身,但是没有限定动画的执行人。比如“{0}和{1}在3s内加速向前移动了{move_length}m”,其中{0}和{1}很好理解就是动画的执行人,我们绑定两个人物即可,这就是Binding;然而{move_length}却有点不同,首先它是一个value_type(而不是reference_type),其次它和动画内容相关(这里的意思是:完整的动画中可能有多个且数量不定的Move的Clip,每一个Clip都有自己单独的{move_length},也就是说这是一个在Clip上设定的参数),这种就是Reference。总结一下:Binding是Track层级的参数,Reference是Clip层级的参数。

举上面的例子是为了理解Binding和Reference,但是这个例子有一个不合理的地方需要说明:在上面的例子中,{move_length}是一个value_type,但是Reference是不允许使用value_type的,看一下定义:

public struct ExposedReference<T> where T : Object

说明Timeline中暴露出来的Reference只能是reference_type(看起来是废话…)。所以要明白:上面使用{move_length}举例子,并不是说Clip中的所有参数都可以被暴露出来,而是只有Object的子类可以被暴露。

OK,那么如何设定一个参数可以被暴露呢?上面的代码也提示了,应该使用ExposedReference,如下:

// In Clip
public ExposedReference<GameObject> exposedSomeReference;
 
// Read
var obj = exposedSomeReference.Resolve(graph.GetResolver());

那么这个参数在Editor中就会提供可以暴露的选项:

那么Binding和Reference都被保存在了那里呢?就是PlayableDirector:PlayableDirector代表一个完整可以播放的动画。首先,它引用了一个Timeline Asset(也就是动画的模板,“{0}和{1}在3s内加速向前移动了{move_length}m”);然后,它保存了一组“实参”,就像{0}=小明、{1}=小红、{move_length_guid_code_xxxx}=5,这样将参数带入“动画模板”,就可以播放动画了!关于实参中的{move_length_guid_code_xxxx}是什么:之前我们提到了{move_length}是一个Reference,在整个动画中可能有多个(而且其是和Clip绑定的),我们不需要知道每一个Move Clip上的{move_length}分别是多少,Unity会自动为每一个{move_length}生成单独的ID,也就是上文中guid_code_xxxx表达的意思,所以我们向”动画模板“中填入Reference的时候,要根据Unity生成的ID来填({move_length_guid_code_xxxx}=5,不是{move_length}=5)。

首先,我们如何操作Binding?

Binding是如何生成的?用户在Timeline Asset中添加几个Track(可以Binding的),我们应该就能拿到几个Binding关系。Binding关系就像一个Map,Key是TimelineAsset中的Track,Value就是Binding的目标。

// Read Binding
foreach (var kOut in kPlayAsset.outputs)
{
    var kBind = kDirector.GetGenericBinding(kOut.sourceObject);
    ...
}

如何恢复Binding?

PlayableDirector.SetGenericBinding(Object key, Object value);

然后,我们如何操作Reference?

Reference是在何时生成的?在用户在Editor中将参数暴露出来的时候,Unity会自动为这个Reference生成ID,序列化到PlayableDirector中。所以生成的一步不需要我们干涉

那么我们怎么知道用户暴露了哪些Reference呢?Unity没有提供这个接口(那我们只好硬抢了),按照以上的思路,暴露的Reference是序列化在PlayableDirector中的,所以…

UnityEditor.SerializedObject kSo = new UnityEditor.SerializedObject(kDirector);
var kRefList = kSo.FindProperty("m_ExposedReferences").FindPropertyRelative("m_References");
for (int i = 0; i < kRefList.arraySize; i++)
{
    var kRef = kRefList.GetArrayElementAtIndex(i);
    var kO = kDirector.GetReferenceValue(kRef.displayName, out bool bIs);
    if (bIs)
    ...

恢复Reference的参数值就很简单了

PlayableDirector.SetReferenceValue(PropertyName id, Object value);

干净的预览方式

什么是“干净的预览”(自己根据理解起的名字)?Timeline的本质还是:随着时间的进行,进行的一系列操作。这其中肯定会涉及到参数的修改,在场景中预览Timeline的时候,Timeline控制的GameObject的各种属性都会被修改,但是当离开预览模式的时候(取消选中PlayableDirector),被Timeline修改的各种参数都应该被恢复回来,否则肯定会影响的场景其他部分的编辑。默认情况下我们自己实现的Track都无法做到恢复,这就是”将场景弄脏了“,引擎默认提供的Track都可以做到恢复参数,这就是“干净的预览”。

从思路上来说,并不复杂,需要修改的参数只要做一层缓冲就可以了,但是可能细节比较多,也会涉及到一些底层操作。而且Unity已经实现了这个机制,我们直接使用就可以了。如下:

// In Track
public override void GatherProperties(PlayableDirector director, IPropertyCollector driver)
{
    var rectTransform = director.GetGenericBinding(this) as RectTransform;
    if (rectTransform != null)
    {
        // 需要做缓冲的属性
        driver.AddFromName(rectTransform, "m_AnchoredPosition");
    }
}

参考:Temporary preview changes from custom timeline track https://forum.unity.com/threads/temporary-preview-changes-from-custom-timeline-track.650902/

Control Track

比较特殊的一个Track,待补充…

Runtime

Play / Pause / Stop

零散

获取Clip的开始结束时间

之前写过的代码中有过实现,参考UiAnimation

Timeline窗口的内容

选中Timeline的资源和选中PlayableDirector时,Timeline窗口的“模式”是不同的,比如:选中Clip时Inspector显示的内容是不一样的(因为有些数据就是Timeline Asset的内容,而有的是序列化在PlayableDirector上的)