2D 导航概述
Godot 提供了多种对象、类和服务器,可帮助 2D 和 3D 游戏实现基于栅格(Grid)或网格(Mesh)的导航和寻路。下文将对 Godot 中与 2D 场景导航相关的对象及其主要用途进行概述。
Godot 为 2D 导航提供了如下对象和类:
-
Astar2D
对象能够在由具有权重的点构成的图中查找最短路径。AStar2D 类最适合的是基于单元格的 2D 游戏,角色不需要到达区域中的任意位置,只需要能够到达一些预先指定的独立位置。
-
NavigationServer2D
提供了强大的服务器 API,能够在区域中查找两个位置之间的最短路径,区域使用导航网格定义。NavigationServer 最适合的是要求角色能够到达区域中任意位置的 2D 实时游戏,区域由导航网格定义。基于网格的导航能够轻松扩展到大型游戏世界,因为大型区域通常能够使用单一多边形定义,如果换成栅格则会需要定义许许多多的单元格。
NavigationServer 中存放了不同的导航地图,每一张地图都由若干区块组成,区块中存放的是导航网格数据。在地图上放置代理就能够进行避障计算。与服务器通信时,使用 RID 来引用内部的地图、区块和代理。
NavigationServer 中可用的 RID 类型如下。
导航地图 RID
引用指定的导航地图,地图中存放的是区块和代理。地图会尝试将区块中的导航网格根据距离进行合并。每一个物理帧,地图都会同步区块和代理。
导航区块 RID
引用指定的导航区块,区块中存放的是导航网格数据。使用导航层位掩码可以对区块进行启用/禁用,限制其使用。
导航链接 RID
引用指定的导航链接,能够将两个导航网格上的位置进行连接,无视距离。
导航代理 RID
引用指定的避障代理。避障使用半径值指定。
导航障碍物 RID
引用指定的避障障碍物,会对代理的避障速度产生影响和约束。
下列场景树节点可以辅助对 NavigationServer2D API 的使用。
-
存放 NavigationPolygon 资源的节点,该资源定义的是 NavigationServer2D 中的导航网格。
区块可以启用/禁用。
通过
navigation_layers
掩码,可以对其在寻路中的使用做进一步的限制。NavigationServer2D 会根据距离将不同区块中的导航网格合并成一个导航网格。
-
将两个导航网格上的位置进行连接的节点,无视距离,可用于寻路。
链接可以启用/禁用。
链接可以设为单向或双向。
通过
navigation_layers
掩码,可以对其在寻路中的使用做进一步的限制。
链接会告诉寻路存在这样的连接、相关的消耗如何。实际的代理处理以及移动需要在自定义脚本中实现。
-
可选的辅助节点,用于为继承自 Node2D 的父节点提供寻路和避障所需的常规 NavigationServer2D API 调用。请将这个节点放在继承自 Node2D 的父节点下。
-
可用于影响和约束启用躲避的代理的躲避速度的节点。此节点不影响代理的寻路。你需要为此更改导航网格。
2D 导航网格由以下资源定义:
-
存放 2D 导航网格数据的资源,提供了多边形绘制工具,既能够在编辑器中定义导航区域,也能够在运行时定义。
NavigationRegion2D 节点使用该资源定义其导航区域。
NavigationServer2D 使用该资源更新各个区块的导航网格。
TileSet 编辑器会定义图块的导航区域时在内部创建并使用该资源。
参见
可以使用 2D 导航演示项目和使用 AStarGrid2D 进行基于栅格的导航 演示项目了解 2D 导航如何运作。
2D 场景的设置
下列步骤演示的是最小可行的 2D 导航的基础设置,使用 NavigationServer2D 和 NavigationAgent2D 进行路径移动。
在场景中添加一个 NavigationRegion2D 节点。
单击该区块节点,向该节点添加一个新的 NavigationPolygon 资源。
使用 NavigationPolygon 绘制工具定义可移动导航区域。然后点击工具栏上的 烘焙 NavigationPolygon 按钮。
备注
导航网格定义的是角色的中心点所能够站立和移动的区域。请在导航多边形的边缘和碰撞对象之间留下足够的边距,这样角色跟随路径移动时就不会因为碰撞而卡住。
在场景中添加一个 CharacterBody2D 节点,设置基础的碰撞形状,添加一个精灵或网格方便观察。
在该角色节点下添加一个 NavigationAgent2D 节点。
- 为 CharacterBody3D 节点添加下面的脚本。场景完全加载后,我们确保设置移动目标,NavigationServer 有时间进行同步。
GDScriptC#
extends CharacterBody2D
var movement_speed: float = 200.0
var movement_target_position: Vector2 = Vector2(60.0,180.0)
@onready var navigation_agent: NavigationAgent2D = $NavigationAgent2D
func _ready():
# These values need to be adjusted for the actor's speed
# and the navigation layout.
navigation_agent.path_desired_distance = 4.0
navigation_agent.target_desired_distance = 4.0
# Make sure to not await during _ready.
call_deferred("actor_setup")
func actor_setup():
# Wait for the first physics frame so the NavigationServer can sync.
await get_tree().physics_frame
# Now that the navigation map is no longer empty, set the movement target.
set_movement_target(movement_target_position)
func set_movement_target(movement_target: Vector2):
navigation_agent.target_position = movement_target
func _physics_process(delta):
if navigation_agent.is_navigation_finished():
return
var current_agent_position: Vector2 = global_position
var next_path_position: Vector2 = navigation_agent.get_next_path_position()
velocity = current_agent_position.direction_to(next_path_position) * movement_speed
move_and_slide()
using Godot;
public partial class MyCharacterBody2D : CharacterBody2D
{
private NavigationAgent2D _navigationAgent;
private float _movementSpeed = 200.0f;
private Vector2 _movementTargetPosition = new Vector2(70.0f, 226.0f);
public Vector2 MovementTarget
{
get { return _navigationAgent.TargetPosition; }
set { _navigationAgent.TargetPosition = value; }
}
public override void _Ready()
{
base._Ready();
_navigationAgent = GetNode<NavigationAgent2D>("NavigationAgent2D");
// These values need to be adjusted for the actor's speed
// and the navigation layout.
_navigationAgent.PathDesiredDistance = 4.0f;
_navigationAgent.TargetDesiredDistance = 4.0f;
// Make sure to not await during _Ready.
Callable.From(ActorSetup).CallDeferred();
}
public override void _PhysicsProcess(double delta)
{
base._PhysicsProcess(delta);
if (_navigationAgent.IsNavigationFinished())
{
return;
}
Vector2 currentAgentPosition = GlobalTransform.Origin;
Vector2 nextPathPosition = _navigationAgent.GetNextPathPosition();
Velocity = currentAgentPosition.DirectionTo(nextPathPosition) * _movementSpeed;
MoveAndSlide();
}
private async void ActorSetup()
{
// Wait for the first physics frame so the NavigationServer can sync.
await ToSignal(GetTree(), SceneTree.SignalName.PhysicsFrame);
// Now that the navigation map is no longer empty, set the movement target.
MovementTarget = _movementTargetPosition;
}
}
备注
第一帧的时候,NavigationServer 上的地图还没有同步区块数据,请求路径时都会返回空。在脚本中等待一帧就可以让 NavigationServer 进行同步。