1. 认识Godot4.2的2D导航系统
如果你正在开发2D游戏,想让角色在复杂地图中自动寻路,Godot4.2的导航系统绝对是你的好帮手。相比之前的版本,Godot4.2对2D导航做了不少优化,使用起来更加直观方便。这套系统的核心思想很简单:先定义地图上哪些区域可以行走(我们称之为"导航网格"),然后告诉角色目标位置,剩下的就交给引擎处理。
我刚开始接触这个功能时,最让我惊喜的是它的易用性。不需要复杂的算法知识,也不需要自己实现A*寻路,只要按照标准流程配置几个节点,写几行代码就能实现看起来很智能的寻路效果。整个过程主要涉及三个关键组件:NavigationRegion2D(定义可通行区域)、NavigationAgent2D(负责路径计算)和CharacterBody2D(实际移动的角色)。
在实际项目中,我发现这套系统特别适合RPG、策略游戏或者任何需要角色自动寻路的2D游戏场景。比如制作一个点击移动的ARPG,或者让NPC在城镇中自动巡逻,都可以用这个方案轻松实现。下面我就带你一步步完成整个实现过程。
2. 创建地图和导航区域
2.1 准备地图素材
首先我们需要一张2D地图作为基础。你可以使用任何喜欢的素材,我建议选择有明显障碍物的地图,这样能更好地测试导航效果。比如一张有建筑物、树木和围墙的城镇地图就很合适。在Godot中新建一个2D场景,把地图图片拖入场景,会自动创建Sprite2D节点,我习惯把它重命名为"Map"。
这里有个小技巧:如果你的地图有多层(比如地面层和建筑层),建议把不可通行的障碍物单独放在一个图层。这样后面绘制导航网格时会更加清晰。我刚开始时就犯过把所有元素混在一起的错误,结果绘制导航区域时差点把眼睛看花。
2.2 绘制导航网格
现在重点来了 - 添加NavigationRegion2D节点。这个节点负责定义角色可以行走的区域。选中它,在检查器中找到NavigationPolygon属性,点击编辑按钮就可以开始绘制了。
绘制导航网格有几个实用技巧:
- 尽量沿着可通行区域的边缘绘制,但不要完全贴边,留出一点空隙,这样角色移动时不会卡在墙边
- 遇到复杂地形时,可以先用一个大多边形覆盖主要区域,再用小多边形填补细节
- 按住Shift键可以创建直线,按住Ctrl键可以删除最近的点
我刚开始使用时犯过一个典型错误:把多个不连通区域画成一个多边形。结果角色明明过不去的地方,系统却认为可以通行。后来发现正确的做法是:如果两个区域确实不连通(比如被一堵墙完全隔开),就应该画成两个独立的多边形。
3. 创建可导航的角色
3.1 设置角色场景
接下来我们创建玩家角色。新建一个CharacterBody2D场景,添加一个Sprite2D显示角色外观(可以用简单的矩形或圆形代替),再添加CollisionShape2D定义碰撞范围。这里建议碰撞形状比视觉外观稍小一点,这样角色在狭窄通道移动时会更顺畅。
关键的一步是添加NavigationAgent2D节点。这个组件就是角色的"导航大脑",负责计算路径和指导移动。它有以下几个重要属性需要关注:
- path_max_distance:最大寻路距离
- target_desired_distance:判定到达目标的距离阈值
- velocity_computed:计算出的理想速度
3.2 编写移动逻辑
角色的移动代码写在_physics_process中,这是Godot专门处理物理逻辑的回调函数。下面是一个典型的实现:
extends CharacterBody2D var move_speed = 200.0 var target_pos: Vector2: set(val): target_pos = val $NavigationAgent2D.target_position = val @onready var nav = $NavigationAgent2D func _physics_process(delta): if nav.is_navigation_finished(): return if nav.is_target_reachable(): var next_pos = nav.get_next_path_position() var direction = global_position.direction_to(next_pos) velocity = direction * move_speed move_and_slide()这段代码的工作原理是:
- 通过setter函数将目标位置同步给NavigationAgent2D
- 每帧检查是否到达目标(is_navigation_finished)
- 检查目标是否可达(is_target_reachable)
- 获取下一个路径点(get_next_path_position)
- 计算当前位置到下一个点的方向向量
- 设置速度并移动(move_and_slide)
我在实际项目中发现,move_speed的值需要根据游戏类型调整。动作游戏可以快一些,策略游戏则可以慢些。另外,如果角色移动时出现抖动,可以尝试调整NavigationAgent2D的path_desired_distance参数。
4. 实现鼠标点击移动
4.1 设置输入检测
为了让角色响应鼠标点击移动,我们需要在主场景中添加输入检测代码:
extends Node2D @onready var player = $Player func _input(event): if event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed(): player.target_pos = get_global_mouse_position()这段代码监听了鼠标左键点击事件,获取点击位置的全局坐标,然后传递给玩家的target_pos属性。由于我们之前设置了setter函数,这个位置会自动同步给NavigationAgent2D。
4.2 处理特殊情况
在实际测试中,我发现几个需要特别注意的情况:
- 点击不可到达区域时,角色会停在最后一个可达点,不会报错
- 移动过程中如果突然点击很远的位置,角色会立即转向新目标
- 角色到达目标后会自动停止,不需要额外逻辑
如果想实现更复杂的行为,比如点击障碍物时显示"无法到达"提示,可以这样修改代码:
func _input(event): if event is InputEventMouseButton and event.pressed: if event.button_index == MOUSE_BUTTON_LEFT: var mouse_pos = get_global_mouse_position() if $NavigationRegion2D.is_point_in_navigation_polygon(mouse_pos): player.target_pos = mouse_pos else: show_error_message("无法到达该位置")5. 高级技巧和优化建议
5.1 动态更新导航网格
如果你的游戏中有可破坏的障碍物或可移动的平台,可能需要动态更新导航网格。Godot提供了相应的API:
# 获取当前导航多边形 var nav_poly = $NavigationRegion2D.navigation_polygon # 修改多边形顶点 nav_poly.add_outline(new_outline) nav_poly.make_polygons_from_outlines() # 重新设置导航多边形 $NavigationRegion2D.navigation_polygon = nav_poly我在一个塔防游戏中就用过这个技术,当玩家建造防御塔时,会实时更新导航区域,让敌人自动绕开新建的障碍物。
5.2 多角色避障
当场景中有多个角色同时移动时,可能会发生碰撞。Godot的NavigationAgent2D提供了简单的避障功能,通过设置avoidance_enabled和radius属性,角色会自动轻微调整路径避开彼此。
func _ready(): $NavigationAgent2D.avoidance_enabled = true $NavigationAgent2D.radius = 16.05.3 性能优化
对于大地图,导航网格可能会很复杂,影响性能。可以考虑以下优化方案:
- 将大地图分成多个小区域,按需加载导航数据
- 简化导航多边形,减少不必要的顶点
- 对于静态地图,可以预先烘焙导航数据
我在一个开放世界项目中就采用了区域分割的方案,当玩家进入新区域时异步加载对应的导航数据,效果很好。