用合理的workflow制作复杂的角色动作控制系统

Last modified date

现在主流引擎点动画状态机虽然很灵活,但要真正用的好,还需要设计合理的流程和规范。

国内做纯ACT类型游戏的公司虽然不多,但是很多项目都有角色动作系统。我参与过的ACT类型的项目也有几个,例如以前英佩的《全球使命》《寻龙》,后来mihoyo的《崩坏3》,还有一些没能做完上线的项目就不说了。这些项目中的角色动作系统都是基于引擎自带的Animator动画控制器实现的,而且有个通病:就是太过依赖Animator,把很多逻辑判断的部分放在Animator中实现。而且上网能搜到的相关教程范例也是如此。以下是网上搜来的一些截图:

一团乱,不知所云。很难想象作者是如何维护的。

也有一些人为了能看清楚,把同类的模块工整的拼在一起的:


稍微好看了点,但依然是一团乱,而且每个状态都有来回跳转条件,依然难以想象作者是如何维护的。

上面几个例子,如果角色动作的设计有较大改动,可能这套状态机就要重做了。用这种方式做项目,很多时间浪费在“修理一个效率低下的破机器”上,有时候还会白忙一场。

我总结一下上述做法的问题:
  • 可视化的状态机虽然很直观,但是不适合制作复杂的逻辑。扩展性很差,越做越复杂,最后作者自己都看不懂,难以维护。
  • 某些复杂逻辑是很难用Animator编辑出来的,最后还是要放到程序逻辑中实现,这样就导致一部分逻辑依赖程序,一部分依赖状态机。依赖关系粘在一起,非常混乱。
  • U3D的Animation可以反过来触发事件,一旦用了这个就会增加新的复杂维度。逻辑判断,事件触发,动作执行全部杂在一起,就像一个作战部队要执行任务,司令员,党委,指挥部都要来各自发号施令,然后队伍里的士兵还要反过来给各位长官提意见。这种执行方式显然是极其混乱的。

我在英佩用UE做项目时,UE3自带一个TPS的demo,他的动作系统也是如上所述,直接把逻辑判断放在动画状态机里的。但是他的动作复杂度有限,这么做也没什么问题,但是如果要做复杂动作游戏呢?显然越做越暴露问题。例如我后来在《崩坏3》项目看到的,动画状态机的网状结构和连线条件让人眼花缭乱,做的人自己也未必能立马看懂。以至于后面几次版本更新(增加新的角色动作)繁出BUG。

现在大部分公司的项目都是用的Unity3d和UE4,这俩引擎的动画控制器是差不多的,也可以说Unity是完全抄UE的。这篇就基于Unity3D引擎来谈谈我设想的这套角色动作控制系统。

那么合理的动作控制系统应该是什么样的呢?我的解决方案是:简化动画状态机,尽量把逻辑判断放到代码层面。

首先说代码层面的问题,我用了MVC的思路,把数据,控制和表现完全分开。

  • Model包含角色的一切数据:
    • 角色基础数据:生命值,攻击力,体力,速度,等级,技能cd时间,等等。
    • 角色美术资源的引用:角色模型,动画,特效,音效,等等。
    • 角色状态数据:是否在移动,当前方向,拿的什么武器,等等。
  • Controller负责执行角色所有的行为:
    • Controller的数据来源于Model
    • 执行的结果返回到Model中的角色状态数据中。
  • View负责所有可视化的角色动作表演:
    • View的数据来源于Model的角色状态数据。
    • 由这些状态数据判断出角色当前该做什么动作,显示哪些内容。最终通过Model中的角色美术资源引用显示出来。
这么做的好处是:
  • 以上流程都是单线执行,分工很明确。不会出现胶水代码。
  • 其中Model和Controller可以单独运行逻辑,不必等到美术资源加入。而View也可以搭配简易的测试数据单独运行,可以拆出来专门做个美术测试Demo。
  • View中执行了角色所有状态的逻辑判断,最终输出到animator状态机里的状态ID就变的很简单,一般就是一个唯一ID。这样就不必在animator中编辑超级复杂的跳转条件了。
  • 策划配数值的时候,可以不依赖美术表现。例如角色连招的时间间隔参数,应该由策划和QA实际一边玩一边测试出来,而不是单纯靠动画做成什么样就填什么数值的。这些和玩法相关的数值必须是反复测试验证出来的。用我的做法,就可以让策划在Model中自由配数值,然后用demo的素模直接测试,不一定要等动画改美术资源。反过来说,美术可以先做一版demo美术资源,能应付策划测试的需求即可,等策划QA测出足够满意的数值,再回头修改一版美术资源就ok了。大家都省事,效率也高。

