Playable API

参考链接:


前言

Playable API的由来
新版的Unity鼓励大家用Animator组件(Mecanim),但依然有很多人在使用Legacy动画系统Animation组件。因为Animation组件对于程序来说比较直观,但使用Animation组件,就意味着不可用使用Mecanim动画系统的一些高级功能,例如:动画重定向、Blend Tree等。

所以,设计Playable API的一个目的就是为了代替Legacy动画系统,同时继续使用动画重定向、BlendTree和AvatarMask等功能
,通过Playable API,可以弃用Unity的状态机系统,定制适配于自己项目的动画系统,但是又保留Unity的高级动画特性。

再者,Playable API可以更直接的访问底层动画系统的接口。
最后,我们可以通过Playable来扩展Timeline的功能。

总结下来,其核心功能如下:
在这里插入图片描述

在Unity底层,驱动Playable Graph的实际上依然是Animator组件,所以在使用时需要你传一个Animator组件给Playable Graph。但是你完全可以像使用Animation组件一样使用Playable。


什么是Playables API
其实就是一组API,它可以用于创建一个树形结构的图,这个图叫做PlayableGraph,如下图所示,可以用来存储数据信息(比如AnimationClip). Playables API支持动画、音频和脚本,提供了用脚本与动画和音频系统进行交互的能力:
在这里插入图片描述

Although the Playables API is currently limited to animation, audio, and scripts, it is a generic API that will eventually be used by video and other systems.


理解Playable与Playable API
首先要区分PlayablePlayable API的关系,Playable API就是Animator Controller底层实现的方式,相当于Unity底层关于动画的API,现在由Unity暴露了出来,这些Playable和PlayableGraph相关的API都属于PlayableAPI。而Playable就不同了,字面意思理解,Playable就是可以播放的东西,比如说,一个对AnimationClip的Wrapper,就是一个AnimationPlayable;在PlayableGraph的层面上理解,上面PlayableGraph图中的一个个小长方形,代表的就是Playable,意思是Graph里面的节点;而在代码层面上理解,Playable就是一个Struct,它遵循IPlayable接口,这里用Struct而不用Class是为了保证高效性,因为不会在堆上分配


Playable vs Animation
Unity本身提供了提供给了用户一套进行动画编辑的工具,也就是Animator对应的动画状态机系统(Mecanim),也就是说,Unity内部人员基于PlayableGraph和Playable开发了Mecanim这一套动画系统,而PlayableGraph不仅仅只可用于动画,它可以代表一系列数据的流动结构,包括动画、音频和脚本等,未来还有可能拓展到视频等内容。(These graphs represent a flow of data, indicating what each node produces and consumes. In addition, a single graph is not limited to a single system. A single graph may contain nodes for animation, audio, and scripts)

使用Playables API的优点

  • Runtime创建一个AnimationClip,使用在场景中的一个对象上
  • 简单播放一段动画,而不需要创建和使用AnimatorController,也不会有AnimatorController的相关消耗
  • 动态的Blend不同的AnimationClip,而且可以每帧都可以调整二者的权重
  • 可以在Runtime创建PlayableGraph,添加和删除playable nodes,非常灵活


关于Playable

Unity Manual里提到,Playable就是一个C#的struct,这个结构体,继承于IPlayable接口,同样,Playable Output也是一个C#的struct,继承于IPlayableOutput接口,用于表示PlayableGraph的output

Playable可以分为三种类型,Animation、Audio和Scriptable类型,如下图所示,其实就是这些可播放的资源的Wrapper,注意这里的箭头不是继承关系,它只是范围的从属关系:
在这里插入图片描述



The PlayableGraph

PlayableGraph 定义了一系列的playable outputs,这些outputs是与GameObject或Component联系到一起的,PlayableGraph也记录了这些playable之间的连接关系。

如下图所示是一个PlayableGraph的样子,这里需要从右往左读,这里先不去寄希望于理解它的意思,现在只是了解一下,想要看到这个节点图,需要安装对应的playablegraph visualizer的插件。
在这里插入图片描述


怎么在Unity2020的版本里安装这个插件
顺便提一下这个事情,因为Unity太鸡贼了,它把这个插件在Package Manager里隐藏了,为什么隐藏,因为这个功能是Unity里一个人做的,但是这个人离职了,但是这个功能还没做完,所以这是一个还在测试中的,被搁置的功能,Unity就把它隐藏了。

