这篇内容将完善敌人的巡逻逻辑。
1. 定义巡逻范围
首先和敌人的监视范围类似,我们需要定义敌人的巡逻范围。目前存在一个问题,我们无法感知这些范围,以便进行调整。为了解决这个问题,视频中介绍可以使用 Gizmos 将范围画出来。
如代码清单 1 所示,我们在 OnDrawGizmosSelected() 函数中进行 Gizmos 绘制。'Selected' 表示只有在界面上选择了这个对象,才进行绘制,可以使画面不会太杂乱。
写好代码后,我们返回到 Unity 界面。选择史莱姆对象,就可以看到如图 1 这样,画出了范围球体线框。
2. 实现巡逻逻辑
巡逻的整体逻辑是:基于敌人的初始坐标上,随机生成巡逻范围之内的偏移,并使敌人走到这个点上。前往这个点的过程中播放行走动画;走到了这个点时,播放待机动画,并计算新的随机点。
我们看到代码清单 2,再次梳理上述逻辑。首先看到随机点生成的 GetNewPatrolDestPoint() 函数,x 和 z 坐标计算基于初始坐标的随机偏移,y 坐标不变,防止所在地形高度不一。
SwitchState() 函数中的 EnemyStates.PATROL 分支是巡逻逻辑的主要实现。巡逻时速度相对于追击时减少,isPursuing 变量设置为 false,让动画层在基础层(第 48 至 49 行)。判断是否走到对应点使用 Vector3.Distance() 函数,注意设置的阈值,调试中发现距离不会到达 0,只会无限接近 0。
注意 Vector3.Distance() 的判断阈值。
如果到达位置点,则播放待机动画,并计算新的随机点(第 54 至 55 行);如果没有到达,则播放行走动画,并设置 NavMeshAgent.destination。
2.1 随机点在不可行走区域
代码清单 2 中的 GetNewPatrolDestPoint() 函数还存在问题:如果生成的随机点在不可行走区域,则敌人永远走不到那里,就会出现卡住的现象。关于区域的概念可以看 《Unity3D RPG Core | 04 智能导航地图烘焙》 重新温习一下。
解决以上问题,可以使用 Unity 的 NavMesh.SamplePosition() 函数。其定义为:
- bool SamplePosition(Vector3 sourcePosition,
- out NavMeshHit hit,
- float maxDistance,
- int areaMask);
函数的作用是,在 sourcePosition 中心点的 maxDistance 范围内,寻找满足 areaMask 掩码定义区域要求的最近的点。如果找到,则返回 true,hit 中附带点的信息;如果没有相关区域,则返回 false。
视频中说 areaMask 为 1 代表可行走区域,但是文档里没有找到定义。留作问题。
还有一个问题是不可行走区域是否也能寻找。
完善过后的 GetNewPatrolDestPoint() 函数如代码清单 3 所示,如果找不到可行走的点,则将随机点设置为当前位置,等待下一次获取。
2.2 让敌人走走停停
我们继续完善敌人的巡逻逻辑:让敌人走到随机点之后,张望一会,然后再走到下一个巡逻点,模拟真实的巡逻场景。
我们在动画控制器中添加一个张望的动画,并定义相应的转移变量 Sensing。如代码清单 4 所示,添加相应变量的更新(第 9 行)。
定义需要停留张望的时间和已经停留的时间变量(第 3 至 4 行)。当走到随机巡逻点后,在原先逻辑上更新停留时间相关变量,并播放张望动画。
最终巡逻张望的效果如图 2 所示,敌人到巡逻点后会张望一会,再到下一个巡逻点。
3. 完善转移状态转移逻辑
目前状态转移方面还剩下两个大问题:一是巡逻状态下还没有发现角色的逻辑;二是追击状态下,脱战时敌人需要回到原始的区域。
如代码清单 4 所示,第 17 至 22 行添加巡逻状态下的发现角色逻辑,发现角色后转移到追击状态。第 65 至 77 行添加脱战逻辑,脱战时敌人也停留一段时间,然后切换到原先的守卫或者巡逻状态。