微信号:Unity-GreaterChina

介绍:Unity官方开发者平台,分享最前沿的技术文章和开发经验,精彩的Unity活动和社区相关信息.

详解Animation C# Jobs

2018-08-30 12:12 Unity

Unity 2018.2中,Animation C# Jobs通过使用在Unity 2018.1中推出的C# Job System扩展了动画Playables。在实现动画系统时,Animation C# Jobs让开发者能自由创建原始解决方案,同时使用安全的多线程代码来提高性能。


Animation C# Jobs是一个底层API,使用它需要对Playable API有充分的了解。使用Animation C# Jobs,你可以编写在PlayableGraph中用户定义位置调用的C#代码。用户自定义的C#脚本可以修改经过PlayableGraph的动画流。


Animation C# Jobs针对有兴趣将Unity动画系统扩展更多功能的开发人员,使用它可以充分利用现代多核硬件的强大功能。对于在主线程上C#脚本性能消耗较大的项目,可以并行化一些动画任务,让性能得到宝贵的提升。


功能

Animation C# Jobs拥有以下功能:

  • 全新Playable节点:AnimationScriptPlayable

  • 控制PlayableGraph中的动画数据流

  • 多线程C#代码


特别提示:

Animation C# Jobs目前仍是一个实验性功能,具体API在UnityEngine.Experimental.Animations。该API之后会根据用户反馈改进。


适用场景

Animation C# Jobs适用以下示例场景:

例如:你想给全新的龙角色加入一个足部锁定功能。可以使用常规MonoBehaviour对其进行编码,但在动画播放结束之前所有代码都将在主线程中运行。如果使用Animation C# Jobs,你可以编写算法并将直接在PlayableGraph中的自定义Playable节点中使用它,代码将于PlayableGraph处理期间在独立线程中运行。

 

你还可以不用特地设置龙尾部的动画,通过Animation C# Jobs将能完美设置程序化计算动作的功能。Animation C# Jobs还能让你编写具体的LookAt算法。例如:帮助定位龙颈部的10块骨骼。

 

另一个很好的示例是制作自定义的动画混合器。假设你有一些具体需要的内容:该节点会从一次输入中获取位置,从另一次输入获取旋转角度,从第三个节点获取缩放值,然后将它们混合为单个动画流。Animation C# Jobs能帮助你实现创意的功能,并根据特定需求进行构建。


获取示例

在深入了解如何使用Animation C# Jobs API之前,让我们参考一些示例,它们将展示了使用Animation C# Jobs可以实现什么。

 

所有示例都可以访问Animation Jobs Samples的GitHub页面获取。安装完成后,示例中自带场景存放在Scenes目录下。


下载地址:

https://github.com/Unity-Technologies/animation-jobs-samples


示例解析

下面我们将为大家一一解析这些示例。


1

LookAt

  

LookAt是一个非常简单的示例,它将骨骼朝向一个效应器。如下图所示,你可以了解它如何应用于3D Game Kit资源包中的角色对象。



2

TwoBoneIK 

TwoBoneIK实现了一个简单的双骨骼IK算法,它能应用于三个连续关节。例如:人类手臂或腿。演示中的角色使用通用Humanoid Avatar制作。



3

FullbodyIK 

FullbodyIK展示如何修改Humanoid Avatar上的数值。例如:目标、提示、观察对象和身体旋转等。下图示例使用了动画流的人形实现。



4

Damping 

Damping展示了实现可应用于动物尾巴或人类马尾辫的阻尼算法,说明了如何生成程序化动画。



5

SimpleMixer 

SimpleMixer是一个非常基础的动画混合器。它接收二个输入流,然后将它们基于混合数值结合起来,功能类似AnimationMixerPlayable 。



6

WeightedMaskMixer 

WeigthedMaskMixer示例是一个较高级的动画混合器。它接收二个输入流,然后将它们基于权重遮罩混合起来,该权重遮罩会定义如何混合各个关节。例如:你可以播放经典闲置动画,然后从另一动画剪辑接收手臂动画。或是通过在脊骨上连续应用较高的权重来平滑上身动画的混合效果。


API

Animation C# Jobs由Playable API提供支持。它带来了三个新结构:AnimationScriptPlayable、IAnimationJob和AnimationStream。


AnimationScriptPlayable和IAnimationJob

AnimationScriptPlayable是一个全新的动画Playable,和其它Playable一样,它可以添加到PlayableGraph的任何位置。它包含一个动画作业,能够作为PlayableGraph和作业之间的代理。该作业是个实现IAnimationJob的用户定义结构。

 

Playable的常见作业流程会输入动画流并混合流中的结果。该动画流程分为二个通道,每个通道都自带IPlayableJob中的回调函数:

  • ProcessRootMotion会处理Root Transform的动作,它会在ProcessAnimation前调用,并根据Animator剔除模式确定是否调用ProcessAnimation。

  • ProcessAnimation用于除Root Motion根动画外的所有对象。


下面示例是一个非常基础的Animation C# Jobs。它会帮助我们了解如何使用动画作业创建AnimationScriptPlayable。

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()

    {

        // 创建视图

        m_Graph = PlayableGraph.Create("AnimationScriptExample");

 

        // 创建Animation job和Playable

        var animationJob = new AnimationJob();

        m_ScriptPlayable = AnimationScriptPlayable.Create(m_Graph, animationJob);

 

        // 创建输出结果并将结果链接到Playable上

        var output = AnimationPlayableOutput.Create(m_Graph, "Output", GetComponent<Animator>());

        output.SetSourcePlayable(m_ScriptPlayable);

    }

 

    void OnDisable()

    {

        m_Graph.Destroy();

    }

}


作为IAnimationJob方法的参数传递的流,它是在每个处理通道中将要处理的对象。

 

默认情况下,所有AnimationScriptPlayable输入会被处理。在仅有一个输入即后期处理作业的情况下,该流会包含已处理输入的结果。在有多个输入即混合作业的情况下,最好是手动处理输入。

 

为了实现手动处理,AnimationScriptPlayable.SetProcessInputs(bool)将启用或禁用输入的处理通道。为了触发输入的处理过程并获取手动模式中的结果流,请调用AnimationStream.GetInputStream()。


AnimationStream和句柄

通过AnimationStream,开发者可以访问视图中Playable之间流动的数据。它还可以访问由Animator组件设置动画的所有数值。

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 ) ;

}


由于同样的数据可能在流的帧与帧之间处于不同的偏移,所以无法直接访问流数据。例如:通过在视图中添加或移除AnimationClip,该数据或许已经移动或是不存在于流中。


为了确保这些访问的安全性和有效性,我们引入了二组句柄:流句柄和场景句柄,其中每个句柄都带有变换和组件属性句柄。


1

流句柄

流句柄(Stream Handle)会安全地管理所有对AnimationStream数据的所有访问。如果发生错误,该系统会抛出C#异常。流句柄共有二个类型:TransformStreamHandle和PropertyStreamHandle。

 

TransformStreamHandle管理Transform并处理变换层级。这意味着你可以修改流中的本地或全局Transform位置,并且为以后位置的请求将提供可预测的结果。

 

PropertyStreamHandle管理系统可以在其它组件上设置动画和查找的所有其它属性。例如:它可以用于读取或写入Light.m_Intensity属性的数值。


public struct TransformStreamHandle

{

    public bool IsValid(AnimationStream stream);

    public bool IsResolved(AnimationStream stream);

    public void Resolve(AnimationStream