但是要想手动安装它,也有解决办法,就是把下面这行加到你自己的manifest.json文件里就行

"com.unity.playablegraph-visualizer": "0.2.1-preview.3"


ScriptPlayable and PlayableBehaviour

用户可以定义自己的Playable,但需要统一继承于PlayableBehaviour :

public class MyCustomPlayableBehaviour : PlayableBehaviour {
    
    
	// Implementation of the custom playable behaviour // Override PlayableBehaviour methods as needed 
}

自定义的ScriptableBehaviour,其类型都是ScriptPlayable,这里的T就是自己定义的Behavior类的名字
To use a PlayableBehaviour as a custom playable, it also must be encapsulated within a ScriptPlayable<> object. If you don’t have an instance of your custom playable, you can create a ScriptPlayable<> for your object by calling:

ScriptPlayable<MyCustomPlayableBehaviour>.Create(playableGraph);//这一行应该是在graph里创建一个ScriptablePlayable?

If you already have an instance of your custom playable, you can wrap it with a ScriptPlayable<> by calling:

MyCustomPlayableBehaviour myPlayable = new MyCustomPlayableBehaviour();
ScriptPlayable<MyCustomPlayableBehaviour>.Create(playableGraph, myPlayable);

In this case, the instance is cloned before it is assigned to the ScriptPlayable<>. As it is, this code does exactly the same as the previous code; the difference is that myPlayable can be a public property that would be configured in the inspector, and you can then set up your behaviour for each instance of your script.

You can get the PlayableBehaviour object from the ScriptPlayable<> by using the ScriptPlayable .GetBehaviour() method



举个例子

使用Playable API实现类似Animation组件的功能

上面的内容有点晦涩难懂,这里举个例子,使用Playable APIs,取代原本的Animator Controller组件,让其功能很像老版的Legacy的Animation组件,但是又支持BlendTree、动画重定向等功能。

先在场景里面放好一个模型,然后给他挂载一个Animator组件,指定其Avatar,但是不给Animator指定原本有的Animator Controller文件,如下图所示:
在这里插入图片描述

然后写一个脚本Test.cs:

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Animations;

// 确保同物体上有Animator组件,但是不需要给该Animator赋值Animator Controller
[RequireComponent(typeof(Animator))]
public class Test : MonoBehaviour
{
    
    
    public AnimationClip clip;//注意这里要像使用Animation组件一样,从外部指定AnimationClip进来
    PlayableGraph playableGraph;

    void Start()
    {
    
    
        // 创建一个PlayableGraph
        playableGraph = PlayableGraph.Create("PlayAnimationSample");
        // 基于playableGraph创建一个动画类型的Output节点,名字是Animation,目标对象是物体上的Animator组件
        AnimationPlayableOutput playableOutput = AnimationPlayableOutput.Create(playableGraph, "Animation", GetComponent<Animator>());
        // 基于本组件已经索引好的AnimationClip创建一个AnimationClipPlayable
        AnimationClipPlayable clipPlayable = AnimationClipPlayable.Create(playableGraph, clip);
        // 将playable连接到output
        playableOutput.SetSourcePlayable(clipPlayable);
        // 播放这个graph
        playableGraph.Play();
    }

    void OnDisable()
    {
    
    
        // 销毁所有的Playables和PlayableOutputs
        playableGraph.Destroy();
    }
}

最后给模型挂载这个脚本,然后像用Animation组件一样拖进去对应的AnimationClip后,点击Play,就可以看到角色在播放拖拽进去的clip了:
在这里插入图片描述

回顾一下这里的操作,这里主要是五步:

  • 创建自己的动画状态机,名字叫做PlayableGraph
  • 建立输出,类型为Output Playable,动画模块需要传入对应的Animator(实际上底层好像还是用Animator做的)
  • 建立输入,类型为AnimationClip
  • 把输入跟输出连接起来
  • 状态机点击Play

简化动画playables的创建和播放
代码如下所示:

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Animations;

[RequireComponent(typeof(Animator))]
public class PlayAnimationUtilitiesSample : MonoBehaviour
{
    
    
    public AnimationClip clip;
    PlayableGraph playableGraph;
    void Start()
    {
    
    
	    // 迅速创建一个graph,省却了之前创建输入的AnimationClipPlayable和输出的AnimationPlayableOutput,以及二者的绑定过程
        AnimationPlayableUtilities.PlayClip(GetComponent<Animator>(), clip, out playableGraph);
    }

