上回说到,游戏项目中客观会遇到逻辑状态的复杂性和动画状态的单一性之间的矛盾,那么Animation Tree是如何解决这个问题的呢?
这又需要引入一个定律:就是逻辑状态无论有多么复杂,但一套逻辑状态组合一定唯一对应一个具体的动画。
举例来说:已知控制当前游戏对象的逻辑状态有是否技能中、是否受击、是否中毒、是否眩晕。
那么我们可以建立一个下面的关系:
技能中 | 否 | 是 | 否 | 否 | 否 | 否 | 是 |
受击 | 否 | 否 | 是 | 否 | 否 | 否 | 是 |
中毒 | 否 | 否 | 否 | 是 | 否 | 是 | 是 |
眩晕 | 否 | 否 | 否 | 否 | 是 | 是 | 否 |
最终动作 | 默认站立待机动作 | 当前技能动作 | 当前受击动作 | 中毒待机动作 | 眩晕动作 |
眩晕动作 (设眩晕动画优先级高于中毒) |
技能动作 (设技能动作优先级高于受击) |
给定每个逻辑的输入,最终的动画结果基本一定是唯一的。
这就是Animation Tree的原理。把整个角色动画,依照不同的逻辑单元组合,最终在给定既定的逻辑取值的情况下,能得到唯一的动画结果。无论是使用什么具体的方案,万变不离其宗。
这中间有几个需要注意的问题:
1、优先级,这个优先级一般与逻辑无关,只是动画本身的优先级,例如上表中的倒数第二列,中毒和眩晕全都触发时,一般眩晕的优先级会高于中毒的优先级。再如最后一列,当中毒和技能同时触发时,技能有限级一般会高于中毒优先级。这就意味着眩晕和技能条件的判断要早于中毒的判断,并享有对整个树的优先处理权,眩晕条件一旦达成,其之后的中毒条件甚至根本就不用去判断了。
2、融合,角色当前属于眩晕动画状态,眩晕解除后,一般不会直接跳转到接下来的状态,而是需要走一个动作融合,这时需要注意接下来到底要转到哪个状态的问题。表面上看,人们不可能知道一个状态解除后接下来会转向什么状态,但实际上在既定的项目中,这一点是可以做到的。
3、逻辑状态本身的互斥性,例如,上表中,不太可能出现眩晕和技能同时存在的情况,因为眩晕的目的旨在剥夺玩家的行为控制能力。在制作具体的Animation Tree时可以灵活根据这些条件进行一些优化和优先级的合理排布。
Animation Tree,目前接触过UE3的Animation Tree思路和CE3的Animation Graph思路。这两个思路是分别从两个不同的出发点入手解决同一个问题。
UEAT的出发点是:逻辑状态无论有多么复杂,但一套逻辑状态组合一定唯一对应一个具体的动画,也就是说,无论AT有多么复杂,最终的结果一定是单一的。这种具有单一结果的情况,一般人们应该都会选择树来做为数据结构。所以,UEAT看起来像下面这个样子(图片来自UDK官网):
4中,被一堆绿色节点包围的那个节点,就是UEAT的根,所有的条件依照这个根以树状排布。
AG不是特别熟悉,这里只是根据笔者现在的经验来写,可能会有疏漏,望懂CE的大牛补充。
CEAG的出发点与UEAT不同:逻辑状态无论有多么复杂,但一套逻辑状态组合一定唯一对应一个具体的动画,所以CE把重点放在了这些状态组合上,AG的编辑界面里排布着大量的关联节点,每个节点的进入条件就是状态的各种组合,从一个节点跳转到另一个节点,如果有路径,就顺路径混合,否则直接按照节点混合直接跳到目标节点(图片来自CE官网):
上图是节点间的转移路径,下图是某个单一节点的条件,一个是Action条件,一个是当前角色速度的条件。
个人推荐使用UEAT的方案,图表一旦大起来的时候,树状逻辑组织的可读性显然高于图状逻辑组织,而CEAG更糟糕的是将节点间的不同性隐藏在了节点内部的属性上,不把节点全部点开一次你是不可能明白这张图到底想干嘛的,而UE则直接从树根追溯到叶子就知道这张图是怎么回事了。但是UEAT的缺点是任意两个动画叶子之间的Blend并不是特别好设置,因为叶子间的切换,需要首先追溯到两个叶子的共同树枝,然后根据这个共同树枝来进行混合,如果有下面这种情况,就很难对付了。
顺便做个广告,Surface Pro的1024级压感电磁笔爽死了,谁用谁知道。
如图所述,1到3、1到4的公共根是Blend节点,节点Blend时间0.1s,所以如果要这两个路径的Blend区分,这里的处理就需要麻烦点。AG处理这个只需要走两条不同的Blend路径就可以。
但是笔者仍然推荐AT,道理很简单,可读性太重要了,而且Blend这种看起来对就是对的的坑爹玩意儿,一般不会有人揪着0.1和0.3不放的,真需要叶子间那么精确到0.1和0.3的可能性不是很大,即便有了,也可以通过回调或者自定义等手段避免掉。但是,学AT的时候,半个小时就明白这玩意儿是怎么回事了,学AG的时候,把整个例子的所有节点走一遍就是一上午,还没来得及排布各个条件的优先关系。AG这种思路太过于直接地考虑程序的实现性了,而忽视了整个体系的方便性,感觉像给自己用的东西,不像给人用的东西。
逻辑状态中最重要的关系就是优先级关系,各个不同体系的逻辑条件,其优先级基本直接决定了最后的结果,所以,像UEAT这样的组织方式,最直接地表述了这样的关系,必然产生的结果就是优良的可读性和可控性。
关于AT的组织和概念就基本上差不多这样了。
基于AT的基本原理,用户可以定制各种辅助的条件和Blend模式来达成自己的目的:
例如速度。
可以把当前角色的相对朝向速度作为条件导给AT,如果速度是正前方的,则角色播放正前方跑步动画,如果是正后方的,则播放后退动画,如果是左右的,则播放向左跑向右跑动画(不是直接转朝向)。如果有前有左呢?在当前节点混合吧!这样就很容易地处理了方向移动的混合问题。与此相同,游泳、飞行这种可能带方向的需求都可以这么做。还可以根据速度的大小,进行走和跑的切换。
例如头部一直向着某目标(Look IK)。
可以把角色的Look IK做成专门的节点,并制作一个专门的处理头部与身体混合的节点,这样,下半身该什么操作还什么操作,跟头部合理融合就行了。
上下半身融合可以采取同样的机制来处理。
在AG里,这么做可就痛苦了,因为AG是既定输入得到既定的唯一结果,而得到两个结果,并使这两个结果之间能够混合……稍微会有点麻烦。
诸如此类的桥段还可以想出很多,再举一个特殊的:
技能动作成百上千,难道全都要放在AT里面去连吗?
不可能,那样的话整个图会变成什么样子呢?设想一下几千个节点,各种连线,这已经超越蜘蛛网,甚至超越乱麻,达到神经网络的层次了。
技能动作再多,其优先级相对却是很简单的,大部分技能优先级一般都高于走跑跳,小于击倒、击飞、眩晕什么的。扩展一个节点,这个节点的动画并不是直接填写在节点里的,而是可以从技能系统中取得当前的技能动画就可以了。上下半身分离技能什么的,可以采用类似的机制来做。
总而言之,AT是个非常强大的概念,使用这套系统基本上就可以做到把程序从繁重而且毫无意义的动作状态机编制过程中解放出来了。而且,您难道不觉得“这玩意儿本来就该这样”吗?逻辑和表现分离,就正如M和V分离一样天经地义,有时候反而搞不明白为什么动作状态表这样的奇葩思路在中国居然还这么流行。
尽早集成,尽早使用,您一定会体会到它彪悍之处的。
希望下个项目的时候,笔者不会再遇到长1000行宽1000行的动作状态表这样的坑爹玩意儿了……
(完)