ALS角色状态机设计

以下是学习ALS(Advanced Locomotion System)的第二篇笔记。文章主要分析ALS多层状态机设计模式与意图,目的是由简到繁,力求表达如何根据需求不断改进动画设计和其表现。

ALS的分层状态机

逻辑中存在多个状态机不一定是分层状态机设计,有可能是多个独立(并行)状态机设计。而分层状态机设计的优势是通过合理的划分抽象逻辑,有效减少各个范围域内的逻辑复杂度,逐级增加复杂表现细节。

ALS的状态机设计也是如此:

  • 首先明确技术范畴,即使用尽量少的动作资源以及依赖运行时骨骼计算来实现3A级别丰富的基础动作和行为动作。
  • 然后是需要先确认哪些动作是必不可少的基础资源,哪些动作是通过各种关系叠加融合出来的,还有哪些动作是锦上添花细腻表现。
  • 最后再由基础抽象慢慢往上构建整套动作系统,提供灵活的行为扩展。

最基本的动作抽象是哪些

角色最基础的行为就是站走跑跳,那么最简单的动作状态机实现就是这四个状态的转移。

Locomotion1

上图左边的状态机设计的问题是走跑变化,或者移动速度位于走跑之间时,动作的融合通常有瑕疵。如果把走跑动作做成混合空间(BlendSpace),然后通过角色移动速度来指定混合空间的参数,那么即可以简化状态设计,又能得到相对较好的动作表现。

通常大家会比较在意动作幅度变化较大的跳跃动作。美术会希望区分移动时起跳和原地起跳,甚至移动时的左右脚不一样的起跳动作。策划可能希望从不同高度落地时能播放不同动作。那么改进一下状态机:

Locomotion2

复杂度已经上来了,但是还能看。等等,我们还有情况没有添加:角色在移动出平台时跌落和落地也要像跳跃一样表现。当然,上面状态机再莽一点也不是不能加,可是跌落又分移动中跌落和站立状态被推跌落。虽然再加转移条件也能看,但是,这时候用分层来设计状态机,就会简单很多。

Locomotion3

行为动作与基础移动动作的整合

当角色要表现手持不同武器和装备的行为动作差异时,常规做法是各自武器都单独对应各自的动画资源、状态机和蓝图,通过动态切换蓝图,或切换相对独立的行为状态。

这么做的好处是:

  1. 各自成套的动作相互独立,互不干扰。
  2. 非常容易做出差异化的表现。

而缺点是:

  1. 成套资源之间切换时动作融合会不流畅、生硬(移动过程中切换尤为明显)。
  2. 如果还要播放切换的过渡动画,那么对应的控制逻辑复杂。
  3. 从资源角度来看,这样的制作方案依赖的动作资源多,美术工作量大,程序内存紧张。
  4. 可能要引入资源预加载相关逻辑来保证动作播放的实时性。

除了直接换整套动作,另一个容易想到和制作的方案是上下半身动画分离,下半身只播放移动表现,上半身播放武器装备表现。

ALS的处理方法非常巧妙,它使用了非常少的资源就整合出流畅丰富的行为表现。当然这一切的代价就是使用者的理解成本急剧上升,CPU解算动画的开销大幅上升。

ALS定义了三个层次的动画表现:

  1. 全套基础移动的动画表现BaseLayer
  2. 当前基础移动的静态姿势BasePoses,静帧资源。
  3. 不同持械状态下的行为动画表现OverlayLayer,静帧资源加少量细节动作。

OverlayLayerd

蓝图里再运用MakeDynamicAdditiveApplyAdditive节点以及LayeredBlendPerBone来混合出最终姿势效果:

ALSOverlay

除了上面这样极端简化的表述外,ALS还定义了很多细节来保证动作的混合效果:

  1. 生成Local SpaceMesh Space两种空间格式的叠加动画(Additive)来应对不同的需求。
  2. 动作并不是笼统的全部走叠加逻辑,而是ALS在OverlayPose里用Curves控制具体部位混合和叠加逻辑。
  3. ALS定义了极其细腻部位(Legs, Pelvis, Spine, Head, Arm L, Arm R, Hands)精细调整其表现。
  4. OverlayLayer也包含配合运动的细节,比如持枪奔跑,因为单从BaseLayer中得到的胳膊表现是错误的,所以持枪状态的OverlayLayer动画要有单独胳膊的表现。

增加运动细节

以上章节其实已经可以组成相对完善的角色动作表现了,但是玩家或者美术同学经常会反馈角色动作:角色移动僵硬,各个姿势切换生硬,总之一句话:“我觉得别扭不流畅!”。