    void OnDisable()
    {
    
    
        // Destroys all Playables and Outputs created by the graph.
        playableGraph.Destroy();
    }
}


PlayableBehaviour

为了实现可以在Graph中显示的编程节点,需要创建脚本继承于PlayableBehaviour,其框架如下所示:

namespace UnityEngine.Playables
{
    
    
    // PlayableBehaviour is the base class from which every custom playable script derives.
    // 它里面提供了很多回调函数,可以在回调函数里面捕捉Graph的事件
    [RequiredByNativeCode]
    public abstract class PlayableBehaviour : IPlayableBehaviour, ICloneable
    {
    
    
        public PlayableBehaviour();

        public virtual object Clone();
        
        // 可以看出来这些接口都是回调函数
     
        public virtual void OnBehaviourDelay(Playable playable, FrameData info);// Obsolete
        // 当Playable的play state变成PlayState.Paused的时候,这个方法被调用。
        public virtual void OnBehaviourPause(Playable playable, FrameData info);
        // 当Playable的Playstate变成PlayState.Playing的时候,这个方法被调用。
        public virtual void OnBehaviourPlay(Playable playable, FrameData info);
        // 当拥有这个PlayableBehavior的PlayableGraph开始的时候, 这个方法被调用。
        public virtual void OnGraphStart(Playable playable);
        // 当拥有这个PlayableBehavior的PlayableGraph停止的时候, 这个方法被调用。        
        public virtual void OnGraphStop(Playable playable);
		// 当拥有这个PlayableBehavior的Playable被创建的时候, 这个方法被调用。(Playable跟节点一样,可以拥有父节点)
        public virtual void OnPlayableCreate(Playable playable);
        // 当拥有这个PlayableBehavior的Playable被销毁的时候, 这个方法被调用。(Playable跟节点一样,可以拥有父节点)
        public virtual void OnPlayableDestroy(Playable playable);

        public virtual void PrepareData(Playable playable, FrameData info);
        //会在PlayableGraph的ProepareFrame阶段被调用
        public virtual void PrepareFrame(Playable playable, FrameData info);
        //会在PlayableGraph的ProcessFrame阶段被调用
        public virtual void ProcessFrame(Playable playable, FrameData info, object playerData);
  

使用基于PlayableBehaviour的ScriptablePlayable实现AnimationMixerPlayable中各个Playable的权重调整

AnimationMixerPlayable相当于一个容器,用来存放多个AnimationPlayable,现在举一个例子,这个例子会用ScriptablePlayable来在每帧动态调整两个AnimationClip的权重,实现AnimationBlending的效果

代码如下:

using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;

// 这个节点就是在Prepareframe阶段,设置了权重
public class BlenderPlayableBehaviour : PlayableBehaviour
{
    
    
    public AnimationMixerPlayable mixerPlayable;// 相当于BlendTree

    public override void PrepareFrame(Playable playable, FrameData info)
    {
    
    
        // blend值会随着时间变化,逐渐在[0,1]范围内来回取值
        float blend = Mathf.PingPong((float)playable.GetTime(), 1.0f);

        mixerPlayable.SetInputWeight(0, blend);//0号clip 权重为blend
        mixerPlayable.SetInputWeight(1, 1.0f - blend);//1号clip 权重为1 - blend

        base.PrepareFrame(playable, info);//调用基类的PrepareFrame函数
    }
}

public class PlayableBehaviourSample : MonoBehaviour
{
    
    
    PlayableGraph m_Graph;
    public AnimationClip clipA;
    public AnimationClip clipB;

