粉丝2565
关注 3
获赞 10390
Unity 动画系统学习总结(转)

[教程] Unity 动画系统学习总结(转)

 !heats_icon! [复制链接]
2598 0 6 83 3年前 举报
来源知乎专栏:游戏开发碎碎念

01. Unity 动画系统学习总结Animation Clip

Unity 的动画是基于所谓 Animation Clips 的概念,它记录了一些对象上的数据(比如坐标、旋转、透明度等等)的关键帧,然后插值获取每一帧的数据。Animation clips 可以通过导入第三方软件的导出动画获得,也可以在Unity中直接创建。第三方导入的 Animation Clip 是只读的,如果需要修改,则需要拷贝出另外的副本。

Animator Controllers

Animation Clip 记录了动画的关键帧与插值过程,可以简单认为就是一段动画,但在游戏中,我们往往需要在一个对象(可能是角色、建筑等等)上在恰当的时机播放特殊的动画,比如角色站立时播放站立的动画,跑动时播放跑步动画,建筑静止时播放静止动画,升级时播放升级动画,并且还需要定义这些动画的切换过程,完成这些工作的就是 Animator Controller。具体而言,它是通过状态机的方式来实现上述需求。
1.jpg

它会将各个状态关联到 Animation Clip 上,可以使用代码直接切换状态,也可以通过修改各种预先设置好的参数(Parameters)来达到同样的目的。和 Animation Clip 一样,Animator Controller 也可以以 Asset 的形式被创建、编辑、保存。编辑获得想要的 Animator Controller 后,将其添加到 Animator 组件上即可在游戏中使用。

Animation 状态机State

上文提到状态机中的每个状态都对应一个 Animation Clip,但是除了这些真正的动画状态之外,还有如下特殊状态
  • Enter,状态机的入口
  • Exit,状态机的出口
  • Any,代表任意状态




Transition

状态与状态之间存在转换的过程,Transition 即是用来描述这个过程,我们可以在这里定义状态之间的切换,比如切换的持续时间,从旧状态的哪一帧结束,新状态的哪一帧开始,从而实现各种业务需求以及表现的平滑过渡。此外可以启用 Mute 和 Solo来限制状态之间的切换,因为即使没有 Transition ,也可以强行从一个状态硬切换到另一个状态。

Animation Parameter

上面说到可以通过修改 Parameter 来达到切换状态的目的,Parameter 的值可以是整数、浮点数、布尔值或者Trigger(使用完后会被自动重置的布尔值),可以通过 Animator 提供的接口来修改它们。可以作为 Transition 的条件从而影响状态的切换。

State Machine Behaviours

可以为状态添加 State Machine Behaviours ,他提供了一些接口让我们可以在一些时机执行相应的逻辑,比如在状态退出的时候调用播放音频的接口。

Sub-State Machines

可以将多个状态组合一个 Sub-State Machines ,可以减轻 Animator 的维护成本。

Animation Layers

Unity 使用 Animation Layers 管理用于不同部分的状态机。不如下半身层管理走路、跳跃,上半身层负责投掷、射击。
可以通过调整层权重(Layers Widget)来管理动画中的层。点击 layer 右侧的齿轮。可以打开层的设置。

2.jpg 3.jpg

可以设置 Avatar mask(确定动画将会应用到模型的哪部分),Blending(和其他层的混合模式),Override 代表忽略其他层,Additive 代表会叠加到前面的层之上。此外,还可以通过 sync layer 实现状态机的复用, 一个 layer 可以通过与其他的 layer 同步从而共享被同步的 layer 的状态机。

4.jpg
Animator Override Controller

类似 Animator Controller,可以用来复用 Animator Controller 的状态机。它可以覆盖替换 Animator Controller 中的状态对应的 Animation Clip。

Avatar

我们可以在导入模型的时候,将骨骼关联到 Avatar 上,这个 Avatar 可以是导入模型时创建,也可以引用之前导入的,这个关联过程如果满足匹配的话就可以自动关联,否则就需要手动关联,Unity 允许部分骨骼关联。拥有 Avatar 可以使用 Unity 提供的目标匹配(Target Matching)、IK (Inverse Kinematics),重定向(Retargeting)等特性。

Blend Tree

混合不同的 motion 再游戏动画里是很常见的需求,比如根据角色速度混合走路和跑步动画,还有跑动时要混合跑步与转向动画。这时候就可以使用 Blend Tree 了。

状态机和 Blend Tree 都能实现不同动画间的平滑过渡,但是前者是基于两个状态的 transition,后者则可以是大于等于两个动画的混合。Blend Tree 本身也可以是状态机里的一个 state。当然这些动画的差距不能太大,毕竟线性插值也是有极限的。

5.jpg

Blend Tree 将会根据绑定的 Parameters 还有 Blend Type 来确定当前播放的动画。

1D Blending

1D Blending 通过单个参数来确定动画,它仅仅是简单在各个子树之间根据权重插值,看图就明白了。
6.jpg

蓝色锥形的高度代表权重,可以看出随参数的变换,各个动画的权重变化。这个权重的分配可以手动编辑,也可以靠 unity 自动完成。自动完成可以是均匀分配,也可以是根据动画的速度等参数加权分配。

2D Blending

2D Blending 通过两个参数来确定动画,本质多是通过两个值构成的向量获得在二维平面上各个位置的权重,不过各个类型之间还是有所区别的。
7.jpg

  • 2D Simple Directional,适用于动画代表不同方向的情况,比如向前后左右走,单个方向只允许有一个动画。
  • 2D Freeform Directional,也适用于动画代表不同方向的情况,但允许同向存在多个动画。
  • 2D Freeform Cartesian,适用于动画不代表不同方向的情况,参数可以用来描述方向之外的概念,比如角速度或者线性速度。


