《全民XX》AI演进史
《全民XX》AI演进史
《全民XX》是一款寻宝冒险题材,多角色俯视角自动战斗玩法的Unity3D手游。玩家可以控制五个角色的整体移动,每个角色还有一个主动技能,由玩家主动释放。以下是《全民XX》的交互设计,它大概反映了玩家的操作方式:
从战斗开始说起
《全民XX》其实是一款弱操作的手游,玩家不进行任何操作,角色也可以自动移动到敌方单位旁边,并开始攻击。下面先介绍下战斗的相关点:
1.每个角色都有一个“警戒距离”,当“警戒距离”内有敌方单位时,自动移动到最近的敌方单位旁;
2.“攻击启动距离”。顾名思义,当敌方单位距离自己的距离小于这个值时,才可以启动普通攻击;一般来说“警戒距离”>“攻击启动距离”;
3.“有效攻击距离”。对于近战角色来说,它就是用来计算伤害的距离;对于远程角色来说,则表示飞行物的最远飞行距离。一般来说“有效攻击距离”>=“攻击启动距离”,以免出现打不到人的情况。
最早的AI中所有角色都是根据自身的警戒范围去寻找最近的可攻击单位,但实现完后经常发现有的角色在战斗,而有的角色则由于警戒范围达不到的原因,傻站在那。最初我们期望通过增加“警戒距离”来减免这种情况,但实验多次后发现还是达不到预期,于是我们不得不去思考有没有更智能的方式;
简单来说,即当有角色在攻击敌方单位时,其他角色也将这个敌方单位作为攻击目标。但这个方案有两个问题:
1.所有角色都集火攻击同一个目标不是我们期望的;
2.仇恨传递机制从规则制定到代码实现都是一个比较复杂的过程;
一定还有更简单更直观的方式,于是我们继续想啊想,突然有一天,我们灵光一现,于是有了下面的方案。
全队警戒和自身警戒的区别在于:当一个角色需要寻找攻击目标时,它不再是在自身的警戒范围内寻找目标,而是以所有角色分别为中心,分别加上一个以自身警戒范围为半径的圆,寻找这些圆圈内离自身最近的敌方单位为目标攻击之。
很直观的,摇杆就是用来控制角色移动的,但是玩家需要同时控制五个角色的移动,这五个角色要怎么移动呢?
在开始介绍我们的整体移动策略之前,需要对以下相关点作个介绍:
1.我们有一个主控角色(以下称为主角),它始终处于屏幕的正中间,其它角色称为从角;
2.对于从角来说,有个统一的“最大远离主角距离”(MAX_DISTANCE_TO_LEADER),当从角距离主角距离大于这个值时需要触发回位机制,以移动到主角附近一定距离内;
3.这个距离叫作“跟随距离”(FOLLOW_DISTANCE),它是小于MAX_DISTANCE_TO_LEADER的;
4.不仅从角超出MAX_DISTANCE_TO_LEADER时要回到FOLLOW_DISTANCE内,当从角无事可做时(即无敌方单位需要攻击)也需要回到FOLLOW_DISTANCE内。当然为了避免从角在FOLLOW_DISTANCE这个临界位置上忽走忽停,我们让从角回位时需要回到FOLLOW_DISTANCE/2的位置。FOLLOW_DISTANCE的设计是为了让五个角色作为一个整体,不要离的太远;
5.当玩家移动摇杆时,回位机制取消。
下面就介绍下我们尝试过的方式。
这种模式下,当玩家移动摇杆时,主角立即响应移动(如果正在攻击中或者正在释放技能中也会立即被打断),从角则等到超出MAX_DISTANCE_TO_LEADER(攻击中的从角)或者FOLLOW_DISTANCE(无事可做的从角)时自动触发回位机制。
l 优点:这种方式的优点是逻辑简单,代码量小;
l 缺点:但它的缺点却是致命的,实际中会发现从角不能够及时响应玩家的移动操作,需要玩家把主角拉到很远才开始移动,通过尝试修改MAX_DISTANCE_TO_LEADER和FOLLOW_DISTANCE无果后,我们尝试了下面的移动模式。
这种模式下,当玩家移动摇杆时,所有从角立即响应移动。这种模式也是其他多控制角色游戏(如《僵尸别动队》)采用的方式。
l 优点:这种方式可以让所有角色立即响应玩家的移动操作了,实现起来也还比较简单;
l 缺点:大老板窝总说这样所有角色朝向显得太统一了,不像一个个独立的角色,显得不够自然。而且这种方式可以将某些角色卡在一些有障碍物的地方走不掉,导致各角色可以被拉得很远,目前没发现这样有什么好处。
前面的说的全控模式只是在每个角色身上绑了个CharacterController组件,当玩家移动摇杆时,直接调用CharacterController.SimpleMove方法,非常简单。所以至此,我们改称之为“无AI的全控模式”。
带AI的全控模式是这样的:当玩家移动摇杆时,从角不再通过CharacterController立即移动,而是通过寻路组件NavMeshAgent待完成当前动作(攻击、释放技能)后寻路到距离自己前方一定距离的点,并在每隔一段时间后更新这个点。
l 优点:从角的移动显得十分自然了,甚至都可以自动绕过障碍物了;
l 缺点:所有从角都需要寻路,增大了计算量。不过实际测试中发现这部分的开销基本可以忽略。
由于不同角色的攻击启动距离不一样,所以有的角色会站在离敌方单位比较近的地方攻击,有的角色会站在离敌方单位比较远的地方攻击,由于每个角色身上都挂了CharacterController和NavMeshAgent组件,这就会出现有些角色寻路是会将已经站立不动的角色挤开的问题,而且这个挤开速度会比较慢,大大影响了角色的移动速度。
对于这个问题,我们想到有两个方向来解决,第一个方向是让角色更快速的挤开其他角色,第二个方向是让角色绕过挡在前面的角色。但是不幸的是第一个方向没找到解决办法,于是我们开始沿着第二个方向寻找解决方案。
最开始我们尝试了在每个角色身上挂一个NavMeshObstacle组件,这个组件的作用是将寻路网格抠掉一块,这样所有的寻路角色就会自动的绕过这一区域。但不幸的是NavMeshAgent和NavMeshObstacle共存的话会出现严重的问题,导致角色在屏幕上快速的到处窜,其原因是:NavMeshObstacle将自己脚下的一小块区域从寻路网格里抠掉后,下一帧NavMeshAgent组件又会自动的将角色调整寻路层上,如此反复,这就导致了角色的屏幕上窜来窜去。
第一个方法不可行后我们想到了第二个方法,由于Unity3D里可以针对不同的对象设置不同的寻路层,于是我们想到是不是可以对每个角色设置一个不同的寻路层,然后将每个角色身下的一定范围从其他角色的寻路网络上抠掉,这样各个角色就不会挤来挤去了。这个方法是可行的,但实现成本非常高,可以先搁置一旁,想想有没有更简单的方式。答案是:我们想到了!
第三个方法其实在第一个方法上作了改进。在第一个方法中,NavMeshAgent和NavMeshObstacle不能共存,既然如此,那我们就不让它们同时存在呗。考虑这样一个问题:NavMeshAgent什么时候是必须存在的?答案是寻路的时候。那么反过来,当角色停下来的时候不就是不需要NavMeshAgent的时候了么!因此,我们可以在角色需要寻路的时候启用NavMeshAgent、禁用NavMeshObstacle,在角色停下来后禁用NavMeshAgent、启用NavMeshObstacle。现在,角色就可以绕开其他已经站立不动的角色了,大功告成!