    // Use this for initialization
    void Start()
    {
    
    
        // Create the PlayableGraph.
        m_Graph = PlayableGraph.Create();

        // Add an AnimationPlayableOutput to the graph.
        var animOutput = AnimationPlayableOutput.Create(m_Graph, "AnimationOutput", GetComponent<Animator>());

        // Add an AnimationMixerPlayable to the graph.
        var mixerPlayable = AnimationMixerPlayable.Create(m_Graph, 2, false);

        // Add two AnimationClipPlayable to the graph.
        var clipPlayableA = AnimationClipPlayable.Create(m_Graph, clipA);
        var clipPlayableB = AnimationClipPlayable.Create(m_Graph, clipB);

        // Add a custom PlayableBehaviour to the graph.
        // This behavior will change the weights of the mixer dynamically.
        var blenderPlayable = ScriptPlayable<BlenderPlayableBehaviour>.Create(m_Graph, 1);
        blenderPlayable.GetBehaviour().mixerPlayable = mixerPlayable;//注意一下写法,需要调用GetBehaviour()

        // Create the topology, connect the AnimationClipPlayable to the
        // AnimationMixerPlayable.  Also add the BlenderPlayableBehaviour.
        m_Graph.Connect(clipPlayableA, 0, mixerPlayable, 0);
        m_Graph.Connect(clipPlayableB, 0, mixerPlayable, 1);
        m_Graph.Connect(mixerPlayable, 0, blenderPlayable, 0);

        // Use the ScriptPlayable as the source for the AnimationPlayableOutput.
        // Since it's a ScriptPlayable, also set the source input port to make the
        // passthrough to the AnimationMixerPlayable.
        animOutput.SetSourcePlayable(blenderPlayable);
        animOutput.SetSourceInputPort(0);

        // Play the graph.
        m_Graph.Play();
    }

    private void OnDestroy()
    {
    
    
        // Destroy the graph once done with it.
        m_Graph.Destroy();
    }
}

如下所示,是这段代码对应的PlayableGraph:
在这里插入图片描述


每一个Playable都会有PrepareFrame函数
当PlayableGraph Play的时候,会遍历到,每一个PlayableOutput,PlayableOutput可以理解为是Graph里的玩家,在这个遍历过程中,每个Playable的PrepareFrame函数都会被调用,这个函数是为了"Prepare itself for the next evaluation",在这个阶段,每个Playable可以
modify its children (either by adding new inputs or by removing some of them),也可以在runtime添加更多的child branches

在PrepareFrame阶段完成后,所有的PlayableOutputs就回来接管这个过程,来处理这些准备好的数据,举个例子,对于AnimationPlayableOutput,在这个阶段,就是由Animator来处理graph的(所以我理解Animator就是一种AnimationPlayableOutput?),再比如说对于ScriptPlayableOutput,那么就是在它的PlayableBehaviour.ProcessFrame


PlayableBehaviour.SetTraversalMode
关于PlayableBehaviour,有一个enum叫PlayableTraversalMode,如下所示:

namespace UnityEngine.Playables
{
    
    
    // Traversal mode for Playables.
    public enum PlayableTraversalMode
    {
    
    
        // Summary: Causes the Playable to prepare and process it's inputs when demanded by an output.
        // 当一个编程Playable是mix模式时,会在它被一个output需要的时候,让它去prepare和process它的inputs
        Mix = 0,
        // Summary:
        //     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.
        // 当一个编程Playable是passthrough模式时,它好像只会把第n个input接口里传递的东西直接处理传给PlayableOutput
        // 其他的就不处理了,这里的n是由SetSourceOutputPort来指定的
        Passthrough = 1
    }
}


AnimationLayerMixerPlayable 使用例子

前面的AnimationMixerPlayable是对两个AnimationClip进行混合,而这里就是把两个AnimationLayer进行混合,示例代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Audio;
using UnityEngine.Playables;


/// <summary>
/// 这篇程序可以参考官方 https://docs.unity3d.com/ScriptReference/Playables.PlayableGraph.Connect.html
/// </summary>
[RequireComponent(typeof(Animator))]
public class Playable_RotateMoveCube : MonoBehaviour
{
    
    
	// 使用两个AnimationPlayable,混合得到MixerPlayable
    public AnimationClip animClipA;
    public AnimationClip animClipB;

    private PlayableGraph graph;
    private AnimationLayerMixerPlayable mixer;
    private AnimationPlayableOutput output;


    void Start()
    {
    
    
        graph = PlayableGraph.Create("RotateMoveCube");
        output = AnimationPlayableOutput.Create(graph, "Animation", GetComponent<Animator>());
        // inputCount 可以接几个playable
        mixer = AnimationLayerMixerPlayable.Create(graph, 3);
        output.SetSourcePlayable(mixer);

        AnimationClipPlayable clipPlayA = AnimationClipPlayable.Create(graph, animClipA);
        AnimationClipPlayable clipPlayB = AnimationClipPlayable.Create(graph, animClipB);
        // sourceOutputPort 默认为0
        graph.Connect(clipPlayA, 0, mixer, 0);
        graph.Connect(clipPlayB, 0, mixer, 1);
        // weight填0.5f只会有一半效果,和我理解的a + b + c = 1不同
        mixer.SetInputWeight(0, 1f);
        mixer.SetInputWeight(1, 1f);
        graph.Play();
    }