MVC还有很多其他程序上的优点,这里就不赘述了。下面来看看动画状态机的工作流。

由于刚才View部分输出了最简易的状态ID给animator,那么animator可以用最灵活的方式来编辑:就是直接从any state 连到所有的状态中。如下图:(这是我做的一个简单demo里的角色Animator,运行效果在文章结尾的Demo链接里可以看到)

  • Controller和View已经把所有的状态判断问题解决了,根本不用担心动作会串。animator基本变成了一个傻瓜播放器,你说什么我就播放什么,每个状态都有对应的唯一的动作。
  • Animator的连线基本变成了单一的放射状连线。我们可以把相关的动作排在一起,让状态机逻辑一目了然,维护很方便,后期可以随意加新动作,不必担心会搞坏之前的动作系统。
  • 由于所有的状态跳转条件都只有一个参数,所以在运行时直接手动改参数就能看到动画跳转效果,这样美术在做动作的时候可以不依赖程序,不用等程序专门开发出测试环境就能快速看到美术表现效果。程序也省得分散精力应付美术的临时需求。
最后有几点细节要注意的。
  • Animation动画最好避免插入事件。虽然Unity提供了这个功能,但实际上这是个很糟糕的用法。一旦用了就会让美术和程序粘在一起。用我的流程,所有逻辑相关的问题都在Model和Controller里解决了,美术不用管任何逻辑相关的问题。
  • 如果角色需要有上半身下半身分开做不同的动作,就建2个layer,用2个参数ID分别控制其状态。注意Animation动画里不要穿插控制身体部位。
  • 不排除有些动态效果是要配合程序才能做出来的。但只要是不牵扯玩法逻辑的东西,都可以归为纯美术类型的资源。而且有些效果是非常终端的,不跟任何其他资源挂钩,这种就可以硬编码做一下,因为该起来也不麻烦,也不影响其他工种。
好了,我再梳理一下整个流程:
  • 首先,要把逻辑判断的部分都放在程序那边实现。因为逻辑判断不属于美术的工作范畴,必然应该由程序来负责。任何逻辑状态判断相关的问题出错也只能找程序来解决。美术应该专注在最终表现效果上,所以剥离掉逻辑判断的动画状态机会更适合美术使用。
  • 其次,程序内部的工作也应当划分清楚逻辑部分和表现部分。例如我之前提到的Model部分的“角色基础数据”和“角色状态数据”是不能有交集的,并且执行流程最好是单线。
  • 最后,这整个工作流应该非常清晰,应该能做到任务职责划分明确。当然真实开发的时候,流程中肯定会有一些小问题,这时候就是TA出场的时候。

我以往碰到的很多项目都是由TA来制作动画状态机,但是实际情况是负责这个任务的TA最后忙的不可开交,大量的时间放在“怎样用Animator搭建出想要的动画效果,并且保证整体不出错”这个揪心的问题上。最终要么做不出来,找个其他的表现方式妥协;要么就是就是勉强做出来了,但是跟之前的动作或多或少有冲突隐患。这两种情况都免不了加班劳神。TA的工作应该是在一个合理的工作流的基础上研究如何提升效率,提高美术品质。而不是在一个漏洞百出的机器上修修补补,最终运行效率和美术质量不断为现实妥协。

另一个问题是,程序会觉得多了很多工作,因为角色状态的逻辑判断丢给了程序。但其实仔细想想,作为程序员都知道可视化编辑器是难以制作复杂逻辑的,而用代码搞定角色的状态判断是小菜一碟,并且程序员可以宏观的考虑如何配置角色状态数据,可以把任务做到一步到位。

最后有人会说:如果项目设计大改,你还不是一样要重做么?对,项目迭代是正常的,用我这套流程,开发过程会简单明了,省掉了不必要的精力浪费。当设计修改时,对于动画师来说,直接在状态机上增减节点即可,不必担心影响之前的功能;对于程序来说,无论如何你都是要重写代码的,那么改动大小就取决于程序的代码框架是否足够灵活了,这是另一个问题。

我这套开发流程在我之前做独立游戏的时候验证过,而开发流程也是轻松愉快的。希望这篇文章能帮到ACT游戏的开发者。

最后附上一个我使用这套流程开发的小Demo:
  • 按住鼠标右键可以控制视角上下左右旋转
  • 鼠标左键攻击(可以3连击,任意时刻按住左键可蓄力攻击
  • WASD键移动(按住Left Shift键可加速移动),X键防御,C键冲刺
  • Space键跳跃(可二段跳)