2D 导航概述

Godot 提供了多种对象、类和服务器,可帮助 2D 和 3D 游戏实现基于栅格(Grid)或网格(Mesh)的导航和寻路。下文将对 Godot 中与 2D 场景导航相关的对象及其主要用途进行概述。

Godot 为 2D 导航提供了如下对象和类:

  • Astar2D

    Astar2D 对象能够在由具有权重的构成的图中查找最短路径。

    AStar2D 类最适合的是基于单元格的 2D 游戏,角色不需要到达区域中的任意位置,只需要能够到达一些预先指定的独立位置。

  • NavigationServer2D

    NavigationServer2D 提供了强大的服务器 API,能够在区域中查找两个位置之间的最短路径,区域使用导航网格定义。

    NavigationServer 最适合的是要求角色能够到达区域中任意位置的 2D 实时游戏,区域由导航网格定义。基于网格的导航能够轻松扩展到大型游戏世界,因为大型区域通常能够使用单一多边形定义,如果换成栅格则会需要定义许许多多的单元格。

    NavigationServer 中存放了不同的导航地图,每一张地图都由若干区块组成,区块中存放的是导航网格数据。在地图上放置代理就能够进行避障计算。与服务器通信时,使用 RID 来引用内部的地图、区块和代理。

    • NavigationServer 中可用的 RID 类型如下。

      • 导航地图 RID

        引用指定的导航地图,地图中存放的是区块和代理。地图会尝试将区块中的导航网格根据距离进行合并。每一个物理帧,地图都会同步区块和代理。

      • 导航区块 RID

        引用指定的导航区块,区块中存放的是导航网格数据。使用导航层位掩码可以对区块进行启用/禁用,限制其使用。

      • 导航链接 RID

        引用指定的导航链接,能够将两个导航网格上的位置进行连接,无视距离。

      • 导航代理 RID

        引用指定的避障代理。避障使用半径值指定。

      • 导航障碍物 RID

        引用指定的避障障碍物,会对代理的避障速度产生影响和约束。

下列场景树节点可以辅助对 NavigationServer2D API 的使用。

  • NavigationRegion2D 节点

    存放 NavigationPolygon 资源的节点,该资源定义的是 NavigationServer2D 中的导航网格。

    • 区块可以启用/禁用。

    • 通过 navigation_layers 掩码,可以对其在寻路中的使用做进一步的限制。

    • NavigationServer2D 会根据距离将不同区块中的导航网格合并成一个导航网格。

  • NavigationLink2D 节点

    将两个导航网格上的位置进行连接的节点,无视距离,可用于寻路。

    • 链接可以启用/禁用。

    • 链接可以设为单向或双向。

    • 通过 navigation_layers 掩码,可以对其在寻路中的使用做进一步的限制。

    链接会告诉寻路存在这样的连接、相关的消耗如何。实际的代理处理以及移动需要在自定义脚本中实现。

  • NavigationAgent2D 节点

    可选的辅助节点,用于为继承自 Node2D 的父节点提供寻路和避障所需的常规 NavigationServer2D API 调用。请将这个节点放在继承自 Node2D 的父节点下。

  • NavigationObstacle2D 节点

    可用于影响和约束启用躲避的代理的躲避速度的节点。此节点不影响代理的寻路。你需要为此更改导航网格。

2D 导航网格由以下资源定义:

  • NavigationPolygon 资源

    存放 2D 导航网格数据的资源,提供了多边形绘制工具,既能够在编辑器中定义导航区域,也能够在运行时定义。

    • NavigationRegion2D 节点使用该资源定义其导航区域。

    • NavigationServer2D 使用该资源更新各个区块的导航网格。

    • TileSet 编辑器会定义图块的导航区域时在内部创建并使用该资源。

参见

可以使用 2D 导航演示项目使用 AStarGrid2D 进行基于栅格的导航 演示项目了解 2D 导航如何运作。

2D 场景的设置

