查看原文
其他

Playable API:定制你的动画系统

Unity Unity官方平台 2018-11-15

本文将由Unity技术工程师金晓宇为大家讲解什么是Playable API?它有什么优点?我们如何利用它来定制我们的动画系统。

Playable API

简单的说,Playable可以通过一组API来创建一个Graph,而每个Graph可以由多个树形结构组成,每个树状结构都由一个Output节点作为根节点,叶子结点则由各种Playable组成。


它提供了一种创建工具,动画系统或其它游戏机制的方式。Playable Graph允许我们混合和修改多个数据源,并通过单个输出来播放它们。

 

目前Playable API 支持播放动画、音效以及自定义的行为。我们把它设计为了一种通用的接口,它并不是仅仅针对动画的。以后还会支持视频等其它系统。


为什么要开发这么一套API

在我们技术支持团队平时接触客户时发现,由于历史原因或者是出于习惯,依然有很多开发者在使用Legacy动画系统Animation组件。Legacy动画系统对于程序来说是比较直观的,但这就意味着没办法使用Mecanim动画系统的一些高级功能,例如:动画重定向、Blend Tree等。

 

首先,设计Playable API的一个目的就是为了代替Legacy动画系统。


开发人员可以用他们喜欢的方式来直接控制动画系统,而不是必须使用Mecanim系统的动画状态机来驱动动画系统。在Unity底层,驱动Playable Graph的实际上依然是Animator组件,所以在使用时需要你传一个Animator组件给Playable Graph。但是你完全可以像使用Animation组件一样使用Playable。

 

其次,我们还可以定制适配于自己项目的动画系统,而不用必须使用动画状态机来构建我们的动画逻辑。


这是一个非常有用,也非常有挑战性的工作。例如:有些有技术积累的公司,他们已经开发过自己的动画控制系统,这里不是指底层的动画驱动系统,而是更高层的动画控制系统,这套系统可能是架构在其它引擎上或是自研引擎上的。如果它们想要沿用这套系统的话,就可以用Playable来更方便的重构他们的控制系统,来适配到Unity上。更重要的是,我们可以定制更加适配于具体项目的动画系统。


再者,Playable API可以更直接的访问底层动画系统的接口。


最后,我们可以通过Playable来扩展Timeline的功能。


使用Playable有什么好处

首先Playable结构简单。它不会有庞大的、蜘蛛网一般的状态机。如果只是单纯播放动画的话,可能几句话就够了,使用起来就像Legacy Animation组件一样。如果是大型的RPG或FPS游戏,我们也没必要把大量的动画都添加到Graph中,我们可以预先创建好需要的子树,然后根据需要在添加到Graph中。

 

动画状态机是不允许运行时添加、删除动画的,它只能使用OverrideController来替换动画。这其实在开发中时是不太方便的。因为当动画数量非常多的时候,我们又没办法在运行时添加、删除State,这往往造成会生成一个巨大的状态机来满足所有可能的状态,而这个巨大的状态机维护起来是十分麻烦的。

 

在Animator状态机中,是通过定义变量来间接控制权重的。而在Playable中,你可以直接控制动画的权重和时间。例如:我们可以让二个动画按0.2和0.8的权重混合。Playable的这种方式是更加灵活的,但是相对的它对于普通开发者实际上是没有Animator parameter方便的。

 

我们不仅能在二个Animation clip之间混合,我们也可以在Clip和AnimatorController之间混合,甚至无数个AnimatorController之间混合都是可以的。这就给我们带来了更多的便利:在动画状态机加这个系统中,二个State machine之间是不能过度的,但是如果使用了Playable,那就是可行的。所以我们完全可以混用Animator状态机和Playable。一些角色的固定动画状态的转变我们可以继续用动画状态机,而那些需要动态改变的功能我们就用 Playable。


基本架构

Playable API大体上有二部分组成:各种Playable和PlayableOutput。

 

Playable是我们这套API的基本组成元素。为了避免Gc alloc,所有的Playable是用Struct实现的。PlayableOutput是每个Graph必须的组成元素。而且PlayableOutput必须连接到至少一个Playable上,否则它是没有任何作用的。

 


注意:上图中所列的Playable结构是不全的,因为后续版本还会不断有新的Playable被添加进来。


Playable Graph

 让我们先从图形入手,对Playable Graph有一个直观的认识。下图是一个很简单的Playable Graph。


 

我们可以看到这里有二个节点:一个Animation clip节点和一个Output节点。这里实现的功能很简单,就是播放一个动画。这是一个Playable节点形成的一个非常简单的树形结构,基本上,每一个PlayableOutput都会形成一颗树形结构。

 

