Unity3D RPG Core | 12 找到 Player 追击

这节视频实现两个功能:一个是游戏视角的变更,使其更接近传统 3D 游戏的操作;第二个是让敌人能发现角色进入攻击范围。

1. FreeLook Camera

Cinemachine 中的 FreeLook Camera 能实现自由视角的功能。我们先要取消之前的 Virtual Camera,接着创建 FreeLook Camera。

如图 1 所示,FreeLook Camera 中也有 Follow 和 Look At 选项,我们将角色对象拖拽其中。

图1 FreeLook Camera 设置

图 1 设置中,还可以指定如何控制 X 轴和 Y 轴。如 Input Axis Name 所示,当前的 X 轴和 Y 轴是通过鼠标左右移动和上下移动控制的。

现在我们想把 Y 轴通过鼠标滚轮控制;X 轴通过键盘按键控制。对应的 Input Axis Name,可以在 Edit - Project Settings - Input Manager - Axes 中的各项进行查看。此处,我们将 Y 轴的 Input Axis Name 设置为 Mouse ScrollWheel,X 轴设置为 Horizontal

Edit - Project Settings - Input Manager - Axes 中可以查看各输入名称。可以直接复制以防拼写错误。

图2 Rig

在图 2 画面中,我们可以看到有三个高度不一、大小不一的圈,它代表相机拉近拉远的视角高度和范围。这些参数可以在图 1 中的 TopRig、MiddleRig 和 BottomRig 选项中进行设置调节。

图3 最终效果

在运行时,勾选 FreeLook Camera 中的 Save During Play 选项,可以一边运行一边调试参数:比如 Y 轴滚轮没有效果,可以把 Y 轴的 Speed 参数调成合适值;或者三个圈的参数不满意,都可以边玩边调。最终拉近拉远和旋转视角的效果如图 3 所示。

2. 敌人发现角色

接下来需要实现的功能是要让敌人在“侦察”范围内能发现角色,以便实现后续的追击功能。如代码清单 1 中第 20 行所示,我们定义 m_sightRadius 代表敌人的“侦察”范围半径。

代码清单 1 敌人发现角色
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.AI;
  5.  
  6. public enum EnemyStates
  7. {
  8.     GUARD,
  9.     PATROL,
  10.     PURSUIT,
  11.     DEATH,
  12. }
  13.  
  14. [RequireComponent(typeof(NavMeshAgent))]
  15. public class EnemyController : MonoBehaviour
  16. {
  17.     public EnemyStates m_enemyStates;
  18.     NavMeshAgent m_navMeshAgent;
  19.  
  20.     public float m_sightRadius = 5;
  21.  
  22.     void Awake()
  23.     {
  24.         m_navMeshAgent = GetComponent<NavMeshAgent>();
  25.     }
  26.  
  27.     // Start is called before the first frame update
  28.     void Start()
  29.     {
  30.        
  31.     }
  32.  
  33.     // Update is called once per frame
  34.     void Update()
  35.     {
  36.         SwitchState();
  37.     }
  38.  
  39.     void SwitchState()
  40.     {
  41.         switch (m_enemyStates)
  42.         {
  43.             case EnemyStates.GUARD:
  44.                 if (HasFoundPlayer())
  45.                 {
  46.                     Debug.Log("HasFoundPlayer");
  47.                     m_enemyStates = EnemyStates.PURSUIT;
  48.                 }
  49.                 break;
  50.             case EnemyStates.PATROL:
  51.                 break;
  52.             case EnemyStates.PURSUIT:
  53.                 break;
  54.             case EnemyStates.DEATH:
  55.                 break;
  56.         }
  57.     }
  58.  
  59.     bool HasFoundPlayer()
  60.     {
  61.         Collider[] colliders = Physics.OverlapSphere(transform.position, m_sightRadius);
  62.         foreach (Collider collider in colliders)
  63.         {
  64.             if (collider.CompareTag("Player"))
  65.                 return true;
  66.         }
  67.         return false;
  68.     }
  69. }

上一节视频中已经讲到,敌人的状态有守卫、巡逻、追击和死亡这四种(第 6 行定义)。关注到 SwitchState() 函数(第 39 至 57 行),这边其实是有限状态机的切换逻辑。为了方便且所要实现的逻辑并不复杂,这边不使用设计模式,把状态和转移条件“揉”在一起。

如第 43 至 49 行所示,如果敌人守卫状态下发现了角色,就会转到追击状态。发现敌人的实现见 HasFoundPlayer() 函数(第 59 至 69 行),它调用 Physics.OverlapSphere() 函数,计算得出敌人处指定半径范围内的所有碰撞体。接着我们遍历检查各个碰撞体,如果发现有角色人物,则代表找到。

有限状态机注意明确状态和转移条件。

敌人的逻辑已经写好了,我们还需要回到 Unity 界面中进行余下的设置。如图 4 所示,逻辑中查找角色是通过 Player 标签确定的,这边我们修改人物的标签为 Player(自带的,不用额外添加)。同时代码中检测的内容是碰撞体,目前角色上还没有碰撞体,这边我们为人物添加一个胶囊碰撞体,并设置好大小参数。

图4 角色人物对象设置

最后,我们可以运行游戏进行功能确认了。代码清单 1 第 46 行中,我们已经通过 Debug.Log() 打印日志确认敌人发现角色人物。所以我们控制角色人物走近敌人,并观察控制台窗口。如果如图 5 所示这样,打印出了指定日志,就代表我们的功能实现成功了。

图5 日志确定