浅析Timeline结构

结构

PlayableDirector Timeline
PlayableGraph Graph
TrackGroup TrackGroup
TrackAsset AnimationTrack PlayableTrack CinemachineTrack CustomTrack
TimelineClips TimelineClips TimelineClips TimelineClips
PlayableAsset AnimationClip (Motion) CustomPlayableAsset CinemachineShot CustomClip
PlayableBehaviour PlayableBehaviour PlayableBehaviour CinemachineShotPlayable PlayableBehaviour
  • 从表中可以看出是一个树结构,在Timeline Editor中也是一目了然。核心的类就是最左边一列中列出的。

组件

Timeline

Timeline & PlayableDirector的关系
  • 本质是个PlayableDirector,作用顾名思义。
PlayableGraph & PlayableDirector的关系
  • 从测试情况看,一个PlayableDirector对应一个PlayableGraph
  • 通过PlayableDirector pd = graph.GetResolver() as PlayableDirector;可以反向得到PlayableDirector,在PlayableAsset中可能会比较有用。

TrackAsset

  • TrackAsset从视觉上看就是Timeline Editor中左边preview中的一项,每个track可以约束它所影响的 GameObject的类型,也可以设置它上面clip的类型。
  • 从实际的操作上来看,PlayableTrack上可以放置任何继承自PlayableAsset的clip,但是其它的track上就必须放置约定类型的clip了。
如何自定义一个Track
1
2
3
4
[TrackColor(1f, 1f, 0f)]
[TrackClipType(typeof(LightControlClip))]
[TrackBindingType(typeof(Light))]
public class LightControlTrack : TrackAsset {}
  • 从上面的代码看TrackBindingType定义了绑定的类型,TrackClipType定义了clip的类型。而TrackColor是定义了在Timeline Editor中最左边的那条很细的彩色线条。
  • 经过测试,在自定义的TrackAsset中可以不实现CreateTrackMixer方法,但是如果要去override它,代码中不能用return Playable.Create(graph);,而是用return ScriptPlayable<CustomMixer>.Create(graph, inputCount);哪怕这个CustomMixer是个空类都可以,很坑爹。

PlayableAsset(Clip)

  • 要说PlayableAsset就离不开PlayableBehaviour。在旧版本的Timeline中BasePlayableBehaviour实现了PlayableAsset和PlayableBehaviour的功能。但是因为已经被舍弃了,因此想实现既可以放到track上,又能监控状态的对象需要用PlayableAsset配合PlayableBehaviour。做法为在自定义的PlayableAsset中写:
1
2
3
4
public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
{
return ScriptPlayable<CustomBehaviour>.Create(graph, template);
}

PlayableBehaviour

  • 核心类,其定义了事件函数涵盖了自身的状态变化、graph的状态变化和PlayableDirector创建销毁时触发的事件。
  • PrepareFrame函数可以在每一帧对timeline中的元素进行访问和设置。可以说是在做自定义blend中不可缺失的功能。

执行顺序

  • 目前来看应该是:

CreateTrackMixer->CreatePlayable->OnPlayableCreate->OnGraphStart->OnBehaviourPause->OnBehaviourPlay->OnGraphStop->OnPlayableDestroy

  • 这个顺序是一个clip的。当一个track上有多个clip时OnPlayableCreate、OnGraphStart、OnBehaviourPause会无序的出现,但是一定是在OnBehaviourPlay之前。
  • 在真正的clip的执行期间,OnBehaviourPlay和OnBehaviourPause是按顺序执行的。
  • 基本上可以判断当一个PlayableBehaviour准备好后,会先被pause。然后按照设计好的顺序执行,当开始执行时触发play,结束后再次触发pause。所以如果是要在一个clip结束后处理什么事情需要做一个判断,是否是第一次触发pause。

组件之间互相获取的方法

Timeline(PlayableDirector)的获取方式
  • 通过脚本中设置PlayableDirector类型变量获得。
  • 如果是自定义的TrackAsset,则通过CreateTrackMixer方法的go参数获得。
1
2
3
4
public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
{
PlayableDirector playableDirector = go.GetComponent<PlayableDirector>();
}
  • 如果是自定义的PlayableAsset,则通过
1
2
3
4
public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
{
var pd = graph.GetResolver() as PlayableDirector;
}
  • PlayableBehaviour脚本中函数都是有playable参数,通过这个参数也可以获得
1
2
3
4
public override void OnBehaviourPlay(Playable playable, FrameData info)
{
PlayableDirector pd = playable.GetGraph<Playable>().GetResolver() as PlayableDirector;
}
Timeline中获取track、clip和PlayerBehaviour
  • 从上面的代码可以看到,获取director的过程比较符合最上面的那个表里的层级关系。但是从timeline获取其它的元素会比较困难。或者说比较不符合这个层级关系。我个人认为在API的设计上是有问题的。
  • 那么要如何通过timeline获取这些元素呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public PlayableDirector pd;
// Use this for initialization
void Start()
{
var binding = pd.playableAsset.outputs;
foreach (var item in binding)
{
switch (item.sourceObject.GetType().Name)
{
case "AnimationTrack":
{
var at = item.sourceObject as AnimationTrack;
foreach (TimelineClip clip in at.GetClips())
{
Debug.Log(clip.animationClip.name + "\n");
}
}
break;
case "CinemachineTrack":
var item2 = item.sourceObject as CinemachineTrack;
foreach (TimelineClip clip in item2.GetClips())
{
CinemachineShot cs = clip.asset as CinemachineShot;
Debug.Log(cs.VirtualCamera.Resolve(pd) + "\n");
}
break;
case "PlayableTrack":
var pt = item.sourceObject as PlayableTrack;
foreach (TimelineClip clip in pt.GetClips())
{
NewPlayableAsset cs = clip.asset as NewPlayableAsset;
}
break;
case "CustomTrack":
var ct = item.sourceObject as CustomTrack;
foreach (TimelineClip clip in ct.GetClips())
{
CustomClip cs = clip.asset as CustomClip;
CustomBehaviour cb = cs.template;
cb.Foo();
}
break;
default:
break;
}
}
}
  • 从代码上看pd.playableAsset.outputs获得了一个IEnumerable<PlayableBinding>类型的集合。这个设计让人非常费解。然后遍历集合,得到item.sourceObject,这个对象就是track了。然后可以根据不同的类型转换成不同的Track。然后就和表中的结构一致了,获得clip->playableasset->playablebehaviour。
获得场景对象
  • Timeline中获取对象在API设计上也不合理,在unity的通用做法是在代码中定义一个Public变量或者使用[SerializeField]标记一个private的变量,然后拖拽。又或者用过GameObject.Find来获取。但是对于策划和美术来说最多的还是拖拽。
  • Timeline相关的脚本中可以继续使用GameObject.Find。但是如果你想用拖拽的形式需要这样定义对象:
1
public ExposedReference<T> object;
  • 而当你想真正使用这个变量的值的时候需要用
1
object.Resolve (graph.GetResolver());
  • 需要说明的是在Timeline的那些脚本类里面是可以用public定义变量的,在inspector面板上也可以显示出来,但是退拽无效。
获取Track绑定的对象
  • 目前来看似乎只有用GameObject.Fine或者直接用属性值了。
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×