下列步骤演示的是最小可行的 2D 导航的基础设置,使用 NavigationServer2D 和 NavigationAgent2D 进行路径移动。

  1. 在场景中添加一个 NavigationRegion2D 节点。

  2. 单击该区块节点,向该节点添加一个新的 NavigationPolygon 资源。

    ../../_images/nav_2d_min_setup_step1.png

  3. 使用 NavigationPolygon 绘制工具定义可移动导航区域。然后点击工具栏上的 烘焙 NavigationPolygon 按钮。

    ../../_images/nav_2d_min_setup_step2.png

    备注

    导航网格定义的是角色的中心点所能够站立和移动的区域。请在导航多边形的边缘和碰撞对象之间留下足够的边距,这样角色跟随路径移动时就不会因为碰撞而卡住。

  4. 在场景中添加一个 CharacterBody2D 节点,设置基础的碰撞形状,添加一个精灵或网格方便观察。

  5. 在该角色节点下添加一个 NavigationAgent2D 节点。

    ../../_images/nav_2d_min_setup_step3.webp

  6. 为 CharacterBody3D 节点添加下面的脚本。场景完全加载后,我们确保设置移动目标,NavigationServer 有时间进行同步。

GDScriptC#

  1. extends CharacterBody2D
  2. var movement_speed: float = 200.0
  3. var movement_target_position: Vector2 = Vector2(60.0,180.0)
  4. @onready var navigation_agent: NavigationAgent2D = $NavigationAgent2D
  5. func _ready():
  6. # These values need to be adjusted for the actor's speed
  7. # and the navigation layout.
  8. navigation_agent.path_desired_distance = 4.0
  9. navigation_agent.target_desired_distance = 4.0
  10. # Make sure to not await during _ready.
  11. call_deferred("actor_setup")
  12. func actor_setup():
  13. # Wait for the first physics frame so the NavigationServer can sync.
  14. await get_tree().physics_frame
  15. # Now that the navigation map is no longer empty, set the movement target.
  16. set_movement_target(movement_target_position)
  17. func set_movement_target(movement_target: Vector2):
  18. navigation_agent.target_position = movement_target
  19. func _physics_process(delta):
  20. if navigation_agent.is_navigation_finished():
  21. return
  22. var current_agent_position: Vector2 = global_position
  23. var next_path_position: Vector2 = navigation_agent.get_next_path_position()
  24. velocity = current_agent_position.direction_to(next_path_position) * movement_speed
  25. move_and_slide()
  1. using Godot;
  2. public partial class MyCharacterBody2D : CharacterBody2D
  3. {
  4. private NavigationAgent2D _navigationAgent;
  5. private float _movementSpeed = 200.0f;
  6. private Vector2 _movementTargetPosition = new Vector2(70.0f, 226.0f);
  7. public Vector2 MovementTarget
  8. {
  9. get { return _navigationAgent.TargetPosition; }
  10. set { _navigationAgent.TargetPosition = value; }
  11. }
  12. public override void _Ready()
  13. {
  14. base._Ready();
  15. _navigationAgent = GetNode<NavigationAgent2D>("NavigationAgent2D");
  16. // These values need to be adjusted for the actor's speed
  17. // and the navigation layout.
  18. _navigationAgent.PathDesiredDistance = 4.0f;
  19. _navigationAgent.TargetDesiredDistance = 4.0f;
  20. // Make sure to not await during _Ready.
  21. Callable.From(ActorSetup).CallDeferred();
  22. }
  23. public override void _PhysicsProcess(double delta)
  24. {
  25. base._PhysicsProcess(delta);
  26. if (_navigationAgent.IsNavigationFinished())
  27. {
  28. return;
  29. }
  30. Vector2 currentAgentPosition = GlobalTransform.Origin;
  31. Vector2 nextPathPosition = _navigationAgent.GetNextPathPosition();
  32. Velocity = currentAgentPosition.DirectionTo(nextPathPosition) * _movementSpeed;
  33. MoveAndSlide();
  34. }
  35. private async void ActorSetup()
  36. {
  37. // Wait for the first physics frame so the NavigationServer can sync.
  38. await ToSignal(GetTree(), SceneTree.SignalName.PhysicsFrame);
  39. // Now that the navigation map is no longer empty, set the movement target.
  40. MovementTarget = _movementTargetPosition;
  41. }
  42. }

备注

第一帧的时候,NavigationServer 上的地图还没有同步区块数据,请求路径时都会返回空。在脚本中等待一帧就可以让 NavigationServer 进行同步。