其他
GDC2016丨育碧《荣耀战魂》的次世代动画之路(上)
而现在Clavet所在做的是叫做【荣誉战魂(For Honor)】的游戏。
【介绍视频】
这款游戏在去年的E3上展出过,的乐趣是和伙伴们在战场里战斗,可以扮演骑士,武士或维京,有大量的角色可供选择。每个区域都像是一场非常真实的中世纪战争。有竞赛和多人的模式,以及一些可以很简单杀死的AI敌人。通常的当玩家发现另外一个玩家角色时,就可以锁定他,选择自己的立场,攻击或者阻挡攻击。战斗是慢节奏的,而不是那种毁手柄的游戏。你需要解读对手的攻击,攻击方式,轻击和重击的效果反馈不一样,可以用巨物或小刀攻击。
下面的视频是4V4的对战。目标就是获取点数,地图里面有一个特殊点,可以干掉对手来控制。可以看到这些游戏里的动画,挑战的是有着舞步一般真实的精确性,关联着详细的信息,相信玩家会喜欢这个游戏。
那么,从情感的立场上,Clavet他们的目标是真实精确的游戏体验,就像试玩里那样,玩家可以清晰的解读攻击,以通过学习来在游戏变得更加出色,通过掌握把握时机的能力,这样就玩的越来越好。但同时玩家需要非常可信任的动画,游戏世界比真实生活要更棒,但没有龙与魔法这些,是非常真实的世界,尽管现实中,维京勇士从来没有与日本武士战斗过。
那么本次分享,首先会介绍一些动画系统的历史, 以显示我们的角色那些是可以做,那些是不能做的,接下来是Motion Matching这种新动画技术的介绍,然后会在编辑器中展示Clavet他们是如何工作的,最后会展示一些程序化的自动润色,让敌人的动画更加适应环境以及获得所需的精确的游戏体验。
首先,Clavet展示了更多的最终效果,以此来聚焦角色在地图上的导航移动,然后会讨论每一个内容。下面的视频里,是女性的守卫(Warden),是第一个女性版本的类似骑士的战士。可以看到不同的动画过渡,有很多不同的细节启动和停止动作,可以说是有无限的停止动作,而且可以连贯播放和切换到休闲的状态。
下面的视频是日本武士角色,行动相当敏捷,是细节很多的类型,可以看到都是全身运动,这里使用了全身动画(FullbodyAnimation),而不是那种上下分层的动画(LayerAnimation)。在一些其他的例子情况可能需要使用分层动画,但大多数的时候是没有分层,而都是全身动画的。
接下里是男性守卫,可以走动,慢跑,冲刺,当角色启动和停止时可以看到各种类型的反映,就像1米以内启动1秒内再停下,都是在动作捕捉的真实的动画变化,Clavet他们也希望可以在游戏里所有在动作捕捉摄影棚中所看到的动作。在视频里可以看到,到玩家锁定敌人后,人物会变得紧张,就像是意识改变全身的动作变化来切换姿态。可以从侧面攻击和格挡,角色可以在自己目标周围跳跃,如果需要的话,要配合角色的姿势来格挡。
然后回到开始,来介绍上面的结果是如何开始进行的
那在一开始Clavet他们创建了叫PlayAnim()的函数。用来让角色播放你需要的动作,在需要的时候可以在你的代码里调用这个函数。
那么也就是像下图这样,为了播放启动动画(start),所以要PlayAnim这个动画,在结束启动动画的播放后,将会播放走动的循环动画(walk loop),这个是一个移动动画的组,不断的重复从开始到结束的动画。当不需要再走动时,需要播放停止的动画。
虽然可以按代码这样来做,但Clavet明显并没有这么去做,因为希望能可视化的来表现,所以使用了可视化的状态机,让状态来进行循环播放,并做动画的转移。在箭头里进行两个动作的混合,或者进行动作的迁移,或是在中间的迁移状态。(这里使用了UE4的动画状态机做示范)
开发者可以决定里面的结构,每种移动都可以在游戏里制作。因为状态机可以是分层,可以更加复杂,需要应对这些状态的迁移,而且Gameplay可以通过传送参数列表来控制状态机,状态转移的条件上利用这些参数,然后就通过参数,可以决定从一个状态迁移到另一个状态。或者用户可以通过一个Gameplay的信号来进行状态的迁移。(下面的图使用的是U3D的动画状态机做示范)。
你可以将这些条件选项可视化。如果需要把动画混合在一起,就是要用动画混合树或者决策树。这个系统的想法,是对从右侧进来的动画,决定他们是在上面还是把中间的节点混合在一起。在到左边放到一个动画里。
同样,也会有一组参数来决定如何把动画混合在一起,例如把慢跑和冲刺混合时,要去获取需要的速度,这样就可以选择用真实规则来决定使用哪个分支。
这种方法还是不好,会变得相当的复杂,所以用不同的动画给Upbody和LowBody,像攀爬动画时就可以独立的控制。最后,这些具体的动画都对应成状态,状态机中的每个状态做成混合树的状态机。也可以在混合树的一个节点里包含一个状态机,一层层嵌套下去 。因为所有都是分层的,会变得像很大的蜘蛛网一样,并控制你的角色。
那么,第一个问题就是,例如要进行移动到一个位置,那么要如何在基本性质上对这个移动行为命名,就像是这个移动的动画文件的名字,以及如何在整个结构体里来配置它。
Clavet的示例动作是一个向前并启动(StartStrafe)再转身(Around),这里命名为StartStrafe90TurnOnSpot45Stop,这种不得不起名字的事情是并是实际想要的。如果要把这个放到混合树或者状态机里,那么只能祝你好运了。还是要说,首先在直觉上我们并不能处理这类疯狂导航的很麻烦的事情。
再返回到动画混合树,下面的图里很多的节点,这里会有大量的动画,那么就要选择一种智能的方法来混合动画
这里选择的方法称作ParametricBlend,通过一组多维数据来设置,例如,速度(speed),Strafe角度(strafeangle)和斜率(slope),可以用多个像这样的纬度,每个红色的点都是一个和其他动画混合的组,当Gameplay有特定的speed,strafeangle,slope时,会去查找一个动画的混合值来把这些融合在一起,来获得和需求接近的结果。
下图中,每一列都是一个纬度参数,动画师可以通过参数来设置值,每一行都是一个循环,假设每个都可以很好做混合,每个列的值并不是浮点数,而是离散的数值,就像是tag一样,来决定需要哪些动画的组。
下面代码里,可以通过Gameplay中类似查询的方式,生成了一个AnimQuery 利用参数来查找,并增加需要的特性。在代码的示例中,找到了一些想要的动画,并按3米每秒的速度,斜度45度,以及90度的strafe angle,然后调用称作computBlendFactor的函数,会计算出每个动画是用多少值来混合在一起。来尽可能生成你想要的效果。就像是radialbasis函数,或者你想象成2D平面上的一个三角形,动画系统通过设置参数来对应三角形中的一个点,以三角形中的重心坐标根据这个点的位置来混合动画。
接下来看下第2个问题。
首先先看下视频动画,角色开始移动,然后有一点转向,这里的问题是,如何通过一种统一的方式来处理动画循环和过渡。从直觉上看,动画循环没有什么特别的,会和结束的动画混合,或和开始的动画混合,感觉就像是在过渡一样。它是从混合一个起始动作开始,然后不断的循环播放它。视频里另外可以看到的是,一些点的启动动画变成循环,当要完成启动并变成循环时,真的很难正确的解决。当必须要精确的去决定,何时启动动画结束,何时循环开始时,并不能很好的去决策制作。也就是说,决定的并不是你实际像要做的结果。
其实这个问题在15年以前就有人提出了Motion Graph。他的想法是通过一个很长动画的非结构化列表(Unstructuredlistof animations)。预处理方面,你可以在动画数据库里预处理,对每个位置都找到它可以跳转到的其他位置。这样就可以设置在姿势匹配的位置。如果姿势可以很好对应的话,速度也几乎相同的话,那么就有了动画的过渡点。
以下图为例,你的动画在任何时候都可以开始动画过渡,然后通过一个可能性的列表来决定移动到哪里。
那么可以在这些动画中漫游,并按图表中的路径提供动画。
接下来第3个问题是,如何选择下一个动画。
这里有一些方法可以选择的,其中一个是方法是利用机器学习的概念中增强学习(reinforcementlearning)的方法。通过预处理,可以观察一个动画过渡到特殊目标的表现是否足够好,为每一个需要转化的离散方向的目标进行预处理操作,譬如说你要往左转,那么将动画切成一小块一小块,然后判断每一小块到转向的变化是否足够好。
Michael Buttner在3年前的讲座里讨论过他在【杀手5:赦免(Hitman: Absolution)】中使用的相关技术。而他现在也在多伦多并受雇于育碧。他在工作室中开发他自己版本的动作匹配(MotionMatching),实际上在育碧里4到5个不同的项目组,也正在研究这类技术。
但这种方法的问题在于,为了能舒适的控制,需要非常高密度的图表,因为通常要有各种动画过渡的等待位置。所以并不能真正的立刻的切换到需要完成的动画。因为之前是离线的采样点,所以在真正切换前往往会稍微等一下,才会真正的变动画。这也是为什么这个管理只在NPC上使用,因为不能足够的反映主要角色。
所以,Clavet他们使用了Moition Field,Moition Field不是什么很酷的论文,是10多年前的了。文章的内容看起来很厉害,让你充满动手试试的欲望,文章提出了一种将运动数据泛化到高维矢量场的方法,被称之为motionfield。在实时计算的时候,根据用户输入在motionfield中随意合成即可。听起来这可以是一个解决方案。就像是一个计算每个可能目标的估值函数,来告知如何在动画组成的高维魔幻世界里游走。
这样就可以获得更多的反映,在下面视频的左边是图表方式使用静态图的解决方案(Graph),可以看到会出现我们讨论过的反应上的丢失,只有一部分时间角色会实际的跟随箭头。视频右边是运动场的版本(MotionField),有更多的运动响应,可以把动作融合在一起,并且快速的过渡,可以打断当前动作并和其他的动作混合,真正的是在运动场中流动。
那么这种方法的问题在哪里呢?运动场(MotionField)并没有像其他技术一样被广泛使用。当你读这篇论文的时候,会感觉这个听起来有些复杂,要达到真正需要效果所需的复杂度等级并不是你所像要的。这个技术感觉起来有些过度了,虽然能运作,但要让他可以运作实现起来太难了,这也可能是它没有使用到游戏里的原因。也可能是它性能问题或内存问题,但Clavet觉得主要还是因为他复杂度上的问题。所以要实现这些可能并不需要像它那样那么复杂的函数,可能是更加贪婪的方法,用我们所需要更加简单的方式来处理这些动画片段。 不需要像原文中使用那么复杂的距离函数,简单粗暴的解决即可。
所以,Clavet他们只是使用这篇论文的主要思路,实现方式非常简单但又很有效果。那就是需要的时候,可以随时跳转到任意其他的帧上。这其实就是最关键的核心点,只需要找到一些数据片段,就可以正确的播放动画了。
那么是最后的一个问题。
最后的一个问题是复杂的停止动画(stopanimation),最后一步细节或者最后发生的一些细节事务。那么,当要开始播放停止动画时,如何在停止动画里选择正确的开始时间?角色是循环播放动画来做相同的跑动,处于一个特定姿势,当这时要开始播放停止动画,需要在停止动画里找到一个看起来相相同的位置来开始播放。
首先要做的是,需要播放哪个停止动画,可能在游戏中你的版本是左脚向前,或者是右脚向前,那么可以在停止动画的最开始播放。当姿势匹配(posematch)时可以精确的开始播放动画。用这种方法当姿势匹配时动作开始和结束都会很精确。速度匹配(velocitymatch)上,因为有时你刚刚开始启动,正在播放启动动画时,刚进行到一半时,并不是全速在移动,这时要开始播放停止的动画,这时就不能播放完整的停止动画了,因为你的速度会边快,然后再减速,这样感觉起来不好。所以最好是可以推测速度,让停止动画在所对应的位置开始播放。
还有就是需要精确的停止位置的匹配,需要在游戏世界的一个精确位置上停下来。那么就要播放和开始走步,以保证可以到达正确位置。或者就是要把上面这些做混合,这就是为什么接下来你会看到,我们将评估动画中的不同权重,并根据代价(Cost)来找到最佳的动画。
接下来,是今天要正式介绍的MotionMatching,前面讨论了不同的版本MotionMatching,现在要清晰的介绍它实际工作和和它了不起的地方。
实现方法是非常简单蛮力的方式来做动画的选择。我们从这个方法出发,就可以看到这是非常简单的。
方法非常简单,就是在每帧查找所有的动补数据,并跳转到最佳的位置上。
实现上,每个候选的跳跃点都有一个成本值(cost),如果候选的点和当前的姿势完美的匹配,而且后面的动作片段就是我们想要的,那么它的代价就是0。这里计算cost的函数有两部分,像当前的姿势和速度,之后的动画片段内容也是要进行匹配的。
那么,在算法的输入上,是一组动作捕捉数据,测试动作是"verytired",真的要是full body的去匹配,很难分割成小的片段。所以Clavet他们不需要手动的去为启动,停止以及转身动画进行分段。这是尝试的第一步是。3年以前,Clavet只是直接把这些动补数据塞入到了游戏里,每段5~10分钟的动画。然后通过算法,每个相似帧的来选择小的动画片段来播放。 下面的视频是最后的结果,可以看到角色真的是在尝试跟随着红色的箭头。 下面的视频是关闭混合,可以看下切换时的动画,切换动作很大(跳帧)。但当我们切换时混合一些时间的话,表现就好多了。视频里后半部就是打开混合的,模型开始跟着变化的方向不断变动,有个渐变的过程。
下面视频是行走的示例,视频左边的一套行走的动作,右边的地面上,红色的路径是希望走的轨迹,通过Gameplay在每帧来传递这个信息,而蓝色的路径是当前播放的动画,可以看到每次蓝色路径切换了,就意味着动画切换了,
当我们要进行动作捕捉时,在游戏里要让它可以像前面那样工作的话,这里有很多的角色,不是只有一个日本武士,一个维京勇士和一个圣骑士,还有大量的其他角色。所以要增加更多的动作捕捉,要捕捉特殊风格的角色,例如女性版本的角色,随着我们做的越来越经验丰富,最后需要大概15或20分钟,一组镜头大概有6到7个左右。
下面的视频展示了剑兵的放松(relax)动作,以及转头的放松动作。用来做放松动作的导航。这里从一组原地转身(turnon spot)开始,按45度来捕捉。然后是中断开始动作(Interrupted Start),这个是从放下木棍开始的。
然后对于一组位置要进行一些重新的定位(Reposition),这里是没有转身因素,以确保可以获得游戏世界里的位置。 接下来是有位移的动作,这样就有了每个方向的启动和停止的动作:从45度(Plant45)开始(其实就是跑动过程中同时转45度),接下来是135,90,180。对于攻击动作来说,需要前进后退和左移右移,以及斜的前进后退(类似对角线)。
然后是一些代码的展示,这个实际上相当的简单,Clavet也相信听众可以回去试试。
那么要保存当前动画索引(CurrentAnimIndex)来指定播放什么动画,以及在动画中所处的时间(CurrentAnimTime)。然后每帧会调用称作AmoUpdate的更新函数,这个是在代码称作Amo的系统,因为Amo全称是automaticmotion。在函数里每帧传递一个Goal,以及dt(DeltaTime)来指示要前进多少。后面会介绍Goal的实际结构。Goal里是Gameplay的所有信息交代给动画去做的,是个很小的信息Pack,小的数据结构来告诉函数下一秒需要什么。
所以接下里,在当前的动画时间(CurrentAnimTime)里加上dt,然后来评估当前的姿势(Pose),这个pose实际上类似指定的动画索引在指定动画里时间的插值(Lerp)。在这个姿势时,也有一些数据的片段,可以让计算cost的函数更快。例如要Pose Matching,需要一些游戏世界里骨骼的位置,那么在这个姿势下,会预计算这些信息,后面将会提到Pose里更多的细节。不过要记住的是里面有我们计算Cost函数需要的所有信息。所以这里调用函数通过数据来评估,因为预计算的姿势,一秒预计算十次,相当于有一个10Hz的动作细节密度。然后在相邻动作直接插值,你也可以像普通的动画一样直接使用。
那么接下就是循环所有的Pose,并记录下最佳cost的数据和最佳的Pose数据,你要做的就是循环全部的数组,并评估每个Post作为候选pose,然后是候选Pose,你可以使用ComputeCost函数来评估候选Pose,提供当前Pose和候选Pose以及Goal来计算。如果ThisCost小于Bestcost,那么把它记录下来。大致上这个是怎么工作的介绍,稍后会提到它是如何组织的。逻辑实现上就是尝试去寻Cost更小的动画。
经验分享丨项目实践项目孵化丨渠道发行做有梦想的游戏人-GAME AND DREAM-