    public void ClickClose()
    {
    
    
        // ...这里暂无判空等检测
        graph.Disconnect(mixer, 0);
        graph.Disconnect(mixer, 1);

        graph.DestroyPlayable(mixer);
        graph.DestroyOutput(output);

        // cube位置会停在销毁的时刻
    }

    public void ClickStopB()
    {
    
    
        // ...这里暂无判空等检测
        mixer.SetInputWeight(1, 0f);

        //graph.Disconnect(mixer, 1);
    }

    void OnDisable()
    {
    
    
        graph.Destroy();
    }

}

AnimationPlayableMixer和AnimationLayerPlayableMixer
看名字应该大概能知道,AnimationPlayableMixer是用于混合多个AnimationClip,感觉像是Blend Tree,而AnimationLayerPlayableMixer是混合多个AnimationLayer的,其接口函数如下:
在这里插入图片描述



什么是Animation Pose

插件提供的Graph View下,赫然看到这个黄色的Animation Pose节点,但是没有官方解释这个事情,如下图所示:
在这里插入图片描述

https://forum.unity.com/threads/what-is-animation-pose-playable.895379/上做了一些相关的介绍,大概意思是说,AnimationPose是Unity的Native Code里的一种Playable,它记录的是人物的Pose信息,这种Playable没有被暴露出来,有人说它是用于动画A向B转换到一半的时候,突然要转向C,这个时候就用Animation Pose记录A到B的中间转态的姿势的,也有人说是用于记录Default Animation Pose的,这样当有的骨头不识别transform时,就播放默认的Animation Pose,总之,应该是记录Animation的姿势的节点,但是没有被暴露出来



后面的内容就是Playable API与Unity提供的多线程结合的部分了,我这一块也没有完全理解,现在只是留下一些记录和整理

Animation C# Jobs给Playable增加的API

参考链接:https://blogs.unity3d.com/2018/08/27/animation-c-jobs/
之前提到的,playable就是graph里的一个节点,它可以是animaiton playable,代表动画节点,也可以是scriptable playable,代表编程节点,还有类似的audio playable。

这里基于Unity的多线程Job System,添加了用于多线程的动画相关的Playable的类,具体是以下三个struct:

  • AnimationScriptPlayable
  • IAnimationJob
  • AnimationStream

AnimationScriptPlayable
这也是一个Playable,可以作为节点放在graph里,它里面存储的是一个animation job,它的作用相当于graph和job的中间接头人(proxy)

而这里的job,是由用户自定义的struct,需要继承于IAnimationJob接口,job应该可以理解为一个函数指针,是Unity的job system里的概念


Example
The example below is like the “Hello, world!” of Animation C# Jobs. It does nothing at all, but it allows us to see how to create an AnimationScriptPlayable with an animation job:

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Animations;
using UnityEngine.Experimental.Animations; 

// 实现继承的两个接口
public struct AnimationJob : IAnimationJob
{
    
    
    public void ProcessRootMotion(AnimationStream stream)
    {
    
    
    }
 
    public void ProcessAnimation(AnimationStream stream)
    {
    
    
    }
}
 
[RequireComponent(typeof(Animator))]
public class AnimationScriptExample : MonoBehaviour
{
    
    
    PlayableGraph m_Graph;
    AnimationScriptPlayable m_ScriptPlayable;
 
    void OnEnable()
    {
    
    
        // Create the graph.
        m_Graph = PlayableGraph.Create("AnimationScriptExample");
 
        // Create the animation job and its playable.
        var animationJob = new AnimationJob();
        // 创建graph与job之间的proxy
        m_ScriptPlayable = AnimationScriptPlayable.Create(m_Graph, animationJob);
 
        // Create the output and link it to the playable.
        var output = AnimationPlayableOutput.Create(m_Graph, "Output", GetComponent<Animator>());
        output.SetSourcePlayable(m_ScriptPlayable);
    }
 
