关于 ai-navigation
面向 Godot 4.3+ 开发者的 AI 导航技能,涵盖 2D/3D 寻路、转向行为与巡逻模式,并提供 GDScript 和 C# 示例。还介绍行为树及追击、攻击状态组合。
主要功能
- 01NavigationAgent2D/3D 寻路
- 02寻路网格烘焙与导航分层
- 03Seek、Flee、Arrive 和 Wander 转向
- 04路点巡逻模式
- 05行为树与追击攻击模式
适用场景
- 01为 2D 或 3D 敌人设置路径移动
- 02在动态关卡中异步重建导航网格
- 03实现巡逻、追逐和攻击逻辑
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jame581/ godotprompter --skill ai-navigation安装后在 Claude Code 中输入/ai-navigation确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/async-baking.mdbehavior-trees.mdchase-attack.mdnavigation-agent-3d.mdpatrol-patterns.mdsteering-behaviors.md详细介绍
Godot 4.3+ 中的 AI 导航
涵盖 NavigationAgent2D/3D、转向行为、行为树和巡逻模式。所有示例均面向 Godot 4.3+,不使用已弃用的 API。
相关技能: state-machine 用于管理 AI 状态,component-system 用于实现模块化 AI 行为,player-controller 用于了解移动物理模式,math-essentials 用于学习寻路向量和转向计算,limboai 用于通过可视化编辑器实现 BT + HSM,beehave 用于轻量级 GDScript 行为树。如需结构化的行为树(而非转向/导航),请参阅 limboai 和 beehave 中的对比表。
1. 导航设置
场景结构
World (Node2D or Node3D)
└── NavigationRegion2D (or NavigationRegion3D)
├── TileMapLayer / StaticBody2D (geometry)
└── Enemy (CharacterBody2D with NavigationAgent2D child)
NavigationRegion2D / NavigationRegion3D
- 在场景中添加一个 NavigationRegion2D(或 NavigationRegion3D)节点。
- 为其指定 NavigationPolygon(2D)或 NavigationMesh(3D)资源。
- 在 NavigationPolygon 编辑器中绘制可行走区域,或在 3D 中配置 NavigationMesh 的边界。
- **在编辑时烘焙网格:**选择 NavigationRegion 节点 → 点击工具栏中的 Bake NavigationPolygon(2D)或 Bake NavigationMesh(3D)。
- 在运行时烘焙,适用于世界会动态变化的情况:
# 2D
$NavigationRegion2D.bake_navigation_polygon()
# 3D
$NavigationRegion3D.bake_navigation_mesh()
// 2D
GetNode<NavigationRegion2D>("NavigationRegion2D").BakeNavigationPolygon();
// 3D
GetNode<NavigationRegion3D>("NavigationRegion3D").BakeNavigationMesh();
异步导航烘焙
在大型地图上,导航烘焙可能导致帧率下降,因此默认情况下会在后台线程中烘焙区域:bake_navigation_polygon()(2D)和 bake_navigation_mesh()(3D)在未传入 false 时会使用 on_thread = true。连接区域的 bake_finished 信号(使用 CONNECT_ONE_SHOT),即可在网格准备就绪时获知。没有线程支持的 Web 构建无法使用线程烘焙。
请参阅 references/async-baking.md,了解完整的 GDScript 和 C# 后台线程烘焙示例(2D 和 3D)。
**适用异步烘焙的情况:**程序生成的关卡、可破坏地形,或任何需要在运行时重建导航网格的场景。网格烘焙期间,游戏仍会继续运行。
导航层
导航层可以将不同 Agent 类型(地面部队、飞行单位、大型敌人)的可行走区域分开。
# Assign layer bits on the NavigationRegion (Inspector or code)
# Layer 1 = ground, Layer 2 = air, Layer 3 = large
# On the NavigationAgent, set matching layers:
$NavigationAgent2D.navigation_layers = 1 # ground only
$NavigationAgent2D.navigation_layers = 2 # air only
$NavigationAgent2D.navigation_layers = 1 | 2 # both (bitwise OR)
// Assign layer bits on the NavigationRegion (Inspector or code)
// Layer 1 = ground, Layer 2 = air, Layer 3 = large
var navAgent = GetNode<NavigationAgent2D>("NavigationAgent2D");
navAgent.NavigationLayers = 1; // ground only
navAgent.NavigationLayers = 2; // air only
navAgent.NavigationLayers = 1 | 2; // both (bitwise OR)
在 NavigationRegion 和 NavigationAgent 上设置
navigation_layers,确保两者相匹配。层不匹配是 Agent 找不到路径的最常见原因之一。
2. NavigationAgent2D 基本用法
GDScript
extends CharacterBody2D
@export var speed: float = 120.0
@onready var nav_agent: NavigationAgent2D = $NavigationAgent2D
func _ready() -> void:
# velocity_computed fires when avoidance calculates a safe velocity
nav_agent.velocity_computed.connect(_on_velocity_computed)
func _physics_process(delta: float) -> void:
if nav_agent.is_navigation_finished():
return
var next_pos: Vector2 = nav_agent.get_next_path_position()
var direction: Vector2 = (next_pos - global_position).normalized()
var desired_velocity: Vector2 = direction * speed
if nav_agent.avoidance_enabled:
# Hand desired velocity to the avoidance system; wait for the signal
nav_agent.velocity = desired_velocity
else:
velocity = desired_velocity
move_and_slide()
func _on_velocity_computed(safe_velocity: Vector2) -> void:
velocity = safe_velocity
move_and_slide()
func set_target(target_pos: Vector2) -> void:
nav_agent.target_position = target_pos
NavigationAgent2D 的关键属性:
| 属性 | 用途 |
|---|---|
target_position | 世界坐标中的目标位置 |
path_desired_distance | 判定到达每个路径点所需的接近距离(默认值为 20) |
target_desired_distance | 判定到达最终目标所需的接近距离(默认值为 10) |
avoidance_enabled | 启用 RVO 障碍物规避 |
radius | 用于规避的 Agent 碰撞半径 |
time_horizon_agents | 规避预测时长(秒),可调整以减少抖动 |
C#
using Godot;
public partial class Enemy2D : CharacterBody2D
{
[Export] public float Speed { get; set; } = 120f;
private NavigationAgent2D _navAgent;
public override void _Ready()
{
_navAgent = GetNode<NavigationAgent2D>("NavigationAgent2D");
_navAgent.VelocityComputed += OnVelocityComputed;
}
public override void _PhysicsProcess(double delta)
{
if (_navAgent.IsNavigationFinished()) return;
Vector2 nextPos = _navAgent.GetNextPathPosition();
Vector2 direction = (nextPos - GlobalPosition).Normalized();
Vector2 desiredVelocity = direction * Speed;
if (_navAgent.AvoidanceEnabled)
_navAgent.Velocity = desiredVelocity;
else
{
Velocity = desiredVelocity;
MoveAndSlide();
}
}
private void OnVelocityComputed(Vector2 safeVelocity)
{
Velocity = safeVelocity;
MoveAndSlide();
}
public void SetTarget(Vector2 targetPos) => _navAgent.TargetPosition = targetPos;
}
3. NavigationAgent3D 基本用法
CharacterBody3D 每个物理帧都会调用 get_next_path_position() 来驱动 NavigationAgent3D,并将方向在 Y 轴上的分量设为零,同时单独处理重力。启用 avoidance_enabled 后,期望速度会传入 nav_agent.velocity,并在 velocity_computed 处理函数中移动;否则会直接调用 move_and_slide()。通过 target_position 设置目标,并使用 is_navigation_finished() 检查是否已完成导航。
请参阅 references/navigation-agent-3d.md,了解完整的 GDScript 和 C#
NavigationAgent3D敌人示例。
4. 转向行为
每帧进行轻量级计算(seek、flee、arrive、wander),无需导航网格即可实现自然的移动效果。可以将返回的向量相加来组合这些行为,也可以每次 _physics_process 调用时选取一个并赋给 velocity。
完整的 GDScript 和 C# 实现见 references/steering-behaviors.md,包括 seek、flee、arrive(带减速渐变)和 wander(带圆周投影抖动)。
5. 巡逻模式
使用 NavigationAgent2D、一组 Marker2D 路点,以及一个用于控制每个点停留时间的 Timer,即可实现简洁的巡逻循环。在 wait_timer.timeout 时循环递增索引,将 nav_agent.target_position 设为下一个路点,并通过 is_navigation_finished() 控制移动。
完整的 GDScript 和 C# 路点链巡逻实现见 references/patrol-patterns.md,其中包含计时器暂停。
6. 行为树概念
行为树(BT)是一种每个刻度都会评估节点的树结构。三种核心节点类型:
| 类型 | 成功条件 | 失败条件 |
|---|---|---|
| Sequence | 所有子节点均成功(AND) | 任一子节点失败 |
| Selector | 任一子节点成功(OR) | 所有子节点均失败 |
| Action | 叶节点操作完成 | 叶节点报告失败 |
Sequence 表示“依次执行 A、B、C”。Selector 表示“尝试 A,否则尝试 B,否则尝试 C”。
完整的轻量级 BT 实现(BTNode 基类 + Sequence / Selector / Action)以及一个使用“追逐 OR 巡逻”的敌人示例,均可见 references/behavior-trees.md,其中提供 GDScript 和 C# 两种实现。
7. 追逐 + 攻击模式
将 NavigationAgent2D 与状态机结合使用。完整的 FSM 基础设施见 state-machine 技能。
状态
| 状态 | 进入条件 | 退出条件 |
|---|---|---|
| PATROL | 默认 / 玩家逃脱 | 玩家进入 detect_range |
| CHASE | 玩家处于 detect_range 内 | 玩家进入 attack_range 或玩家逃脱 |
| ATTACK | 玩家处于 attack_range 内 | 玩家离开 attack_range |
完整的 GDScript 和 C# 实现见 references/chase-attack.md,包括 PATROL → CHASE → ATTACK 状态转换、攻击冷却计时器、巡逻路点推进,以及返回巡逻状态的逃脱范围交接。
对于较大的项目,可以使用 state-machine 技能将每个状态提取为独立的节点类,并从父节点注入
NavigationAgent2D引用。
8. 专用 2D 导航服务器(Godot 4.5+)
在 Godot 4.5 之前,NavigationServer2D 只是一个轻量前端,所有工作都由内部的 3D 导航服务器处理。Godot 4.5 将两者拆分为完全独立的服务器。这一变化不会影响使用方式——无需更改 API,也无需迁移代码——但带来了两项实际好处:
- 独立性: 2D 导航不再经由 3D 服务器运行;2D 地图、区域和 Agent 均由各自的实现处理。
- 缩小仅含 2D 游戏的导出体积: 可以编译不包含 3D 导航服务器的自定义导出模板(
disable_navigation_3d=yes),从而减小二进制文件大小。
# No code change needed — NavigationServer2D calls work identically.
# The split is internal; you continue using NavigationServer2D as before.
# Example: query a path directly via the server (unchanged API).
# Not named get_path_to: that is Node.get_path_to(node) and cannot be overridden with another signature.
func find_path_to(target: Vector2) -> PackedVector2Array:
var map: RID = get_world_2d().get_navigation_map()
return NavigationServer2D.map_get_path(
map,
global_position,
target,
true # optimize path
)
// No code change needed — NavigationServer2D calls work identically.
public Vector2[] FindPathTo(Vector2 target)
{
Rid map = GetWorld2D().NavigationMap;
return NavigationServer2D.MapGetPath(map, GlobalPosition, target, true);
}
仅含 2D 的项目: 标准导出模板仍包含 3D 导航。若要移除它,请使用
disable_navigation_3d=yesSCons 选项编译自定义导出模板(Godot 4.5+)。只有在项目中完全未使用任何 3D 导航节点(NavigationRegion3D、NavigationAgent3D)时,这样做才安全。
9. 常见问题
| 问题 | 症状 | 解决方法 |
|---|---|---|
| 未烘焙导航网格 | Agent 停在原地;找不到路径 | 运行前烘焙 NavigationPolygon/NavigationMesh,或在场景加载后于运行时调用 bake_navigation_polygon() |
agent_radius 过大 | Agent 无法通过门口或狭窄走廊 | 降低 NavigationPolygon/NavigationMesh 上的 agent_radius 并重新烘焙——Agent 自身的 radius 仅用于避障,不影响寻路 |
| 避障抖动 | Agent 靠近其他 Agent 时会卡顿或来回摆动 | 增大 time_horizon_agents(尝试 2–4 s),或略微降低 Agent 的 max_speed |
| 路径重新计算过于频繁 | 每帧 CPU 占用激增;Agent 变慢 | 添加一个 Timer(0.2–0.5 s),仅在计时器触发时设置 target_position,而不是每个物理帧都设置 |
| 导航层错误 | Agent 忽略某些区域或找不到路径 | 确认 NavigationRegion 和 NavigationAgent 的 navigation_layers 位掩码一致 |
| NavigationServer 尚未就绪就设置了目标 | 第一帧路径为空 | 地图会在物理帧中首次同步——在 _ready() 中,设置 target_position 前使用 await get_tree().physics_frame(或 NavigationServer2D.map_changed) |
| 3D 中未应用重力 | Agent 悬浮或陷入地面 | 始终单独累加来自重力的 velocity.y;只将导航方向的 X/Z 分量归零 |
| 烘焙导致帧率下降 | 烘焙被强制放到主线程上(on_thread = false),或正在解析非常大的场景 | 保持默认的多线程烘焙(bake_navigation_mesh());连接 bake_finished,且不要阻塞等待烘焙完成 |
⚠️ Godot 4.7 中的变化:
NavigationServer3D.map_get_closest_point_normal(map, to_point)现在会返回一个归一化向量(GH-119022)——此前返回的表面法线可能未归一化。通过对结果调用normalized()来补偿的代码仍可正常运行;依赖未归一化向量的模长的代码则会出错。
10. 检查清单
- 已将 NavigationRegion2D/3D 添加到场景,并为其配置 NavigationPolygon/NavigationMesh 资源
- 已烘焙导航网格(在编辑时烘焙,或在 Agent 需要路径前于运行时烘焙)
- NavigationRegion 和 NavigationAgent 的
navigation_layers位掩码一致 -
NavigationAgent2D/NavigationAgent3D是敌人节点的子节点 - 每个物理帧都调用
get_next_path_position(),而不是get_target_position() - 当
avoidance_enabled为true时,已连接velocity_computed信号 - 移动到目的地前检查
is_navigation_finished(),以避免抖动 - 跟随移动中的玩家时,通过节流计时器而非每帧更新路径目标
-
agent_radius足够小,能通过关卡中最窄的通道 - 重力独立于水平导航速度应用(仅限 3D)
- 大型或动态地图保持默认的多线程烘焙(
bake_navigation_mesh()、on_thread = true),并等待bake_finished - 使用 Godot 4.5+ 的纯 2D 项目可以通过
disable_navigation_3d=yes构建自定义导出模板,以减小导出体积