925236118/alphaagent/godot-navigation-system

Claude Code and agent skill by 925236118/alphaagent

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

NavigationAgent2D使用、AStarGrid2D算法、自定义A*实现、流式寻路及性能优化。用于2D/3D导航系统、动态障碍物避让、游戏AI导航等场景。

Install

npx vskill@latest install 925236118/alphaagent/godot-navigation-system

SKILL.md

Godot 导航与寻路系统

Godot 4 导航系统完整指南,涵盖 NavigationAgent2D、AStarGrid2D、自定义 A* 算法、流场寻路及性能优化策略。

何时使用此技能

  • 需要 NavigationServer2D/3D 导航系统
  • 实现游戏AI寻路导航
  • 需要动态障碍物和NavLink连接
  • 优化大量单位的寻路性能

1. NavigationAgent2D 使用

NavigationAgent2D 是 Godot 4 推荐的 2D 导航解决方案,封装了 NavigationServer 的复杂操作。

基础设置
gdscript
# navigation_agent_2d.gd
class_name NavigationAgent2D
extends NavigationAgent2D
signal navigation_finished
signal path_changed
signal target_reached
@export var actor: CharacterBody2D
@export var move_speed: float = 200.0
var _target_position: Vector2 = Vector2.ZERO
func _ready() -> void:
# 设置代理半径(障碍物避让)
agent_height = 0
agent_max_speed = move_speed
# 连接信号
navigation_finished.connect(_on_navigation_finished)
path_changed.connect(_on_path_changed)
target_reached.connect(_on_target_reached)
# 等待NavigationServer同步
await get_tree().physics_frame
await get_tree().physics_frame
func _physics_process(delta: float) -> void:
if actor and _target_position != Vector2.ZERO:
if is_navigation_finished():
return
var next_pos := get_next_path_position()
var current_pos := actor.global_position
var new_velocity := (next_pos - current_pos).normalized() * move_speed
actor.velocity = new_velocity
actor.move_and_slide()
func set_target(world_position: Vector2) -> void:
_target_position = world_position
target_position = world_position
func _on_navigation_finished() -> void:
navigation_finished.emit()
func _on_path_changed() -> void:
path_changed.emit()
func _on_target_reached() -> void:
target_reached.emit()
NavigationRegion2D 导航区域
gdscript
# navigation_region.gd
class_name NavigationRegion
extends NavigationRegion2D
@export var tile_map: TileMap
@export var bake_on_ready: bool = true
func _ready() -> void:
if bake_on_ready:
await get_tree().physics_frame
bake_navigation_polygon()
# 从 TileMap 几何数据生成导航多边形
func bake_from_tilemap() -> void:
var polygon := NavigationPolygon.new()
var outline: Array[Vector2] = []
var used_rect := tile_map.get_used_rect()
for x in range(used_rect.size.x):
for y in range(used_rect.size.y):
var cell := Vector2i(used_rect.position.x + x, used_rect.position.y + y)
var tile_data := tile_map.get_cell_tile_data(0, cell)
if tile_data and tile_data.get_custom_data("obstacle"):
# 障碍物格子不加入导航
continue
var world_pos := tile_map.map_to_local(cell)
outline.append(world_pos)
if not outline.is_empty():
polygon.add_outline(outline)
polygon.make_polygons_from_outlines()
navigation_polygon = polygon
bake_navigation_polygon()
动态障碍物
gdscript
# dynamic_obstacle.gd
class_name DynamicObstacle
extends Area2D
@export var radius: float = 32.0
@export var navigation_region: NavigationRegion
var _last_position: Vector2
func _ready() -> void:
area_entered.connect(_on_area_entered)
area_exited.connect(_on_area_exited)
func _physics_process(_delta: float) -> void:
if global_position != _last_position:
_last_position = global_position
_update_navigation()
func _update_navigation() -> void:
# 简单实现:移动时重新烘焙导航网格
# 生产环境建议使用 NavigationMesh::update()
if navigation_region:
navigation_region.bake_navigation_polygon()
func _on_area_entered(area: Area2D) -> void:
# 障碍物进入逻辑
pass
func _on_area_exited(area: Area2D) -> void:
# 障碍物离开逻辑
pass

2. AStarGrid2D 算法实现

AStarGrid2D 是 Godot 4 内置的高效网格寻路组件,适合规则网格的快速 A* 搜索。