通常来说问题发展到这里,经常会掉落到资源需求和状态设计的陷阱里:那我们增加状态间的过渡状态呗:站立到跑动、跑动到停止、落地后移动……那个熟悉的复杂的状态联接关系又回来了。比如刚进入的“站立到跑动”的状态又要中断回到“站立”状态!

那么ALS怎么解决的呢?

运动倾斜

StartRun

角色运动一定要伴随着重心变化,甚至是重心先动,才带动身体与躯干做相应动作。但是在游戏里,因为我们要保证玩家的输入响应,所以重心只能做伴随变化。ALS里把重心变化抽象成独立的运动倾斜混合空间资源BlendSpace,再根绝当前角色移动速度设置叠加动画Additive的参数,最后得到具有相应运动趋势移动效果。

lean

光有倾斜还不够,ALS又做了根据加速度播放的起步叠加动作,再进一步增加角色起步加速、急停的表现张力。但是这么做还是造成了动作状态增加,老办法,再上分层状态机。

LocomotionDetail

通常来说,当我们设计基础移动时,要么会选择StandWalkRuning具体细节的包含独立的动画资源的状态。要么会选择一个包含WalkRuning等资源的混合空间BlendSpace,再提供一个统一的输出。而上图却把这两种方案一起使用了:MovingStateMachine提供统一的动画输出,然后再由DetailStateMachine划分各个细节状态转移的逻辑,最后在LocomotionStateMachine选择或叠加使用前两个状态。看起来似乎是多此一举,但事实上这么做是非常聪明且细节的设计:

  1. UE状态机内部状态切换的混合是独立的,不受其他状态机影响。因此Moving中的跑停混合不会被Detail的倾斜干扰,LocomotionStateMachine中两个状态的切换也是独立的。这样设计可以单独调整各自状态机的混合时间与方式,容易调出满意的效果。
  2. 角色的移动速度经常会落到走跑的速度区间里,BlendSpace能提供比两个独立状态更连续顺畅的动画表现。(可以参考ALS第一篇文章
  3. 分层设计有利于解耦具体的资源依赖,后续我们还会看到对MovingStateMachine改进,而不影响上层状态机设计。

再次设计跳跃

有的游戏跳跃落地后可以马上移动,有的游戏跳跃落地以后必须等角色平衡了才能移动,有的游戏兼而有之普通落地可以移动重落地要等待一会儿。这些方案没有优劣都是选择,但不论怎么选,要想动作更流畅,那上文设计的MainMovementState就又不够用了。

首先FallingLoop还是简单的,无非不是两个姿势的过渡,ALS的处理细节是用Blend节点混合FallingLoop,再单独处理ToLand状态。这样既能提供连续的变化姿态,又能节省一个状态机设计。ToLand又分落地不动和落地移动两个状态,其中落地移动状态里的资源要使用Additive叠加动画资源来加到基础移动逻辑里,用以表达角色落地后的重心变化。

MovementState

原地转身

作为次世代游戏,怎么能滑着转身呢?!哪怕转身情况很复杂,有左右30°、60°、90°、180°,那也得上,不仅得上,还要动作丝滑反应迅捷。

ALS把转身的需求拆分了三个维度来处理:

  1. 根据当前旋转状态来区别处理:
    1. Aiming状态下,角色角度锁死在左右100°之间,超过则硬转,其他角度则通过选择三级Spine骨骼来实现扭腰。这种情况满足了反应迅捷(上半身与镜头方向锁死,下半身播放动画)。
    2. VelocityDirectionLookDirection状态下只限制头部旋转,角色旋转角度滞后于玩家控制,后续通过旋转动画慢慢补偿。这种情况满足动作丝滑。
  2. 左右转身只处理90°的动画,如果待转角度超过了90°,那么再播放一次,提前转到了就结束。
  3. 转身动画没有使用RootMotion方案,而是把Root的旋转信息折合到动画Curve(RotationAmount)中,再由Curve驱动每次旋转的角度增量。这种情况对应提前结束转身的需求。

在本篇文章中我们暂时先不看Spine的处理,单说状态机设计的话,那么LocomotionStateMachine则如下:

LocomotionRotation

呼吸相位的匹配

基本上动作方案做到这里,感觉已经要满的溢出来了,接下来最多是一些小状态的修修补补。

但是有天美术贱兮兮的过来跟你说,“我觉得的动作切换还是硬,不流畅。”

你说,“咋还硬啊,Blend时间都拉长到0.5s了怎么还能硬呢?逐帧看也没有一个跳帧啊!”

“你说的都对,但是我就算感觉不流畅,那种感觉,你知道吧。站到走,跑到站,端枪换枪,开启瞄准,每一个环节,隐隐约约就感觉不流畅。”

这种对话一但发展出来,基本上就剩一个相互口腔体操的结果了,但是ALS告诉大家,一切以为的玄学,都还是理论知识不到位。

通常为了表现角色有生气不呆板,美术同学会给所有基础动作加上一个周期性的呼吸动画。既然有了周期动画,那么多个动画融合的时候,就会产生多个周期相位匹配的问题。如果画两个正弦函数来表示两个动作融合过程的话:

PhaseBlend

上图同周期的曲线都融合的不流畅,更何况不同动画携带的曲线周期不同呢。所以ALS在Locomotion阶段就剥离了所有移动、行为动画上的周期呼吸动作,在最后输出时通过叠加(Additive)方法再把呼吸动作加回来。这样直接消除了呼吸周期对融合结果的影响。

这么做也到不是100%完美,它会让部分行为动画,尤其是手,来回乱颤。ALS给出的方案是通过Tow Bones IK再把手定住了。

随着业务的发展,后期角色可能又要跟乘骑载具等呼吸动画配合,那么相位问题就又出现了。这就是另一个大课题了:多角色的动画配合。简单粗暴的办法就是先把角色身上的呼吸动画禁了。

多向移动

本来多向移动是跟走跑跳一样基础的内容,应该先做的。但是在多层状态机的设计模式下,后序的改动对前序的影响就不大了,这也是分层状态机的优点。

上篇文章介绍过4向或8向移动对比ALS中的6向移动的区别,这里再介绍一下ALS在六个状态设计和其转移条件。

ALS设计了6向状态,但是具体在每一个状态中都需要前后左右四个动画参与融合,这个技巧在前文也提到过,它可以保证状态切换的流畅性。比如角色在向前移动突然换向向后移动,此时LocomotionStateMachine还处在前状态,但是动画已经开始融合后向移动动作了。因此在ALS的LocomotionStateMachine中的各个状态的职能并不是处理动作融合,而是处理转移逻辑和资源选择。

对于转移逻辑来说,ALS定义的6个状态不是星形联接的,就算有联接的状态里的转移条件优先级也不都一样。因此角色运动方向就有了偏向性和优先级。举个例子,角色在向左走时突然换向到向右走,那么角色的状态机变化是先左前状态转移到右后状态,待等时机成熟(动作动画差异较小的时候)转移到右前状态。这个说起来可能抽象一点,它在模仿人类运动的偏好,建议大家站起来走几步,体会一下。

对于资源选择来说,比如说ALS里有两个实现向右走的动画,一个是脸朝右,一个是背朝右。它们都能实现向右走的效果,但是在不同的方向状态里,就做了区分,这一点也是配合实现模仿运动偏好的。基于这个特点,我们也可以再做出其他差异化的动作(两种向后移动),或者减少动作(没有向后移动动作,以左后或右后取而代之)。

立定动画和通知事件

以前在设计立定动画的时候都是增加一个状态,及从移动到站立多一个过渡状态。ALS没有采用这个方案,而是在具体立定状态里处理脚步IK,来保证角色落在地上的脚不会再抬起来了。又通过条件转移或状态进入等的事件通知机制(UE蓝图里NotifyEvent)播放动态的蒙太奇动画。

对于简单情况下这两种方案其实都差不多,ALS方案的优越性要体现在处理不同逻辑下播放不一样的立定动画的需求上。 ALS有个不足是它没有处理玩家停止输入后马上响应的立定动画,这个功能参考现有立定事件通知来处理,问题也不大。

状态的可扩展性

分析到这里,我们基本上可以提供一套完整的动画设计了,直到需求又要加跟上面一模一样标准的蹲姿动作。ALS给出的示例是再加一层状态机MainGroundState来切换站蹲姿态,将MainMovementStateLocomotion的引用替换成MainGroundState

除了蹲姿以外,我们可能还会需要很多其他特殊的移动状态,比如开车、攀爬、掩体等,这些状态都可以针对MainMovementState来增加。这里也倒不是每个状态都能无脑添加,其原因是角色最后的动画还受行为叠加(OverlayLayer)动画影响。如果一个新增的特殊移动状态跟原有动画差异特别巨大的化,那么就要考虑再基础姿势(BasePose)上也增加一个相应的静帧姿势,用以参与后续混合于叠加。

最后来到行为姿势(OverlayLayer)的扩展。其实上文很多艰难繁琐的处理都是为了能叠加行为姿势而不得不选择的取舍。一旦前序工作做好了,那么后面的行为姿势就可以随心所欲的增删改。比如我们持枪,一个基础的OverlayLayer静帧就能满足,如果要表现持枪的不同阶段,那么就再加一层状态机,选择休闲、警戒、射击三个条件下的静帧资源。同理,我们需要增加其他类型武器,比如手枪、弓箭、乃至RPG,或者我们需要不同的行为表现,比如操作设备,手持电话等等,都可以通过输出OverlayLayer姿势来实现。