Unity自学之旅05
- Unity学习之旅⑤
- [📝 AI基础与敌人行为](#📝 AI基础与敌人行为)
- [🤗 总结归纳](#🤗 总结归纳)
Unity学习之旅⑤
📝 AI基础与敌人行为
🥊 AI导航
理论知识(基础)
一、导航系统的基本组件
- NavMesh(导航网格) :
- NavMesh 是 Unity 中 AI 导航的基础,它是一个表示可行走区域的网格。它将场景中的可行走表面(如地面、楼梯等)进行三角剖分,形成一个平面的网格,用于代理(Agent)导航。
- 生成 NavMesh 的步骤:
- 首先,需要将场景中的几何体标记为静态(Static),以便 Unity 可以将它们纳入 NavMesh 的计算。
- 然后,打开 Navigation 窗口(Window -> AI -> Navigation),在 Bake 选项卡中调整设置,例如 Agent Radius(代理半径)、Agent Height(代理高度)等,这些设置决定了代理在场景中占用的空间大小和形状。
- 点击 Bake 按钮,Unity 会生成 NavMesh,显示为场景中的蓝色网格。
- NavMeshAgent(导航网格代理) :
- 这是附加在游戏对象上的组件,用于让对象在 NavMesh 上移动。
- 主要属性:
- Speed:代理的移动速度。
- Acceleration:代理的加速度。
- Stopping Distance:距离目标多远时停止移动。
- Radius:代理的半径,用于避障和计算可行走区域。
- Height:代理的高度,决定了代理可以通过的最小空间高度。
- NavMeshObstacle(导航网格障碍物) :
- 该组件用于表示场景中的动态障碍物。当一个对象附加了
NavMeshObstacle
时,它会影响NavMeshAgent
的导航路径。 - 可以设置为
Carve
模式,这会在 NavMesh 上动态地修改 NavMesh,使代理能够绕开障碍物。
- 该组件用于表示场景中的动态障碍物。当一个对象附加了
二、导航行为和逻辑
- 路径规划 :
NavMeshAgent
会自动根据 NavMesh 为代理规划从当前位置到目标位置的最短路径。它会考虑障碍物和可行走区域。- 可以使用
agent.remainingDistance
来检查代理离目标的剩余距离,使用agent.pathStatus
来查看路径的状态,如是否完成、正在计算或被阻塞。
- 避障 :
- Unity 的导航系统会自动处理代理之间和代理与障碍物之间的避障。通过设置
NavMeshAgent
的属性,如Radius
和Avoidance Priority
,可以调整避障行为。 - 代理之间会根据彼此的
Avoidance Priority
进行避障,较低优先级的代理会主动避让较高优先级的代理。
- Unity 的导航系统会自动处理代理之间和代理与障碍物之间的避障。通过设置
- 动态导航 :
- 当场景中的障碍物或目标位置发生变化时,
NavMeshAgent
会自动重新计算路径。 - 例如,如果要在游戏运行时改变目标位置,可以调用
agent.SetDestination(newDestination);
,其中newDestination
是一个Vector3
类型的新位置。
- 当场景中的障碍物或目标位置发生变化时,
开始实践
window→package manager 选择 Unity Registry 然后搜索 AI Navigation

window→ai→navigation(obsolete) ,这里是因为我所使用的Unity版本那个AI Navigation static 已经弃用了,我更改了一种使用方式。

选择Environment,然后为点击Inspector中的Static,接着将Environment的对象及其子对象都设置为Navigation Static.

点击Bake→bake

为Enemy添加NavMeshAgent,让敌人可以在NavMesh上面行走。

绘制几个路径点,用于敌人的自动巡逻。


引用巡逻点,编辑EnemyBehavior脚本
csharp
// 巡逻点的transform
public Transform PatrolRoute;
// 四个巡逻点的transform集合
public List<Transform> Locations;
void Start()
{
// 脚本运行时,初始化巡逻路径
InitializePatrolRoute();
}
void InitializePatrolRoute()
{
foreach (Transform t in PatrolRoute)
{
Locations.Add(t);
}
}

让Enemy跟着巡逻点动起来,在此编辑脚本
csharp
// ...
// 默认第一个索引为0,即先走第一个巡逻点
private int _locationIndex = 0;
// NavMeshAgent的引用
private NavMeshAgent _agent;
void Start()
{
// 获取Enemy对象上的NavMeshAgent组件的引用
_agent = GetComponent<NavMeshAgent>();
// 脚本运行时,初始化巡逻路径
InitializePatrolRoute();
MoveToNextPatrolLocation();
}
// 移动到下一个巡逻点
void MoveToNextPatrolLocation()
{
_agent.destination = Locations[_locationIndex].position;
}
//....

现在只会移动到第一个巡逻点,然后我们的目的是,在每个巡逻点,循环往复的进行移动,这个时候就需要在Update()中搞点事情了。
csharp
void Update()
{
// 检测当前对象与目标位置距离
if(_agent.remainingDistance < 0.2f && !_agent.pathPending)
{
MoveToNextPatrolLocation();
}
}
// 移动到下一个巡逻点
void MoveToNextPatrolLocation()
{
if(Locations.Count == 0) return;
_agent.destination = Locations[_locationIndex].position;
// 索引循环往复
_locationIndex = (_locationIndex + 1) % Locations.Count;
}

🎃 敌人游戏机制
敌人按照巡逻路线进行巡逻,当与玩家碰撞后,减少玩家生命值,然后敌人回到初始位置,玩家也可以攻击敌人,减少其生命值。
追踪玩家
当玩家进入敌人警戒范围时,敌人会直接奔向玩家。
csharp
// 玩家的Transform引用
public Transform Player;
void Start()
{
// ...
// 获取Player对象的引用
Player = GameObject.Find("Player").transform;
}
// ...
// 碰撞触发器
void OnTriggerEnter(Collider other)
{
if(other.name == "Player")
{
// 玩家进入到警戒范围后,敌人奔向玩家
_agent.destination = Player.position;
Debug.Log("玩家进入警戒范围");
}
}
攻击玩家
攻击玩家的逻辑很简单,当玩家与敌人发生碰撞时,玩家减少生命值,并且在GUI上显示
修改玩家的Playerbehavior脚本(敌人脚本也可以,因为碰撞是相互的)
csharp
// gameManager的引用,因为HP,收集道具数量都在该处存放
private GameBehavior _gameManager;
void Start()
{
// 将当前player的刚体组件获取并设置
_rb = GetComponent<Rigidbody>();
// 获取当前角色的胶囊碰撞体
_col = GetComponent<CapsuleCollider>();
_gameManager = GameObject.Find("Game_Manager").
GetComponent<GameBehavior>();
}
// 发生碰撞时
void OnCollisionEnter(Collision collision)
{
// 如果与敌人碰撞则HP-1
if (collision.gameObject.name == "Enemy")
{
_gameManager.HP -= 1;
}
}

这里的话需要注意一个问题,就是敌人身上的碰撞体为碰撞触发器,勾选了Is Trigger,然后直接通过Player的OnCollisionEnter()并不会触发,需要给敌人再加入一个碰撞体就可以了。

子弹碰撞
为Player添加反击手段,逻辑就是敌人有三滴血,当子弹射击三次并且击中敌人后,敌人自动销毁。
编辑敌人Enemy的游戏脚本
csharp
// 敌人的HP=3
private int _lives = 3;
public int EnemyLives
{
get { return _lives; }
set
{
_lives = value;
if (_lives <= 0)
{
Destroy(this.gameObject);
Debug.Log("敌人死亡");
}
}
}
void OnCollisionEnter(Collision collision)
{
// 当子弹碰撞到敌人时
if(collision.gameObject.name == "Bullet(Clone)")
{
EnemyLives -= 1;
Debug.Log("遭受子弹射击");
}
}

完善游戏失败条件
逻辑是,生命值为0时,游戏暂停,出现一个按钮说:你输了。
先绘制一个Button按钮,和之前的一样,默认禁用即可,当生命值为0时,在脚本内调用。

编辑GameManager的脚本
csharp
public Button LossButton;
private int _playerHp = 10;
public int HP
{
get { return _playerHp; }
set
{
//...
if (_playerHp <= 0)
{
ProgressText.text = "You want another life with that?";
LossButton.gameObject.SetActive(true);
Time.timeScale = 0f;
}
else
{
ProgressText.text = "Ouch... that's got hurt.";
}
Debug.LogFormat("HP:{0}", _playerHp);
}
}

🤗 总结归纳
本文主要介绍了在 Unity 中实现 AI 导航以及构建敌人游戏机制的方法。包括导航系统的基本组件、导航行为和逻辑,以及敌人的巡逻、追踪玩家、攻击玩家、被子弹击中和完善游戏失败条件等功能。
重要亮点
- 导航系统基本组件:Unity 中 AI 导航的基础是 NavMesh,它将场景中的可行走表面进行三角剖分形成网格。NavMeshAgent 是附加在游戏对象上用于在 NavMesh 上移动的组件,NavMeshObstacle 用于表示动态障碍物。
- 导航行为和逻辑:NavMeshAgent 能自动规划从当前位置到目标位置的最短路径,处理避障和动态导航。例如,当场景中的障碍物或目标位置变化时,会自动重新计算路径。
- 敌人巡逻机制:通过为敌人添加 NavMeshAgent,设置巡逻点,编写脚本实现敌人在巡逻点之间循环往复移动。
- 追踪玩家:当玩家进入敌人警戒范围时,敌人会奔向玩家。通过在敌人脚本中检测玩家进入碰撞触发器,设置敌人的目标位置为玩家位置。
- 攻击玩家:当玩家与敌人发生碰撞时,玩家减少生命值。在玩家脚本和敌人脚本中分别处理碰撞事件。
- 完善游戏失败条件:当玩家生命值为 0 时,游戏暂停,出现 "你输了" 按钮。在游戏管理器脚本中根据玩家生命值控制按钮的显示和游戏时间的暂停。