让我们来看一个稍微复杂点的例子。


 

一个树形结构一般由一个输出节点,若干个输入节点以及若干个功能节点组成。在上图中,有一个输出节点Animation Output,二个输入节点Animation clip以及二个功能节点。这个Graph实现的是二个动画作为2个Layer来融合,可以看作和动画状态机中的Layer是一样的功能。

 

如果单看动画这一部分的话,Playable graph本质上就是一颗动画混合树。注意:这里指的这个混合树并不是Blend Tree,而是一个更广泛的概念。它的内部节点是运算符,主要起到混合的作用,而叶节点是输入。

 

现在,我们来看一个更复杂一点的例子。



这是由三颗树形结构组成的一个Playable Graph。它由Animation output、Audio output以及Script output 三个输出节点。可见一个Playable Graph不仅仅可以包含动画,它是一个更广泛的概念,你可以在播放动画的时候同时播放音效以及自定义的逻辑。


Playable用途

通过以上的介绍,大家应该能对Playable Graph有一个直观的、简单的认识。那么Playable又能实现哪些功能呢?接下来我们就深入了解一下。

  

首先,我们可以用Playable API代替Animation组件。这使得我们在满足Legacy动画系统的机制的同时,我们还可以获得其它好处,例如:Blend Tree、动画重定向等。

 

这里给出一个官方的示例,主要用Playable API来模拟了一套动画状态机,以类似Animator动画状态机的机制来使用Playable,大家可以参考一下。


官方的示例下载:

https://github.com/Unity-Technologies/SimpleAnimation

 

再就是按照项目的需求来定制了。你可能并不喜欢用动画状态机来管理动画,也有可能你想要实现一个自动评估transition的系统,使系统可以评估当前状态,自动过渡到最理想的动画等等。Playable API是提供给了大家按需定制的功能。

 

运行时创建

我们的Playable Graph都需要在运行时创建,目前它还没有一个默认的Asset来供使用。不过开发者完全可以自己来创建自己的Asset来序列化自己想要的Graph结构,然后运行时通过Asset来反序列为Graph。


下面是创建一个简单的Graph的代码。



从上图可以看到,Playable结构都是以工厂方法Create来创建的。通过这段代码创建的Graph就是下图这样的,十分简单。


运行时添加

在运行时,可以创建你想要的Playable并且连接到对应的输出节点或者另一个Playable上。



在用工厂方法创建好AnimationClipPlayable结构后,需要通过Graph.Connect方法来连接到Graph中。

 

下图就是这段代码添加二个Playable后的Graph的样子。


运行时移除和销毁


 类似的,我们也可以在运行时动态修改Graph的结构,以及销毁指定的Playable。


直接访问与控制Playable


我们可以直接控制每一个Playable的属性,例如播放速度。我们甚至可以暂停一个动画把它当作一个Pose来使用。


其次我们可以控制每一个Playable的混合权重,通过控制权重,我们就可以实现类似Blend Tree、Transition等功能。

 

我们还可以控制更新频率,这在我们做动画的性能优化时比较常用,例如:那些距离Camera过远的角色我们就可以把动画的更新频率降低。

  

要方便的控制每一个Playable还需要我们做一些额外的工作。这是因为:想象一颗非常深的树形结构,你想访问或控制非常深的一个叶节点实际上是不太方便的。当然你可以保存起它的引用,不过那通常不太灵活,像是写死在代码中一样,一个二个还好,但是你不可能把所有的都存下来。所以这就需要我们做一些额外的工作来管理和访问Playable。例如:提供一个查询叶子节点的接口或者实现一个类似Animator parameter系统来和Playable的属性进行映射。

 

Transitions


Playable API 中没有专用的Transition功能,它没有类似动画状态机中二个state之间拉一个线段来定义Transition。这是因为Transition本质是二个动画的Blend,所以Playable API没有提供专用的Transition功能,而是提供给应用层基本的Blend功能,应用层根据自己的需求来实现Transition。

 

这样就给了我们更多的可能性。除了经常使用的平滑过渡以外,我们还可以实现其它类型的过渡,例如:冻结过渡,也就是由动画A过渡到动画B时,A就不在更新了。而动画B在更新的同时Weight由0变为1。这种过渡方式比较适合那些前后二个动画差异较大的情况。另外我们为了更圆滑的过渡,我们还可以控制过渡时的曲线,例如:使用Hermit曲线。

 