基础 AStarGrid2D
gdscript
# astar_grid_2d.gd
class_name AStarGrid2D
extends Node
@export var tile_map: TileMap
@export var obstacles_layer: int = 0
var _grid: AStarGrid2D
func _ready() -> void:
_setup_grid()
func _setup_grid() -> void:
var region := tile_map.get_used_rect()
_grid = AStarGrid2D.new()
_grid.size = region.size
_grid.offset = tile_map.tile_set.tile_size / 2
_grid.cell_size = tile_map.tile_set.tile_size
_grid.center = false
_grid.diagonal_mode = AStarGrid2D.DIAGONAL_MODE_NEVER
_grid.update()
# 标记障碍物
for cell in tile_map.get_used_cells(obstacles_layer):
var tile_data := tile_map.get_cell_tile_data(obstacles_layer, cell)
if tile_data and tile_data.get_custom_data("obstacle"):
_grid.set_point_solid(cell - region.position)
func find_path(start: Vector2i, end: Vector2i) -> PackedVector2Array:
var region := tile_map.get_used_rect()
var local_start := start - region.position
var local_end := end - region.position
if not _grid.is_point_inside(local_start) or not _grid.is_point_inside(local_end):
return PackedVector2Array()
if _grid.is_point_solid(local_start) or _grid.is_point_solid(local_end):
return PackedVector2Array()
var path := _grid.get_point_path(local_start, local_end)
# 转换回世界坐标
var world_path := PackedVector2Array()
for point in path:
world_path.append(tile_map.map_to_local(point + region.position))
return world_path
func is_walkable(cell: Vector2i) -> bool:
var region := tile_map.get_used_rect()
var local_cell := cell - region.position
if not _grid.is_point_inside(local_cell):
return false
return not _grid.is_point_solid(local_cell)
func set_obstacle(cell: Vector2i, obstacle: bool) -> void:
var region := tile_map.get_used_rect()
var local_cell := cell - region.position
if obstacle:
_grid.set_point_solid(local_cell)
else:
_grid.clear_point(local_cell)
带权重的 AStarGrid2D
gdscript
# weighted_astar_grid.gd
class_name WeightedAStarGrid
extends AStarGrid2D
var _cell_weights: Dictionary = {}
func _ready() -> void:
_setup_grid()
func _setup_grid() -> void:
var region := tile_map.get_used_rect()
_grid = AStarGrid2D.new()
_grid.size = region.size
_grid.offset = tile_map.tile_set.tile_size / 2
_grid.cell_size = tile_map.tile_set.tile_size
_grid.center = false
_grid.diagonal_mode = AStarGrid2D.DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES
_grid.update()
# 初始化权重
for x in range(region.size.x):
for y in range(region.size.y):
var cell := Vector2i(region.position.x + x, region.position.y + y)
_initialize_cell_weight(cell)
# 标记障碍物
for cell in tile_map.get_used_cells(obstacles_layer):
var tile_data := tile_map.get_cell_tile_data(obstacles_layer, cell)
if tile_data and tile_data.get_custom_data("obstacle"):
_grid.set_point_solid(cell - region.position)
func _initialize_cell_weight(cell: Vector2i) -> void:
var tile_data := tile_map.get_cell_tile_data(obstacles_layer, cell)
if tile_data:
var weight := tile_data.get_custom_data("weight")
if weight != null:
_cell_weights[cell] = weight
else:
_cell_weights[cell] = 1.0
else:
_cell_weights[cell] = 1.0
func find_path(start: Vector2i, end: Vector2i) -> PackedVector2Array:
# 使用默认的 A* 路径(权重需要在使用时自定义处理)
return super.find_path(start, end)
# 估计成本(启发式函数)
func _estimate_cost(from: Vector2i, to: Vector2i) -> float:
var weight := _cell_weights.get(to, 1.0)
return (from - to).length() * weight

3. 自定义 A* 实现

