Unity敌人自动寻路

基于NavMesh(导航网格)系统的自动寻路

(图为jump烘焙)

准备工作

(项目里有的各种地形,例如)

开始

——”我的资产“

插件AI Navigation

地面烘焙Ⅱ

在“层级”中创建AI——NavMesh Surface

or

找个物体挂载NavMesh Surface组件

设置好参数后烘焙

↓************************************************************************************************************↓

NavMesh Surface组件详情

*Agent Type(代理类型)

定义当前烘焙出的网格是以什么角色数值为基础烘焙的。角色身上的NavMesh Agent组件必须与此处的设置相匹配,才能在该网格上正确寻路。(可自定义角色类型与数值)

(图中角色是Humanoid,该角色高2米,半径0.5,可在45°坡上行走,能跨过0.75的落差)

*Default Area(默认区域)

设定烘焙出的网格默认具备什么属性。最常用的是Walkable(可行走),此外还有Not Walkable(不可行走,如悬崖)或Jump(跳跃区域)等。(封面是jump烘焙)

*Generate Links(生成链接)

作用:自动生成连接不同 NavMesh 区域的可行走路径(例如跨越缝隙的跳板)。

*Use Geometry(使用什么几何体)

——Render Meshes:使用美术看到的精美模型(如复杂的房屋)。缺点是烘焙慢,且 AI 可能会贴着装饰性的栏杆走。

——Physics Colliders:使用碰撞体。优点是 AI 会贴着物理边界边缘行走,且烘焙速度快,通常推荐使用此项。

Object Collection (对象收集)

-Collect Objects (收集对象)

——All Game Objects(所有游戏对象)

——体积(Volume)可设置烘焙大小,用于局部烘焙

——Current Object Hierarchy(当前对象层级)这是一种预制体(Prefab)内部烘焙模式。它只会抓取挂载了该NavMesh Surface组件本身的那个物体及其所有的子物体来参与烘焙。(你制作了一个包含楼梯和走廊的“塔楼”预制体。当你把这个塔楼拖入主场景时,你希望塔楼内部的敌人只在塔楼里寻路。此时将这个选项设为Current Object Hierarchy,塔楼的 NavMesh 就只基于它自己的模型生成,完美独立。)

——NavMeshModifier Component Only(仅导航网格修改器组件)在此模式下,NavMesh Surface本身不收集任何实际的场景几何体。它的作用是专门用来抓取场景中所有带有NavMeshModifier组件的物体,将这些组件上设置的属性(如将某区域设为“不可行走”或提高“通行代价”)应用到底层的导航网格数据中。(你有一大片草地,你想让 AI 尽量不走某条特定的小路。你不需要修改草地的模型,而是放几个空物体,给它们加上NavMeshModifier组件并设为Not Walkable。使用此收集模式,烘焙时就会把这些“不可行走”的规则提取出来应用到最终的网格上。)

-Include Layers包含层 (包含层)

选择"层级"

Advanced(高级)

-Override Voxel Size (覆盖体素大小)手动控制体素的大小。大:精度高,小:性能高

-Override Tile Size (覆盖瓦片大小)为了提高内存效率和并行计算,NavMesh 会被切分成许多个小方块(瓦片)来处理。大:精度高,小:性能高(比如说非常多动态障碍物,网格重算频繁,导致卡顿,此时调小可以让重算速度变快,减少游戏卡顿)

-Minimum Region Area (最小区域面积)

-Build Height Mesh (构建高度网格)目前通常不推荐使用未完全实现

NavMesh Data(导航网格数据):显示当前引用的网格资源文件。

↑************************************************************************************************************↑

烘焙

这是烘焙后的网格文件

↓************************************************************************************************************↓

可以通过不同的参数组合实现局部烘焙,指定物品烘焙,空气墙等不同的烘焙

例如通过"图层"实现只烘焙地面

👆变成👇

↑************************************************************************************************************↑

角色挂载Ⅰ

(一般来说先写好Nav Mesh Agent角色类型再烘焙地面网格)

给需要自动寻路的物体挂载Nav Mesh Agent组件(一般是Enemy(敌人))

(这是新创建的Player与敌人)(分别为他们设置了标签Player与Enemy,图层Player与Enemy,方便后面寻路代码的实现)

自行修改想要的参数,只有这个必须与NavMesh Surface组件中的代理类型一致。

代码