和 1D Blending 一样,各个动画的位置也可以通过设置自动计算出来。

Direct Blending

除了上述的通过参数计算出各个动画权重的方式,还有直接用参数作为权重的方式。
8.jpg

这样可以直接而非间接地控制动画的混合,可以看见每个动画都被直接分配到一个参数上,因为直接所以事实上是最灵活的类型,可以用其实现淡入淡出、甚至自己的 2d 混合算法,还可以结合 blend shape 做出复杂的表情动画。

blend shapes

Blend Shape 也是依赖 SkinnedMeshRenderer 组件,打开该组件即可看到各个shape 的权重。
9.jpg

基本原理是各个预设好的 shape 的顶点按照权重去混合,成本不低(相比起骨骼动画而言),一般只用于脸部表情等精细部位。
总结

大体上过了一下 Unity 动画基于 state machine 还有基于 Blend Tree 的两种机制,其实两者并非互斥的,Blend Tree 本身也是 state machine 中的一个 state,它只是提供了另一种动画控制与平滑过渡的机制。


02. Unity 动画系统学习总结 动画系统的优化Animator

如果 Animator 的 Animator Controller 字段为空,则不会有消耗。
简单动画

旧版的 Unity 动画系统,会直接采样帧曲线并应用到目标上,新版的动画系统采样之后则会先写入一个数据缓存,如果混合多个动画时,会更快,但如果是没有混合的简单动画,则反而会更慢。
缩放曲线

播放缩放动画相比平移和旋转开销更大,尽量避免使用,但常量曲线(水平的直线)则没有关系,因为 Unity 针对常量曲线做了优化,并不会每一帧都将帧数据应用到目标上。

Layers

大部分时候 Unity 会评估动画,保持 layer 与 状态机的开销在最小,这部分开销是否能被减小,取决于layer 播放的 blend Tree 和 animation,当 layer 的权重为 0 时,Unity 会跳过它的更新。

Humanoid vs. Generic animation types

  • 当导入 Humanoid animation 的时候,使用 Avatar Mask 去移除掉不需要的部分,比如 IK 目标或者手指动画。
  • 导入 Generic 时,使用 root motion 会有性能开销,但是如果不使用的话,要确保不回修改到 root bone。


运行时优化

可以通过 Animator 的 Culling Mode 来裁剪掉通过的更新,比如使用 Based on Renderers 时,关闭 skinned Mesh Render 的 Update When Offscreen,模型不在视锥体时就不会更新。

Playable

Playable 相关的API ,通过 PlayableGraph 向我们提供了混合、修改各种数据并且输出的能力。他能够超越 Unity 动画系统 基于状态机 的实现导致的限制。

PlayableGraph

PlayableGraph 定义了绑定到 GameObject 或者 Component 上的可播放输出的集合与它们之间的联系。
10.jpg

Playable 的API 有两个主要部分,Playable 和 Playable output,前者是 PlayableGraph 的非根节点,后者是根节点也就是输出结果。为了避免 gc ,这两种类型都是 struct,它们在命名上看起来像是继承关系,但是实现上并不是,不过表现上很像,为了方便理解可以简单认为是父子类关系。

11.jpg
playable 核心类型
12.jpg
playable output 核心类型

这些 struct 相关调用不是通过成员方法,而是一系列扩展方法。所有非抽象的 playable 类型通过公共静态方法 Create 来创建,playable output 类型也相同。
Playable output 类型需要链接到 playable 类型上,否则无法正常工作。该操作通过调用 PlayableOutput.SetSourcePlayable 来实现。Playable 节点之间,则是调用 PlayableGraph.Connect API 来链接,需要注意有些 playable 没有输入。
PlayableGraph通过调用 PlayableGraph.Create 来创建,可以调用相关 API 来完成播放、停止、读取、销毁。我们需要主动管理它的生命周期。

ScriptPlayable 与 PlayableBehaviour

为了创建自定义的 playable ,需要继承 PlayableBehaviour 类。之后便可以调用ScriptPlayable<T>.Create 来完成自定义 playable 的创建。之后可以调用ScriptPlayable<T> .GetBehaviour() 获取实际的对象。

Playable 的实际案例

我们可以通过 github 上的开源项目 PlayableGraph Visualizer 来可视化地查看 PlayableGraph。图上节点的颜色强度代表权重。需要调用 GraphVisualizerClient 的 Show 方法注册希望显示的 PlayableGraph。

13.jpg
播放单一动画
14.jpg
混合动画
15.jpg
多个输出
16.jpg
控制节点状态,注意右下角的clip暂停了
17.jpg
Playable 队列,会按照顺序依次播放动画

这些实例大同小异,总之就是要理解 PlayableGraph 基于节点、权重、输入输出而形成的动画机制。代码细节看 文档 。
总结

相比起状态机机制, Playable Graph 提供了更灵活的接口,现有的动画系统里的各种基于状态机的实现都可以通过 Playable Graph 做到,状态机做不到的也能做到,为更精细、复杂的动画需求提供了机制。不过也有只能动态创建、不利于维护等缺点(其实可以通过自定义序列化数据以及编辑器扩展来补足),总的来说很有潜力,不过撸起来还是挺费力的,大部分普通的动画需求其实用现有动画系统也能满足。
参考链接:
https://docs.unity3d.com/Manual/AnimationSection.html
https://docs.unity3d.com/Manual/MecanimPeformanceandOptimization.html
https://docs.unity3d.com/Manual/Playables.html



6
点赞
0
打赏
83
添加到收藏夹
打赏一次,1个CG券
全部评论0
您需要登录后才可以回帖 登录 | 立即注册

暂无评论,去成为第一人吧