这给我们带来了更佳灵活、自由的控制方式。现在的动画状态机使用起来虽然方便,仅在二个State之间拉一条线就可以了.但是我们想要精确的控制Transition却比较困难。例如:有这么个需求,服务器和客户端动画的同步问题。如果用Animator来做的话,是很难做到完美的。因为Transition你在外部是不能直接控制的。而如果用Playable的话,我们只需要同步二个动画的ID,以及它们的权重和Transition的持续时间就可以了。

 

Animation Layer


Playable API同样可以实现骨骼分部混合和加法混合,类似于Animator中的Layer功能。这在我们混合全局姿势和局部姿势时会非常有用。比如RPG中上下半身的动画,像是边跑边攻击之类,或者是FPS中角色边跑边换枪以及瞄准姿势等。我们也可以运行时动态的增加、删除Layer,如下图所示。


 

通过设置AnimationLayerMixerPlayable的input weight来控制混合。一个基本的Animation layer混合如下图所示。


Blend Tree


我们也可以用Playable实现Blend Tree来混合动画。


这段代码相当于实现了一个最简单的1D Blend Tree。



通过AnimationMixerPlayable来进行混合,并且通过Input weight来控制混合过程。为了保证动画的准确性,AnimationMixerPlayable的混合权重在内部会保证和为1。


虽然Playable可以定制Blend Tree的混合方式,但是个人觉得对于一般的开发者来说目前过于繁琐了。比如2D Blend Tree中的三元混合,你需要以三角形质心坐标系来计算三个输入的权重。因此对于Blend Tree,大家不如直接使用动画状态机中的Blend Tree,那样更为方便。

 

与Animator Controller混合使用


首先,Playable可以和Controller叠加分层动画。在动画状态机中Layer是Static的。所以利用Playable和Animator controller混合就可以起到动态添加你想要的Layer的作用。

 

其次,Playable可以和Controller进行混合,你可以让它们按一定的权重进行Blend。

 

再者,Playable可以和Controller互相CrossFade。例如:我们有一把武器,想要让武器来告诉角色该怎么使用这把武器。所以我们创建一个Animator controller放在武器上,当角色拿起武器后,就可以CrossFade到武器的动画状态机上。这可以让大大降低我们的动画系统的复杂度,因为动画的CrossFade不在局限于一个状态机里了。

 

最后,二个Controller可以进行混合。例如:你可以从一个状态机Crossfade到另一个状态机上。

 

控制多个输出


一个Playable Graph中不是只能有一个Output。除了动画的Output,我们还能添加其它种类的Output,例如:Audio output或者Script output. 这给了我们同时播放不同种类的Playable的能力,我们可以在动画的某一时间点播放一个音效。



上面段代码会生成下图的Graph。


PlayableBehaviour

PlayableBehaviour是一个供用户自定义行为的类。我们可以对Playable进行直接的访问和控制。同时它也定义了一些回调函数来捕捉一些事件。例如:开始播放时的事件、销毁事件,我们想监控这些事件时就离不开PlayableBehaviour这个类。

 

更重要的是,它提供了一些在每一帧的动画计算流程上的回调。例如:PrepareFrame函数会在Prepare frame这个阶段回调。我们就可以在每一帧对Playable中的元素进行访问和设置,例如:Time和Weight。可以说这是在做自定义Blend中不可缺失的功能。

 

举个例子:你想把子弹时间和人物射击动画的时间联系起来,你可以在PrepareFrame或ProcessFrame中获取射击动画的Normilized time,来修改游戏的Time.scale。

 

PlayableGraph Visualizer

PlayableGraph Visualizer是一个查看Playable graph结构的工具,这个工具目前来看还有些简单,它只能查看Graph的结构,没有编辑功能。但是对于Debug或者了解Playable graph的运行过程还是有帮助的。

 

PlayableGraph Visualizer下载地址:

https://github.com/UnityTech/graph-visualizer


未来展望

在Playable未来的开发中,我们会逐渐加入更多功能,例如: Procedural Animation与C# Job System相结合、Custom mixer、Motion stream等等。Procedural animation: IK, full body IK或者是FPS中举枪瞄准的动画。


结合C# Job System,可以让你的Playable运行在一个高效的、线程安全的子线程中,而

Custom mixer,可以定制你自己的mixe,Motion stream,一个特殊的Node可以直接播放一个Pose。

 

Playable API为我们带来了强大的灵活性和扩展性。它的目标并不是为了替换掉当前的动画系统,相反它可以跟当前的动画系统进行有机的结合,来订制一套适合开发者项目需求的体系。

 

结语

关于Playable API的分享就到这里了,如果你有希望有哪些分享的技术内容,可以后台留言给小编,更多Unity技术内容尽在Unity官方中文论坛(Unitychina.cn) !

 

推荐阅读


点击“阅读原文”访问Unity官方中文论坛

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存