Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
3D 导航概述¶
Godot 提供了多种对象、类和服务器,可帮助 2D 和 3D 游戏实现基于栅格(Grid)或网格(Mesh)的导航和寻路。下文将对 Godot 中与 3D 场景导航相关的对象及其主要用途进行概述。
Godot 为 3D 导航提供了如下对象和类:
- Astar3D
Astar3D对象能够在由具有权重的点构成的图中查找最短路径。AStar3D 类最适合的是基于单元格的 3D 游戏,角色不需要到达区域中的任意位置,只需要到达预先指定的一些独立位置。
- NavigationServer3D
NavigationServer3D提供了强大的服务器 API,能够在区域中查找两个位置之间的最短路径,区域使用导航网格定义。NavigationServer 最适合的是要求角色能够到达区域中任意位置的 3D 实时游戏,区域由导航网格定义。基于网格的导航能够轻松扩展到大型游戏世界,因为大型区域通常能够使用单一多边形定义,如果换成栅格则会需要定义许许多多的单元格。
NavigationServer 中存放了不同的导航地图,每一张地图都由若干区块组成,区块中存放的是导航网格数据。在地图上放置代理就能够进行避障计算。与服务器通信时,使用 RID 来引用内部的地图、区块和代理。
- NavigationServer 中可用的 RID 类型如下。
- 导航地图 RID
引用指定的导航地图,地图中存放的是区块和代理。地图会尝试将区块中的导航网格根据距离进行合并。每一个物理帧,地图都会同步区块和代理。
- 导航区块 RID
引用指定的导航区块,区块中存放的是导航网格数据。使用导航层位掩码可以对区块进行启用/禁用,限制其使用。
- 导航链接 RID
引用指定的导航链接,能够将两个导航网格上的位置进行连接,无视距离。
- 导航代理 RID
引用指定的避障代理,避障时使用的是半径值。
- 导航障碍物 RID
引用指定的避障障碍物,会对代理的避障速度产生影响和约束。
下列场景树节点可以辅助对 NavigationServer3D API 的使用。
- NavigationRegion3D 节点
存放 Navigation Mesh 资源的节点,该资源定义的是 NavigationServer3D 中的导航网格。
区块可以启用/禁用。
通过
navigation_layers掩码,可以对其在寻路中的使用做进一步的限制。NavigationServer3D 会根据距离将不同的导航网格合并成一个导航网格。
- NavigationLink3D 节点
将两个导航网格上的位置进行连接的节点,无视距离,可用于寻路。
链接可以启用/禁用。
链接可以设为单向或双向。
通过
navigation_layers掩码,可以对其在寻路中的使用做进一步的限制。
链接会告诉寻路存在这样的连接、相关的消耗如何。实际的代理处理以及移动需要在自定义脚本中实现。
- NavigationAgent3D 节点
方便调用寻路和避障所需的常规 NavigationServer3D API 的辅助节点。该节点的父节点应该继承自 Node3D。
- NavigationObstacle3D 节点
可用于影响和约束启用躲避的代理的躲避速度的节点。此节点不影响代理的寻路。你需要为此更改导航网格。
3D 导航网格由以下资源定义:
- NavigationMesh 资源
存放 3D 导航网格数据的资源,提供了 3D 几何体的烘焙选项,既能够在编辑器中定义导航区域,也能够在运行时定义。
NavigationRegion3D 节点使用该资源定义其导航区域。
NavigationServer3D 使用该资源更新各个区块的导航网格。
GridMap 编辑器会在栅格单元格中存在对导航网格的定义时使用该资源。
参见
可以使用 3D 导航演示项目了解 3D 导航如何运作。
3D 场景的设置¶
下列步骤演示的是最小可行的 3D 导航的基础设置,使用 NavigationServer3D 和 NavigationAgent3D 进行路径移动。
在场景中添加一个 NavigationRegion3D 节点。
单击该区块节点,向该节点添加一个新的 NavigationMesh 资源。
将一个新的 MeshInstance3D 节点添加为该区块节点的子节点。
选中该 MeshInstance3D 节点,添加一个新的 PlaneMesh 并将其 XY 大小设为 10。
再次选中该区块节点,点击顶栏中的“烘焙导航网格”按钮。
现在就会显示出透明的导航网格,悬浮在 PlaneMesh 上方。
在场景中添加一个 CharacterBody3D 节点,设置基础的碰撞形状,添加一些网格方便观察。
在该角色节点下添加一个 NavigationAgent3D 节点。
为 CharacterBody3D 节点添加一个脚本,内容如下。场景完全加载后,我们确保设置移动目标,NavigationServer 有时间进行同步。另外,添加一个 Camera3D、一些灯光以及环境,这样才能够看到东西。
extends CharacterBody3D
var movement_speed: float = 2.0
var movement_target_position: Vector3 = Vector3(-3.0,0.0,2.0)
@onready var navigation_agent: NavigationAgent3D = $NavigationAgent3D
func _ready():
# These values need to be adjusted for the actor's speed
# and the navigation layout.
navigation_agent.path_desired_distance = 0.5
navigation_agent.target_desired_distance = 0.5
# 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: Vector3):
navigation_agent.set_target_position(movement_target)
func _physics_process(delta):
if navigation_agent.is_navigation_finished():
return
var current_agent_position: Vector3 = global_position
var next_path_position: Vector3 = 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 MyCharacterBody3D : CharacterBody3D
{
private NavigationAgent3D _navigationAgent;
private float _movementSpeed = 2.0f;
private Vector3 _movementTargetPosition = new Vector3(-3.0f, 0.0f, 2.0f);
public Vector3 MovementTarget
{
get { return _navigationAgent.TargetPosition; }
set { _navigationAgent.TargetPosition = value; }
}
public override void _Ready()
{
base._Ready();
_navigationAgent = GetNode<NavigationAgent3D>("NavigationAgent3D");
// These values need to be adjusted for the actor's speed
// and the navigation layout.
_navigationAgent.PathDesiredDistance = 0.5f;
_navigationAgent.TargetDesiredDistance = 0.5f;
// Make sure to not await during _Ready.
Callable.From(ActorSetup).CallDeferred();
}
public override void _PhysicsProcess(double delta)
{
base._PhysicsProcess(delta);
if (_navigationAgent.IsNavigationFinished())
{
return;
}
Vector3 currentAgentPosition = GlobalTransform.Origin;
Vector3 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 进行同步。