文档:
参考代码:
具体的代码实现,参考之前写过的例子
描述一个Clip的Behaviour
描述一个Track上所有Clip的融合之后的Behaviour
描述一个Clip
描述一个Track
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.
动画的执行者
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是在做什么?
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.
Timeline代表的是线性的时间,默认的Clip都是有长度的,有明确的开始和结束范围,但是在实际应用中,需要一些没有长度的“代表”(两者就像“时间”和“时刻”的关系),这就是Timeline中Event的概念
Event就是上述所说的“没有长度的代表”这一概念。Signal和CustomMarker是实现Event的方式,Signal是引擎默认提供的功能,它也是一种Marker吧,底层都是一样的机制,都是Marker
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是如何生成的?用户在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是在何时生成的?在用户在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/
比较特殊的一个Track,待补充…
Play / Pause / Stop
之前写过的代码中有过实现,参考UiAnimation
选中Timeline的资源和选中PlayableDirector时,Timeline窗口的“模式”是不同的,比如:选中Clip时Inspector显示的内容是不一样的(因为有些数据就是Timeline Asset的内容,而有的是序列化在PlayableDirector上的)