四大脚本

  • Data - PlayerBehaviour 每一个Clip所包含的数据,属于Clip

  • Clip - PlayerAsset 轨道上的每一个片段
  • Mixer - PlayerBehaviour 可以理解为一个总的Clip,用于混合所有的子Clip
  • Track - TrackAsset 轨道,用于存放Clip

自定义轨道

以物品移动为例创建一个简单的自定义轨道

创建Data

这代表我们Clip里所需要用到的数据

[System.Serializable]
public class TransformTweenBehaviour : PlayableBehaviour
{
    public Transform endLocation;
}

创建Clip

[System.Serializable]
public class TransformClip : PlayableAsset, ITimelineClipAsset
{
    // data
    public TransformTweenBehaviour data = new TransformTweenBehaviour();
    public ExposedReference<Transform> endLocation;
    
    public ClipCaps clipCaps => ClipCaps.Blending;
    
    // Factory method that generates a playable based on this asset
    public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
    {
        var playable = ScriptPlayable<TransformTweenBehaviour>.Create(graph, data);
        var clone = clip.GetBehaviour();
        clone.endLocation = endLocation.Resolve(graph.GetResolver());
        return playable;
    }
}

首先ITimelineClipAsset是Clip的接口,继承之后会有一个clipCaps的属性可以重写,他影响面板上Clip的样式,不过这不重要

Data是属于Clip的

image-20220920174316097

所以我们的Clip里很自然的会定义一个所需要的Data数据,也就是之前的TransformTweenBehaviour

重点是这个CreatePlayable方法,方法啥时候调用呢?为什么是叫Playable而不是Clip呢?

Clip只是一种资源形势,而我们运行Timeline的第一帧,系统会调用这个方法,把Clip转换为Playable,同时塞入Data数据

创建Playable的方法是固定的,照着写就行了

var playable = ScriptPlayable<TransformTweenBehaviour>.Create(graph, data);

这是一个工厂方法,graph就是图,也就是整个Timeline,这个我们不管

Create方法还要求我们塞入Clip所需要的数据类型,他会把数据类型给我们填到Playable中

image-20220920180142727

但是这时候Playable里还是没有真实数据的,只有数据类型而已

所以我们还需要把数据类型自己赋值进去

var clone = clip.GetBehaviour();
clone.endLocation = endLocation.Resolve(graph.GetResolver());

那么这里的Resolve又是啥玩意呢

因为Clip是一种资源,资源是不能直接记录资源引用的,Mono可以引用Asset,但是反过来Asset是不能引用Mono的,所以就需要用到一种奇怪的方式

定义成如下结构

public ExposedReference<Transform> endLocation

然后想要查找资源的时候再使用Resolve解析就好

创建Track

基础部分,分别代表颜色,Track里的Clip类型,Track自身能够绑定的资源类型

[TrackColor(0.855f,0.855f,0.855f)]
[TrackClipType(typeof(TransformTweenClip))]
[TrackBindingType(typeof(Transform))]
public class TransformTweenTrack : TrackAsset
{
}

给Data添加行为

数据和行为,都会在Data里,所以Data才叫做PlayableBehaviour

可以发现Data里我们可以重写很多生命周期方法

image-20220920182244591

我们重写其中的ProcessFrame,很明显是一个每帧都会调用的方法

[System.Serializable]
public class TransformTweenBehaviour : PlayableBehaviour
{
    public Transform endLocation;

    public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    {
        var actor = playerData as Transform;
        if (actor == null) 
            return;
        actor.position = Vector3.Lerp(actor.position, endLocation.position, info.deltaTime);
    }
}

预览

首先我们需要一个Playable Director

image-20220920183150361

随便创建一个Timeline然后拖进去

这里是我们需要移动的目标物体

image-20220920183221158

表面上Cube是绑定在Track上的,实则不然,我们观察一下Playable Director就可以发现问题

image-20220920183252882

所以资源实际上还是记录在了Mono上

点击Clip我们可以选择我们的EndLocation

image-20220920183343661

点击播放

1

但实际上目前的移动逻辑是有一定的问题的

混合轨道

MixerPlayable本质上他也是一个Clip,但是他就特殊在,他是铺满整个轨道的一个Playable,因此他可以在任何时候对轨道进行处理(所谓混合也就是在恰当的逻辑时间点进行逻辑处理而已

image-20220920184843900

Mixer的逻辑很蠢,他会在每一帧遍历所有的Clip,然后会你可以获取每一个Clip在当前帧的权重,根据这权重值,你可以自己进行逻辑处理

比如时刻A,Clip2的权重就是1,而Clip、Clip3的权重都是0

而时刻B,Clip的权重为0,Clip2、Clip3的权重可能都是0.5

image-20220920185225811

Mixer只是把权重你告诉你,至于具体你想怎么混合,那是你自己的事情

修改移动行为

创建混合轨道前,我们先给移动行为添加一些属性

一个新的Duration

public class TransformTweenClip : PlayableAsset, ITimelineClipAsset
{
    [NonSerialized]
    public float duration;

    public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
    {
        ...
        clone.duration = duration;
        ...
    }
}

每帧逻辑也要修改,GetTime获取的是当前Playable的运行时间,是当前这个Clip的,每一个Clip都会从0开始,最长时间等于duration

[System.Serializable]
public class TransformTweenBehaviour : PlayableBehaviour
{
    public Transform endLocation;
    public float duration;
    public float weight;
    
    public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    {
        var actor = playerData as Transform;
        if (actor == null) 
            return;
        actor.position = Vector3.Lerp(actor.position, endLocation.position, info.deltaTime * weight);
    }
}

创建混合轨道

他本质上也是一个Clip,所以也需要创建一个数据(Behaviour)

public class TransformTweenMixerBehaviour : PlayableBehaviour
{
}

但这次,我们是在Track里面创建他,固定写法,同时,在创建时,我们给所有的Clip的Duration赋值

[TrackColor(0.855f,0.855f,0.855f)]
[TrackClipType(typeof(TransformTweenClip))]
[TrackBindingType(typeof(Transform))]
public class TransformTweenTrack : TrackAsset
{
    public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
    {
        foreach (var clip in GetClips())
        {
            var clipAsset = clip.asset as TransformTweenClip;
            if (clipAsset != null)
                clipAsset.duration = (float) clip.duration;
        }
        return ScriptPlayable<TransformTweenMixerBehaviour>.Create(graph, inputCount);
    }
}

由此不难看出Mixer是优先于Playable创建的

然后修改Mixer的逻辑

public class TransformTweenMixerBehaviour : PlayableBehaviour
{
    public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    {
        var transform = playerData as Transform;
        if (transform == null) 
            return;

        // 一共有几个Clip
        var count = playable.GetInputCount();
        // 每一个Clip的权重
        for (int i = 0; i < count; i++)
        {
            var weight = playable.GetInputWeight(i);
            var inputPlayable = (ScriptPlayable<TransformTweenBehaviour>) playable.GetInput(i);
            var behaviour = inputPlayable.GetBehaviour();
            if (behaviour != null)
                behaviour.weight = weight;
        }
    }
}

1