标准 A* 算法
gdscript
# custom_astar.gd
# 自定义 A* 寻路实现
class_name CustomAStar
extends Node
@export var tile_map: TileMap
@export var obstacles_layer: int = 0
var _grid_size: Vector2i
var _walkable: Dictionary = {}
class AStarNode:
var cell: Vector2i
var g_cost: float # 从起点到当前节点的实际成本
var h_cost: float # 从当前节点到终点的估计成本
var f_cost: float: # g_cost + h_cost
return g_cost + h_cost
var parent: AStarNode = null
func _init(c: Vector2i, g: float, h: float) -> void:
cell = c
g_cost = g
h_cost = h
func _ready() -> void:
_initialize_grid()
func _initialize_grid() -> void:
_grid_size = tile_map.get_used_rect().size
var origin := tile_map.get_used_rect().position
for x in range(_grid_size.x):
for y in range(_grid_size.y):
var cell := Vector2i(origin.x + x, origin.y + y)
var tile_data := tile_map.get_cell_tile_data(obstacles_layer, cell)
_walkable[cell] = tile_data == null or not tile_data.get_custom_data("obstacle")
func find_path(start: Vector2i, end: Vector2i) -> Array[Vector2i]:
if not _walkable.has(start) or not _walkable.has(end):
return []
if not _walkable.get(end, false):
# 目标不可达,寻找最近的可通行点
end = _find_nearest_walkable(end)
if end == Vector2i(-1, -1):
return []
var open_set: Array[AStarNode] = []
var closed_set: Dictionary = {}
var start_node := AStarNode.new(start, 0.0, _heuristic(start, end))
open_set.append(start_node)
while not open_set.is_empty():
# 找到 f_cost 最低的节点
open_set.sort_custom(func(a, b): return a.f_cost < b.f_cost)
var current := open_set.pop_front()
if current.cell == end:
return _reconstruct_path(current)
closed_set[current.cell] = current
for neighbor in _get_neighbors(current.cell):
if closed_set.has(neighbor) or not _walkable.get(neighbor, false):
continue
var g_cost := current.g_cost + _get_move_cost(current.cell, neighbor)
var h_cost := _heuristic(neighbor, end)
var existing := _find_in_open_set(open_set, neighbor)
if existing == null:
var new_node := AStarNode.new(neighbor, g_cost, h_cost)
new_node.parent = current
open_set.append(new_node)
elif g_cost < existing.g_cost:
existing.g_cost = g_cost
existing.parent = current
return []
func _find_in_open_set(open_set: Array[AStarNode], cell: Vector2i) -> AStarNode:
for node in open_set:
if node.cell == cell:
return node
return null
func _heuristic(a: Vector2i, b: Vector2i) -> float:
# 曼哈顿距离
return absf(a.x - b.x) + absf(a.y - b.y)
func _get_move_cost(from: Vector2i, to: Vector2i) -> float:
# 斜向移动
if from.x != to.x and from.y != to.y:
return 1.414
return 1.0
func _get_neighbors(cell: Vector2i) -> Array[Vector2i]:
return [
cell + Vector2i(0, -1),
cell + Vector2i(1, 0),
cell + Vector2i(0, 1),
cell + Vector2i(-1, 0),
cell + Vector2i(1, -1),
cell + Vector2i(1, 1),
cell + Vector2i(-1, 1),
cell + Vector2i(-1, -1),
]
func _find_nearest_walkable(target: Vector2i) -> Vector2i:
var closest: Vector2i = Vector2i(-1, -1)
var min_dist := INF
for cell in _walkable.keys():
if _walkable[cell]:
var dist := (cell - target).length()
if dist < min_dist:
min_dist = dist
closest = cell
return closest
func _reconstruct_path(end_node: AStarNode) -> Array[Vector2i]:
var path: Array[Vector2i] = []
var current: AStarNode = end_node
while current != null:
path.push_front(current.cell)
current = current.parent
return path

4. 流式寻路(Flow Field Navigation)

流场寻路特别适合大量单位同时寻路的 RTS 游戏场景。

NavigationServer 流场
gdscript
# nav_flow_field.gd
# 使用 NavigationServer 实现流场
class_name NavFlowField
extends Node2D
@export var navigation_region: NavigationRegion2D
@export var tile_map: TileMap
@export var obstacles_layer: int = 0
@export var destination_layer: int = 1
var _nav_rid: RID
var _flow_map: Dictionary = {} # Vector2i -> Vector2
var _update_needed: bool = false
func _ready() -> void:
_nav_rid = navigation_region.navigation_rid
_build_initial_flow_field()
func _physics_process(_delta: float) -> void:
if _update_needed:
_build_flow_field()
_update_needed = false

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
103
Repository forks
14
7-day trend score
23

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/925236118/alphaagent/godot-navigation-system/badge)](https://verified-skill.com/skills/925236118/alphaagent/godot-navigation-system)

Something wrong with this skill or its page? Tell us.