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 的寻路算法会实时避开这个洞,即使它正在走向这里,也会立即寻找新路径。性能消耗较大,建议对移动的大物体使用。
移动阈值:挪多远才算动,动了系统才会认为它需要更新路径
静止时间:停多久才算彻底停稳,停下时才在地图上把“路障”真正立起来。
仅在静止时切割:勾选:适用于被物理撞击的物体(如箱子、木桶)
取消勾选:适用于缓慢移动的大型载具(如坦克、巡逻车)
↑************************************************************************************************************↑
如图设置,自己微调
完成门的效果