    void OnDisable()
    {
    
    
        m_Graph.Destroy();
    }
}

IAnimationJob是一个interface,定义了两个接口函数:

namespace UnityEngine.Animations
{
    
    
    [JobProducerType(typeof(ProcessAnimationJobStruct<>))]
    [MovedFrom("UnityEngine.Experimental.Animations")]
    public interface IAnimationJob
    {
    
    
        //
        // Summary:
        //     Defines what to do when processing the animation.
        //
        // Parameters:
        //   stream:
        //     The animation stream to work on.
        void ProcessAnimation(AnimationStream stream);
        //
        // Summary:
        //     Defines what to do when processing the root motion.
        //
        // Parameters:
        //   stream:
        //     The animation stream to work on.
        void ProcessRootMotion(AnimationStream stream);
    }
}


AnimationStream and the handles

AnimationStream可以:

  • 给graph中data的flow信息,应该意味着playables之间的连接关系
  • 可以获取Animator组件animate的所有值
public struct AnimationStream
{
    
    
    public bool isValid {
    
     get; }
    public float deltaTime {
    
     get; }
 
    public Vector3 velocity {
    
     get; set; }
    public Vector3 angularVelocity {
    
     get; set; }
 
    public Vector3 rootMotionPosition {
    
     get; }
    public Quaternion rootMotionRotation {
    
     get; }
 
    public bool isHumanStream {
    
     get; }
    public AnimationHumanStream AsHuman();
 
    public int inputStreamCount {
    
     get; }
    public AnimationStream GetInputStream(int index);
}

It isn’t possible to have a direct access to the stream data since the same data can be at a different offset in the stream from one frame to the other (for example, by adding or removing an AnimationClip in the graph). The data may have moved, or may not exist anymore in the stream. To ensure the safety and validity of those accesses, we’re introducing two sets of handles: the stream and the scene handles, which each have a transform and a component property handle.

在我理解,AnimationStream就是Graph里面的nodes(即Playable)之间的联通关系的数据,也就是代表的那根连线,如果想要修改和读取这一块的数据,需要借助AnimationScriptPlayable,用它创建一个job,用这个job去改Stream的信息,注意这个Job需要遵循IAnimationJob接口

The stream handles
主要是用来保护stream读取数据的合法性的,如果报错会抛出C#异常,有两种stream handles:

  • TransformStreamHandle
  • PropertyStreamHandle.

AnimationHumanStream
当AnimationStream的模型是Humanoid类型时,可以通过其接口转化为AnimationHumanStream:
在这里插入图片描述
转化为AnimationHumanStream之后,就有了对应为Human的各种动画数据的读取操作,如下图所示是部分AnimationHumanStream的接口函数:
在这里插入图片描述



其他的相关类与API整理

AnimationPlayableUtilities

此类主要是为了简化API操作而做的,一共提供了五个Static方法:

// 根据参数,直接在Grapgh里面添加node,并设置好连接关系,然后Play
public static void Play(Animator animator, Playables.Playable playable, Playables.PlayableGraph graph);

// 创建一个graph,同时根据输入的RuntimeAnimatorContoller返回一个创建的AnimatorControllerPlayable
public static Animations.AnimatorControllerPlayable PlayAnimatorController(Animator animator, RuntimeAnimatorController controller, out Playables.PlayableGraph graph);

// 创建一个graph,根据输入的clip创建对应的AnimationClipPlayable,并播放这个graph
public static Animations.AnimationClipPlayable PlayClip(Animator animator, AnimationClip clip, out Playables.PlayableGraph graph);

// 创建一个graph,同时返回一个有inputCount个Layer的AnimationLayerMixerPlayable(可能不全是Layer)
public static Animations.AnimationLayerMixerPlayable PlayLayerMixer(Animator animator, int inputCount, out Playables.PlayableGraph graph);

// 创建一个graph,同时返回一个有inputCount个AnimationClipPlayable的AnimationMixerPlayable(可能不全是AnimationClipPlayable)
public static Animations.AnimationMixerPlayable PlayMixer(Animator animator, int inputCount, out Playables.PlayableGraph graph);

FrameData

FrameData是一个结构体,它存储了一个Playable在Playable.PrepareFrame等函数里的接受到的frame information:

public abstract class PlayableBehaviour : IPlayableBehaviour, ICloneable
{
    
    
    public PlayableBehaviour();
    ...
    public virtual void PrepareData(Playable playable, FrameData info);
	public virtual void PrepareFrame(Playable playable, FrameData info);
	public virtual void ProcessFrame(Playable playable, FrameData info, object playerData);
}

FrameData有以下Property:

