上一节课提到,我们给一个角色做一个跳起来的动画的时候,我们要把它的位移去掉。因为在游戏中这个角色跳多高,包括多久能落地,都不是动画能做死的。比如说站在一个台阶上,我从台阶上跳下来,虽然跳的高度是一定的,但是因为跳下来台阶的深度并不知道,所以我并不知道什么时候落地。
所以一般我们会给跳跃做三个动画:
- 第一个动画就最上面的叫起跳。
- 第二个动画叫空中的 loop。也就是在空中浮空的时候,很优雅地把手和腿摆来摆去。
- 最后一个就是落地的时候有一个叫 landing 的动画。
这个时候我们会发现没有办法去 blending 它了,因为它不是个 blending 的问题,它是一个彼此依赖的状态切换的问题。
比如说我们在游戏中按跳跃键,角色就会起跳,起跳动作播放完后,就会播放 loop clip,这时候角色不知道什么时候会着陆。当角色快落地的时候(而不是落地的一瞬间),角色就需要做出一种紧张的姿势,然后准备落地。这其中这个角色其实是在三个状态里面有序地切换的,这个就是一个非常典型的状态机的需求。因此我们需要定义动画的状态机。
ASM Definition
- ASM consists of nodes and transitions
- Node types
- Blend space
- Clip
class ActionStateMachineClipNode { AnimationClip m_clip; bool m_is_loop; }; class ActionStateMachineBlendSpaceNode { BlendSpace m_blend_space; bool m_is_loop; };
动画的状态机一般有两种核心元素:
- 节点。
- 一个节点可以是动画的一个 clip,也可以是一整个 Blend Space,甚至可以把一个动画的蓝图放进去。(其中蓝图借用的是 Unreal 的概念,就是整个动画混合的树都可以放进去,非常灵活。但是蓝图产出的一定是一个动画,也就是一个 pose animation。)
- transitions
- 动画的切换。
- 当动画的当前节点满足了一定条件之后,状态机会从 node A(状态 A)切换到 node B。
以上图为例,从 idle 开始,当状态机收到了这个玩家跳起来的指令后,满足了跳跃条件,就会进入到 jump 节点。 jump 那个节点首先是 jump start,然后到了高点的时候 jump start 就结束了,就会切换到 jump loop,然后 jump loop 一直循环,直到被通知角色要落地了。这个时候状态就切换到 landing (图中的 jump end),landing 播完之后,才会回到 idle。这样角色的行为看上去才是自然的。
上图中的两块 jump 是分了两种类型,一种是 idle 时的 jump start、loop、end,一种是跑步时的 jump 动画。
Translation
- Transition type
- simply “pop” from one state to another
- cross-fade from one state to the next
- Special transitional states
class ActionStateMachineTransition { int m_source_node_index; int m_target_node_index; }; class ActionStateMachineTransitionWithCrossFade { int m_source_node_index; int m_target_node_index; float m_duration; Curve m_curve; };
其中 Translation 比较复杂,这里给出最简单的一个定义。
我们需要知道它的起点动画、终点动画。复杂一点要做插值的话,中间还需要有插值过程。
一个完整的动画状态机的实现的 transition 条件可能是很多个。
Cross Fades
两种方式做 fade:
- Smooth transition
- 从动画 1 到动画 2 之间,动画 1 的贡献量越来越少,动画 2 的贡献量越来越多。这是一个非常容易理解的一种插值。
- 限制:两个 clips 一定是循环的,动画长度必须同步。
- Frozen transition
- 动画1 要切到动画 2 的时候,动画 1 先停住,然后动画 2 逐渐地进来,进来的时候是基于现在动画 1 的基础。
- 有些动作要发生切换的时候,如果继续播放动作 1 的话,它和动作 2 在一起混合的结果看上去反而更不自然。
以上图起跳为例,如果用 smooth transition,会觉得这个人在空中还在试图跑步,这当然也是一种跨栏的演示方法。但是用 frozen transition 的话,会觉得这个人好像试图停一下在原地起跳。
到底哪个好?这个问题应该是 artist 决定的。但是我们作为 engine designer,我们需要给它们提供这个选项。
Cross Fades Curve
两个信号之间的 fade 是有非常多讲究的。fade 曲线比较多的形状就两种:线性插值,或者 easy out、cubic 等。
动画系统里面指数曲线用的比较少。我们会发现动画大部分时候,要么就是线性差值,要么就是 easy in、easy out 的插值,但是这不妨碍大家知道这些基础的理论,因为这个东西在引擎的很多地方都会用到。比如后面讲到 particle 系统的时候,particle 明暗的变化可能就要用这些曲线来表达。
可以参考下面链接页底的视频。
Animation State Machine in Unreal
以 Unreal ASM 为例,上面每一个小小的双箭头就表示了一个 transition,打开 transition 就能看到里面有很多的属性。这里面可以看到 duration 到底要插值多久,包括是什么样的一种插值类型。
上图展示了 transition 条件的设置。
每一个 states 可以是非常简单的动画 clip;它也可以做成一个非常复杂的动画蓝图等,也就是很多动画在里面按照一定的规则去融合;也可以放一个 1D 或 2D 的 Blend Space。
Unreal 的动画系统非常灵活,可以根据自己的需要自由地嵌套、自由地组合,而且它的很多图标包括交互设计都非常符合设计师的直觉。因此 Unreal 是非常好的动画系统的参考。
Layered ASM
different parts of a character’s body to be doing different, independent or semi-independent actions simultaneously
我们在游戏中表达一个角色的时候,在最古典的时代,我们用的叫做 Layered ASM 多层次的动画状态机。比如角色的上半身有一套复杂的状态机,可以表达比如受击或者各种攻击别人的动作。下半身会根据玩家的 control 比如说角色在跑跳走。然后把两层东西叠在一起,就是一层管上半身,一层管下半身。可能还有一层管头,还可能有一层管角色的受击。
几层状态机做好之后,基本上能够在游戏中表达一个活灵活现的角色。
比如像鬼泣 5 的例子,我们会发现这个角色为什么会觉得很有打击感,因为他下身跳的各种动作和上身打怪的各种攻击动作基本上是可以独立控制的,这样会觉得这个角色特别的活而不那么僵。这就是 Layered ASM 很大的好处。