using UnityEngine; public class Xunlu : MonoBehaviour { [Header("基础设置")] public float detectionRange = 22f; // 检测范围 public float moveSpeed = 3f; // 移动速度 public float stoppingDistance = 1.5f; // 停止距离 public LayerMask obstacleLayers; // 障碍物层级(在Inspector中设置) [Header("调试")] public bool showDebugGizmos = true; // 显示调试图形 public Color gizmoColor = Color.yellow; // Gizmo颜色 private Transform player; // 玩家Transform private bool canSeePlayer = false; // 是否能看见玩家 private Vector3 lastKnownPosition; // 最后已知位置 void Start() { // 查找玩家 GameObject playerObj = GameObject.FindWithTag("Player"); if (playerObj != null) { player = playerObj.transform; } else { Debug.LogWarning("未找到带有'Player'标签的游戏对象!"); } // 如果没有设置障碍物层级,默认包含Everything if (obstacleLayers.value == 0) { obstacleLayers = ~0; // 包含所有层级 } } void Update() { if (player == null) return; // 检查玩家是否在检测范围内 float distanceToPlayer = Vector3.Distance(transform.position, player.position); if (distanceToPlayer <= detectionRange) { // 射线检测玩家是否被遮挡 canSeePlayer = CheckPlayerVisibility(); if (canSeePlayer) { lastKnownPosition = player.position; ChasePlayer(); } else { // 看不见玩家时的行为(可选:前往最后已知位置) // MoveToLastKnownPosition(); } } else { canSeePlayer = false; } } /// <summary> /// 检查玩家是否可见(射线检测) /// </summary> bool CheckPlayerVisibility() { if (player == null) return false; Vector3 directionToPlayer = player.position - transform.position; Ray ray = new Ray(transform.position, directionToPlayer.normalized); // 绘制调试射线 Debug.DrawRay(transform.position, directionToPlayer.normalized * detectionRange, canSeePlayer ? Color.green : Color.red); // 射线检测,忽略触发器 RaycastHit hit; if (Physics.Raycast(ray, out hit, detectionRange, obstacleLayers, QueryTriggerInteraction.Ignore)) { // 检查击中的是否是玩家 if (hit.collider.CompareTag("Player")) { return true; } } return false; } /// <summary> /// 追击玩家 /// </summary> void ChasePlayer() { if (player == null) return; float distanceToPlayer = Vector3.Distance(transform.position, player.position); // 如果距离大于停止距离,则移动 if (distanceToPlayer > stoppingDistance) { // 计算移动方向 Vector3 direction = (player.position - transform.position).normalized; // 移动 transform.Translate(direction * moveSpeed * Time.deltaTime, Space.World); // 面向玩家(可选) transform.LookAt(new Vector3(player.position.x, transform.position.y, player.position.z)); } } /// <summary> /// 移动到最后已知位置(可选功能) /// </summary> void MoveToLastKnownPosition() { float distanceToLastPos = Vector3.Distance(transform.position, lastKnownPosition); if (distanceToLastPos > stoppingDistance) { Vector3 direction = (lastKnownPosition - transform.position).normalized; transform.Translate(direction * moveSpeed * Time.deltaTime, Space.World); transform.LookAt(new Vector3(lastKnownPosition.x, transform.position.y, lastKnownPosition.z)); } } /// <summary> /// 绘制调试图形 /// </summary> void OnDrawGizmos() { if (!showDebugGizmos) return; // 绘制检测范围 Gizmos.color = gizmoColor; Gizmos.DrawWireSphere(transform.position, detectionRange); // 绘制停止距离 Gizmos.color = Color.red; Gizmos.DrawWireSphere(transform.position, stoppingDistance); // 如果正在追击,绘制到玩家的连线 if (Application.isPlaying && canSeePlayer && player != null) { Gizmos.color = Color.green; Gizmos.DrawLine(transform.position, player.position); } } }

将此脚本组件挂载到敌人身上

(范围内追击玩家(Player标签),敌人“看见”玩家才追击(射线,层级))

(这里只用到了Player标签与障碍物的“层级”)

*注意玩家与敌人都要处于烘焙好的网格上才行

额外

台阶/斜坡

(图为正常烘培)

当我们不需要台阶或斜坡时

方法一(全部取消烘焙)

window(窗口)——Ai——Navigation(导航)

(可迈过高度)(斜坡°)

再次烘焙

完成

方法二(指定物体取消烘焙)

同(天花板)

天花板(单独物体烘焙)

在天花板物体上添加组件Nav Mesh modifier

方法一Remove Object模式

修改模式为‘Remove Objecdt’(移除)

此时此物体将不会被烘焙

↓************************************************************************************************************↓

Affected Agents(受影响的代理)

作用:筛选受影响的角色类型(自定义的角色类型)

Apply To Children(是否应用到子物体)

↑************************************************************************************************************↑

再次烘焙

完成

方法二 Add or Modify Object模式

(更精准)

↓************************************************************************************************************↓

Affected Agents(受影响的代理)

作用:筛选受影响的角色类型(自定义的角色类型)

Apply To Children(是否应用到子物体)

Override Area(是否为覆盖区域)

作用:通常 Unity 会自动判断地面是否可行走(比如悬空的平台默认是不可行走的)。勾选这两项的意思是,无论这个物体的物理形状如何(哪怕它是悬空的),都强制要求 Unity 把它算作“地面”,允许 AI 在上面寻路。

I

√ ——Area Type(作为什么烘焙类型的覆盖区域)

Override Generate Links(是否覆盖生成链接)

I

√ ——Generate Links(是否自动生成链接)

作用:如果生成了链接,AI 可能会尝试从平台边缘跳下或跳向对面

(√自动生成链接的效果)

(口不自动生成链接的效果)

↑************************************************************************************************************↑

作为“不走路”类型的区域烘焙

烘焙

完成

门(动态障碍物烘焙)

添加组件Nav Mesh Obstacle

↓************************************************************************************************************↓

*切割

*不勾选(默认):障碍物只是“飘浮”在导航网格上方的一层,像一块地毯。AI 只能在网格未被覆盖的地方走。缺点:如果 AI 已经走在网格被覆盖的区域,它可能不会立刻反应过来要躲避。

*勾选:障碍物会像刀一样,直接在底层导航网格上切出一个洞。优点:AI 的寻路算法会实时避开这个洞,即使它正在走向这里,也会立即寻找新路径。性能消耗较大,建议对移动的大物体使用。

移动阈值:挪多远才算动,动了系统才会认为它需要更新路径

静止时间:停多久才算彻底停稳,停下时才在地图上把“路障”真正立起来。

仅在静止时切割勾选:适用于被物理撞击的物体(如箱子、木桶)

取消勾选:适用于缓慢移动的大型载具(如坦克、巡逻车)

↑************************************************************************************************************↑

如图设置,自己微调

完成门的效果

END