  • deltaTime: 帧间时间
  • effectiveParentSpeed: graph遍历阶段,到该Playable的parent Playable时的累积速度(accumulated speed)
  • effectivePlayState: 播放到当前Playable的累积播放状态(play state)
  • effectiveSpeed: 播放到当前Playable的累积速度
  • effectiveWeight: 播放到当前Playable的累积权重
  • evaluationType: PlayableGraph.PrepareFrame函数被调用的方式
  • frameId: 当前帧的id
  • output: The PlayableOutput that initiated this graph traversal.(Output不是被发起的吗?)
  • seekOccurred: Indicates that the local time was explicitly set.
  • timeHeld: bool型变量,表示local time不会再增长,因为它播放完了,而且extrapolation mode设置为了Hold,应该就是播放一次的意思
  • timeLooped: bool型变量,表示local time wrapped,而且extrapolation mode设置为了Loop,应该是循环播放的意思
  • weight: 当前playable的权重

关于EvaluationType
该选项会决定一个Graph以何种方式被执行:

  • Evaluate:graph会以调用PlayableGraph.Evaluate([DefaultValue(“0”)] float deltaTime)函数的方式进行Update
  • Playback:graph会在runtime调用PlayableGraph.Play函数之后开始运行,然后会随Update或FixedUpdate函数的频率进行Update

Notification

遵循INotification接口,具体的例子等会补充

Properrties

  • id

Constructors

  • public Notification(string name);


一些更深入的理解

Playable

Playable就是graph里互相连接的节点,每一个Playable可以设置其每一个子Playable的"weight"或"infulence"

一个PlayableGraph可以有多个outputs,它们也叫players,它们均继承于IPlayableOutout接口,每个PulayableOutput都会取其source playable的结果,然后把它应用到场景的一个物体上,举个例子,动画的 AnimationPlayableOutput会与两个东西进行连接,一个是Graph里的SourcePlayable,一个是场景里的物体(物体上的Animator),当graph is played,在graph evaluation过程中会算出一个AnimationPose,然后把它应用在Animator上

The ScriptPlayable 是一种custom的脚本做成的Playable(感觉AnimationPlayable这些的本质应该也是ScriptablePlayable),T需要derived from PlayableBehaviour,它们可以在graph evaluation阶段(在PlayableBehaviour.PrepareFrame and PlayableBehaviour.ProcessFrame函数中)写入相关的参数。

A good example of a ScriptPlayable is the TimelinePlayable which is
controlling the Timeline graph. It creates and links together the
playables in charge of the tracks and the clips.

When a PlayableGraph is played, each PlayableOutput will be traversed. During this traversal, it will call the PrepareFrame method on each Playable. This allows the Playable to “prepare itself for the next evaluation”. It is during the PrepareFrame stage that each Playable can modify its children (either by adding new inputs or by removing some of them). This enables Playable to “spawn” new children branches in the Playable tree at runtime. This means that Playable trees are not static structures. They can adapt and change over time.

Once the preparation is done, the PlayableOutputs are in charge of processing the result, that’s why they are also called “players”. In the case of an AnimationPlayableOutput, the Animator is in charge of processing the graph. And in the case of a ScriptPlayableOutput, PlayableBehaviour.ProcessFrame will be called on each ScriptPlayable.(所以ScriptPlayableOutput的输出执行方式就是执行ProcessFrame函数?)


PlayableAsset

一种asset文件类,可以用于在runtime instantiate出来一个Playable

Properties:

  • public double duration: playable对应资源的播放时间(seconds)
  • public IEnumerable outputs:A description of the outputs of the instantiated Playable.

公有函数

// 会根据owner,在graph里创建一个Playable,返回这个Playable对应的Root Playable(因为这个可能是一个Tree型的Playable)
public Playables.Playable CreatePlayable(Playables.PlayableGraph graph, GameObject owner);

未经作者允许,本文谢绝转载,谢谢各位阅读,欢迎批评指正。
文章链接(防止爬虫):https://blog.csdn.net/alexhu2010q/article/details/113921119

猜你喜欢

转载自blog.csdn.net/alexhu2010q/article/details/113921119
API