Godot 2D 敌人 AI 与寻路
今日速览
- 「追玩家」最容易踩的坑:直接写
position += (player.position - position).normalized() * speed,敌人会一头撞进墙里、卡在墙角抖。真正要的是寻路(pathfinding):先在「可走区域」里算一条从 A 到 B 的路,再沿路走。 - 两套方案,别选错:
AStar2D / AStarGrid2D适合格子制玩法(战棋、Roguelike 格子移动);NavigationServer2D(配NavigationRegion2D)是导航网格,适合角色能在区域内任意位置自由走动的实时动作游戏。 - 一套寻路 = 三件事:烘焙一张导航网格(哪里能走)→ 给敌人挂
NavigationAgent2D(算路+沿路)→ 你在_physics_process()里把节点朝get_next_path_position()移动。 - 导航系统从不自己移动你的节点:它只告诉你「下一个路点在哪」,移动永远是你的脚本负责——这是新手最大的误解。
- Dynamic 障碍:会动的障碍物用
NavigationObstacle2D,别每次动态重烤整张网格。
核心学习内容
1. 导航网格 vs 格子:先选对模型
Godot 官方 2D Navigation 文档给了两条路。格子制(AStarGrid2D)简单粗暴:地图本身是格子,敌人就是从一个格子跳到另一个格子,适合回合制/解谜。导航网格(NavigationRegion2D + NavigationServer2D)则是把「能走的区域」烘焙成一张多边形网格,敌人在网格内可以走任意位置——这才是《吸血鬼幸存者》《挺进地牢》这类实时动作游戏需要的东西。选错方案的代价是:格子寻路用在自由移动的角色上,会走出一格一格的「抽搐感」。
2. 烘焙一张导航网格
在 2D 场景里加一个 NavigationRegion2D 节点,给它一个 NavigationPolygon 资源。你可以手动画多边形,也可以放一张 TileMapLayer 后用 TileMap 自动烘焙(TileSet 里给瓦片设置 navigation 属性,或直接用「Bake NavigationPolygon」)。烘焙时会自动把障碍(NavigationObstacle2D / 物理碰撞体)挖掉,留下可走的洞。改了地图记得重烤——网格没更新,敌人的路就会穿墙。
3. 让敌人真的走起来(这是最关键的一段)
给敌人(比如一个 CharacterBody2D)加子节点 NavigationAgent2D,然后在脚本里:
extends CharacterBody2D
@export var speed: float = 120.0
@onready var agent: NavigationAgent2D = $NavigationAgent2D
var target: Node2D
func _physics_process(delta: float) -> void:
if target == null:
return
agent.target_position = target.global_position # 设目标即触发算路
if agent.is_navigation_finished(): # 先判断,避免原地抖
return
var next := agent.get_next_path_position() # 取下一个路点
velocity = global_position.direction_to(next) * speed
move_and_slide()
四个必须记住的点:① get_next_path_position() 每物理帧只调一次,调多次或多帧空转会让敌人「来回抖」;② 每帧一开始就用 is_navigation_finished() 提前返回;③ 路点间距由 path_desired_distance 控制,敌人跑得快就要调大,否则会「回溯」走回头路;④ _ready() 里算路可能返回空路径(网格还没同步),要改成 call_deferred 或等导航地图就绪信号。
4. 从「会走」到「会追」
光有寻路只是腿,还需要「脑子」。一个够用的敌人 AI 状态机:巡逻(Patrol) 沿预设点走 → 警戒(Alert) 发现玩家,短暂停顿 + 播放察觉动效 → 追击(Chase) 把 target_position 设为玩家位置一路追 → 搜索/返回(Search → Return) 丢失目标后到最后一个已知位置找一会儿,再回巡逻。发现玩家用「距离 + 视线」判断:距离用 distance_to(),视线用 RayCast2D 从敌人指向玩家,被墙挡住就不算「看见」。这套组合就是大多数 2D 动作游戏敌人的骨架。
🎯 DeepSeek 提示词(可直接复制)
提示词 1:写一个会绕墙追踪的敌人脚本
你是一名 Godot 4 资深开发者。请为一个 2D 俯视角动作游戏写一个敌人 AI 脚本(GDScript)。 场景结构:敌人根节点是
CharacterBody2D(脚本挂在这里),子节点有NavigationAgent2D(命名 NavigationAgent2D)。地图已用NavigationRegion2D烘焙好导航网格。 需求:
- 用
_physics_process(delta)实现跟随目标(@export var target_path: NodePath指向玩家)的寻路移动;- 必须使用
agent.target_position+agent.get_next_path_position(),并且每帧只调用一次get_next_path_position();- 每帧开头用
agent.is_navigation_finished()提前返回,避免原地抖动;speed用@export暴露给编辑器;- 在
_ready()里用call_deferred初始化路径,避免网格未同步导致空路径。 输出:完整可运行的单文件 GDScript,代码用中文注释说明每个关键调用,最后列出 3 个最容易出错的点。
提示词 2:排查「敌人原地抖 / 回溯 / 卡住」的寻路 bug
你是一名 Godot 4 调试专家。我的 2D 敌人用了
NavigationAgent2D追踪玩家,但出现以下症状:【粘贴症状,例如:站在墙角原地抖动 / 走两步又回头 / 到目标附近来回跳】。 我的相关代码:【粘贴_physics_process里的移动代码】。 请按「先判断症状对应的根因」的方式排查,重点检查:get_next_path_position()是否被每帧多次或不应调用的时机调用、path_desired_distance/target_desired_distance/path_max_distance是否与移动速度匹配、导航网格是否需要重新烘焙、是否在_ready()里过早算路。 输出:① 最可能的 2-3 个根因(按概率排序);② 每条的验证方法;③ 修改后的完整代码。
提示词 3:设计巡逻-警戒-追击-返回的敌人状态机
你是一名游戏 AI 设计师兼 Godot 开发者。请设计并实现一个 2D 敌人的行为状态机,状态包括 Patrol(巡逻)、Alert(警戒)、Chase(追击)、Search(搜索)、Return(返回),用
NavigationAgent2D负责移动。 约束:用 enum + match 实现(不要用第三方插件);发现玩家的判断 = 距离阈值 +RayCast2D视线遮挡检测;丢失目标后先到「最后已知位置」搜一会儿再返回巡逻起点;每个状态切换时打印日志便于调试。 输出:① 状态切换条件表(markdown 表格);② 完整 GDScript;③ 一段解释「为什么追击时不要每帧重设 target_position」。
学习心得
寻路是「敌人看起来像活的」的第一道门槛。一旦敌人能手绕开障碍追你,游戏的紧张感立刻上一个台阶——而这套东西在 Godot 里其实只要三个节点(NavigationRegion2D + NavigationAgent2D + 你的移动脚本)。对我们「用 DeepSeek 做 Steam 游戏」的目标来说,这更是绝佳的 AI 协作场景:寻路的模板代码高度固定,正好丢给 DeepSeek 生成;而调参和调试(抖动、回溯、速度匹配)才是你要花时间的地方。把上面第 2 条提示词存好——以后任何「AI 寻路不对劲」的问题,直接填症状让 DeepSeek 定位,比自己盯着代码发呆快得多。
明日关注
- 把导航网格烘焙接到 TileMapLayer 上,试试「改地图自动重烤」,体会一次从穿墙到绕墙的差别。
- 给敌人加上
RayCast2D视线检测,让「躲在墙后不被发现」真的成立。 - 让 DeepSeek 给你写一个「显示敌人当前路径的调试绘制」(
agent.get_current_navigation_path()),可视化排查寻路问题。
本文由自动化学习流水线每日生成,坚持即学即记。
评论