TileMap BFS寻路实现、六边形网格寻路、路径追溯与流式寻路功能。用于需要网格寻路、动态障碍物避让、游戏AI导航等场景。
Scanned 9/12/2026
Install to Claude Code
npx -y skills add 925236118/AlphaAgent --skill godot-pathfinding --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Godot Pathfinding?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/925236118-godot-pathfinding)More formats (shields.io, HTML) on the badges page.
---
name: godot-pathfinding
description: TileMap BFS寻路实现、六边形网格寻路、路径追溯与流式寻路功能。用于需要网格寻路、动态障碍物避让、游戏AI导航等场景。
---
# Godot TileMap 寻路系统
TileMap网格寻路实现,包含BFS扩散算法、六边形网格支持、路径追溯以及流场寻路功能。
## 何时使用此技能
- 需要为格子地图实现寻路功能
- 实现RTS、塔防、回合制策略等游戏
- 需要动态障碍物避让
- 需要流场寻路支持多单位导航
## 1. TileMap BFS 寻路实现
### 基础 BFS 扩散算法
```gdscript
# tile_map_bfs.gd
# TileMap网格BFS寻路实现
class_name TileMapBFS
extends Node2D
signal path_found(path: Array[Vector2i])
signal path_not_found
@export var tile_map: TileMap
@export var obstacles_layer: int = 0 # 障碍物所在的层
var _grid_size: Vector2i
var _walkable: Dictionary = {} # {Vector2i: bool}
func _ready() -> void:
if tile_map:
_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):
return []
if start == end:
return [start]
var open_set: Array[Vector2i] = [start]
var came_from: Dictionary = {}
var visited: Dictionary = {start: true}
while not open_set.is_empty():
var current := open_set.pop_front()
if current == end:
return _reconstruct_path(came_from, current)
for neighbor in _get_neighbors(current):
if not visited.get(neighbor, false) and _walkable.get(neighbor, false):
visited[neighbor] = true
came_from[neighbor] = current
open_set.append(neighbor)
return []
func _get_neighbors(cell: Vector2i) -> Array[Vector2i]:
return [
cell + Vector2i(0, -1), # 上
cell + Vector2i(1, 0), # 右
cell + Vector2i(0, 1), # 下
cell + Vector2i(-1, 0), # 左
]
func _reconstruct_path(came_from: Dictionary, current: Vector2i) -> Array[Vector2i]:
var path: Array[Vector2i] = [current]
while came_from.has(current):
current = came_from[current]
path.push_front(current)
return path
func is_walkable(cell: Vector2i) -> bool:
return _walkable.get(cell, false)
func set_obstacle(cell: Vector2i, obstacle: bool) -> void:
_walkable[cell] = not obstacle
```
### 带权重的 BFS(用于斜向移动)
```gdscript
# weighted_bfs.gd
class_name WeightedBFS
extends Node
@export var tile_map: TileMap
@export var obstacles_layer: int = 0
var _cell_cost: Dictionary = {} # 每个格子的移动成本
func _ready() -> void:
_initialize_costs()
func _initialize_costs() -> void:
var rect := tile_map.get_used_rect()
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:
_cell_cost[cell] = tile_data.get_custom_data("cost")
else:
_cell_cost[cell] = 1.0
func find_path(start: Vector2i, end: Vector2i) -> Array[Vector2i]:
if start == end:
return [start]
var open_set: Array[Vector2i] = [start]
var came_from: Dictionary = {}
var cost_so_far: Dictionary = {start: 0.0}
var visited: Dictionary = {}
while not open_set.is_empty():
open_set.sort_custom(func(a, b): return cost_so_far[a] < cost_so_far[b])
var current := open_set.pop_front()
if current == end:
return _reconstruct_path(came_from, current)
visited[current] = true
for neighbor in _get_neighbors(current):
if visited.get(neighbor, false):
continue
var move_cost := _get_move_cost(current, neighbor)
var new_cost := cost_so_far[current] + move_cost
if not cost_so_far.has(neighbor) or new_cost < cost_so_far[neighbor]:
cost_so_far[neighbor] = new_cost
came_from[neighbor] = current
if not open_set.has(neighbor):
open_set.append(neighbor)
return []
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 _get_move_cost(from: Vector2i, to: Vector2i) -> float:
var base_cost := _cell_cost.get(to, 1.0)
# 斜向移动成本更高
if from.x != to.x and from.y != to.y:
return base_cost * 1.414 # sqrt(2)
return base_cost
func _reconstruct_path(came_from: Dictionary, current: Vector2i) -> Array[Vector2i]:
var path: Array[Vector2i] = [current]
while came_from.has(current):
current = came_from[current]
path.push_front(current)
return path
```
## 2. 六边形网格寻路
### 六边形网格坐标系统
```gdscript
# hexagon_pathfinding.gd
class_name HexagonPathfinding
extends Node
# 六边形网格偏移系统
enum OffsetSystem { POINTY_TOP, FLAT_TOP }
enum CoordinateSystem { CUBE, AXIAL, OFFSET }
@export var offset_system: OffsetSystem = OffsetSystem.POINTY_TOP
@export var tile_map: TileMap
@export var obstacles_layer: int = 0
var _cube_directions := [
Vector3i(1, -1, 0), Vector3i(1, 0, -1), Vector3i(0, 1, -1),
Vector3i(-1, 1, 0), Vector3i(-1, 0, 1), Vector3i(0, -1, 1)
]
func axial_to_cube(hex: Vector2i) -> Vector3i:
return Vector3i(hex.x, hex.y, -hex.x - hex.y)
func cube_to_axial(cube: Vector3i) -> Vector2i:
return Vector2i(cube.x, cube.y)
func offset_to_axial(offset: Vector2i) -> Vector2i:
if offset_system == OffsetSystem.POINTY_TOP:
return Vector2i(offset.x, offset.y - (offset.x - (offset.x & 1)) / 2)
else:
return Vector2i(offset.x - (offset.y - (offset.y & 1)) / 2, offset.y)
func axial_to_offset(axial: Vector2i) -> Vector2i:
if offset_system == OffsetSystem.POINTY_TOP:
return Vector2i(axial.x, axial.y + (axial.x - (axial.x & 1)) / 2)
else:
return Vector2i(axial.x + (axial.y - (axial.y & 1)) / 2, axial.y)
func get_neighbors(hex: Vector2i) -> Array[Vector2i]:
var axial := offset_to_axial(hex)
var cube := axial_to_cube(axial)
var neighbors: Array[Vector2i] = []
for direction in _cube_directions:
var neighbor_cube := cube + direction
var neighbor_axial := cube_to_axial(neighbor_cube)
var neighbor_offset := axial_to_offset(neighbor_axial)
neighbors.append(neighbor_offset)
return neighbors
func is_walkable(hex: Vector2i) -> bool:
var tile_data := tile_map.get_cell_tile_data(obstacles_layer, hex)
return tile_data == null or not tile_data.get_custom_data("obstacle")
func find_path(start: Vector2i, end: Vector2i) -> Array[Vector2i]:
if start == end:
return [start]
if not is_walkable(end):
return []
var open_set: Array[Vector2i] = [start]
var came_from: Dictionary = {}
var visited: Dictionary = {start: true}
while not open_set.is_empty():
var current := open_set.pop_front()
if current == end:
return _reconstruct_path(came_from, current)
for neighbor in get_neighbors(current):
if not visited.get(neighbor, false) and is_walkable(neighbor):
visited[neighbor] = true
came_from[neighbor] = current
open_set.append(neighbor)
return []
func _reconstruct_path(came_from: Dictionary, current: Vector2i) -> Array[Vector2i]:
var path: Array[Vector2i] = [current]
while came_from.has(current):
current = came_from[current]
path.push_front(current)
return path
# 计算两个六边形之间的距离
func distance(a: Vector2i, b: Vector2i) -> int:
var cube_a := axial_to_cube(offset_to_axial(a))
var cube_b := axial_to_cube(offset_to_axial(b))
return maxi(
maxi(abs(cube_a.x - cube_b.x), abs(cube_a.y - cube_b.y)),
abs(cube_a.z - cube_b.z)
)
```
## 3. 路径追溯与平滑
### 路径可视化与平滑
```gdscript
# path_follower.gd
class_name PathFollower
extends Node2D
@export var path_line: Line2D
@export var move_speed: float = 200.0
var current_path: Array[Vector2i] = []
var current_index: int = 0
var is_moving: bool = false
var target_position: Vector2
signal path_completed
signal position_changed(new_pos: Vector2)
func set_path(path: Array[Vector2i], grid_to_world: Callable) -> void:
current_path = path
current_index = 0
is_moving = false
if path.is_empty():
return
target_position = grid_to_world.call(path[0])
position = target_position
func _physics_process(delta: float) -> void:
if not is_moving or current_path.is_empty():
return
var world_pos := grid_to_world(current_path[current_index])
var direction := (world_pos - position).normalized()
var distance := (world_pos - position).length()
if distance < 5.0:
current_index += 1
position_changed.emit(position)
if current_index >= current_path.size():
is_moving = false
path_completed.emit()
else:
position += direction * move_speed * delta
func start_moving() -> void:
is_moving = true
func stop_moving() -> void:
is_moving = false
func grid_to_world(grid_pos: Vector2i) -> Vector2:
return tile_map.map_to_local(grid_pos)
# 平滑路径(去除多余拐点)
func smooth_path(path: Array[Vector2i], line_of_sight: Callable) -> Array[Vector2i]:
if path.size() <= 2:
return path
var smoothed: Array[Vector2i] = [path[0]]
var current := 0
while current < path.size() - 1:
var furthest := current + 1
for i in range(path.size() - 1, current, -1):
if line_of_sight.call(path[current], path[i]):
furthest = i
break
smoothed.append(path[furthest])
current = furthest
return smoothed
```
## 4. 流式寻路(Flow Field)
流场寻路是RTS游戏中常用的技术,所有单位共享同一个流场,实现自然的群体避让。
### 流场构建原理
```gdscript
# flow_field.gd
# 流场寻路实现
# 原理:
# 1. 从目标点向外BFS扩散,构建距离场
# 2. 每个格子记录到目标的最短距离
# 3. 流场方向指向距离递减最快的方向
# 4. 单位沿流场方向移动即可到达目标
class_name FlowField
extends Node2D
@export var tile_map: TileMap
@export var obstacles_layer: int = 0
@export var destination_layer: int = 1
var _grid_size: Vector2i
var _cell_size: Vector2i
var _distance_field: Dictionary = {} # {Vector2i: float}
var _flow_field: Dictionary = {} # {Vector2i: Vector2}
var _walkable: Dictionary = {}
var _use_diagonals: bool = true
signal field_ready
func _ready() -> void:
if tile_map:
_initialize()
func _initialize() -> void:
var used_rect := tile_map.get_used_rect()
_grid_size = used_rect.size
_cell_size = tile_map.tile_set.tile_size
_build_walkable_map(used_rect)
func _build_walkable_map(rect: Rect2i) -> void:
for x in range(rect.size.x):
for y in range(rect.size.y):
var cell := Vector2i(rect.position.x + x, rect.position.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 build_field(destination: Vector2i) -> void:
_distance_field.clear()
_flow_field.clear()
# 如果目标不可行走,从最近的可行走格子开始
if not _walkable.get(destination, false):
destination = _find_nearest_walkable(destination)
if destination == Vector2i(-1, -1):
return
# BFS 扩散构建距离场
var queue: Array[Vector2i] = [destination]
_distance_field[destination] = 0.0
while not queue.is_empty():
var current := queue.pop_front()
var current_dist := _distance_field[current]
for neighbor in _get_neighbors(current):
if not _walkable.get(neighbor, false):
continue
var new_dist := current_dist + _get_move_cost(current, neighbor)
if not _distance_field.has(neighbor) or new_dist < _distance_field[neighbor]:
_distance_field[neighbor] = new_dist
queue.append(neighbor)
# 构建流场
for cell in _distance_field.keys():
_flow_field[cell] = _calculate_flow_direction(cell)
field_ready.emit()
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 _get_neighbors(cell: Vector2i) -> Array[Vector2i]:
var neighbors: Array[Vector2i] = [
cell + Vector2i(0, -1),
cell + Vector2i(1, 0),
cell + Vector2i(0, 1),
cell + Vector2i(-1, 0),
]
if _use_diagonals:
neighbors.append_array([
cell + Vector2i(1, -1),
cell + Vector2i(1, 1),
cell + Vector2i(-1, 1),
cell + Vector2i(-1, -1),
])
return neighbors
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 _calculate_flow_direction(cell: Vector2i) -> Vector2:
var neighbors := _get_neighbors(cell)
var best_direction := Vector2.ZERO
var lowest_distance := INF
for neighbor in neighbors:
if _distance_field.has(neighbor):
var dist := _distance_field[neighbor]
if dist < lowest_distance:
lowest_distance = dist
var dir := Vector2(neighbor - cell).normalized()
best_direction = dir
return best_direction
func get_flow_direction(cell: Vector2i) -> Vector2:
if _flow_field.has(cell):
return _flow_field[cell]
return Vector2.ZERO
func get_distance(cell: Vector2i) -> float:
return _distance_field.get(cell, INF)
# 移动单位沿流场方向
func move_along_flow(unit_position: Vector2, speed: float, delta: float) -> Vector2:
var cell := tile_map.local_to_map(unit_position)
var flow := get_flow_direction(cell)
if flow.length() > 0.01:
return unit_position + flow * speed * delta
else:
# 如果没有流向(不在流场中),尝试随机移动
return unit_position + Vector2.RIGHT * speed * delta * 0.5
```
### 多个目标的流场
```gdscript
# multi_target_flow_field.gd
class_name MultiTargetFlowField
extends FlowField
var _target_cells: Array[Vector2i] = []
func add_target(cell: Vector2i) -> void:
if not cell in _target_cells:
_target_cells.append(cell)
func remove_target(cell: Vector2i) -> void:
_target_cells.erase(cell)
func clear_targets() -> void:
_target_cells.clear()
func build_field() -> void:
if _target_cells.is_empty():
return
_distance_field.clear()
_flow_field.clear()
# 多目标BFS
var queue: Array[Vector2i] = _target_cells.duplicate()
var visited: Dictionary = {}
for target in _target_cells:
if _walkable.get(target, false):
_distance_field[target] = 0.0
visited[target] = true
while not queue.is_empty():
var current := queue.pop_front()
var current_dist := _distance_field[current]
for neighbor in _get_neighbors(current):
if not _walkable.get(neighbor, false):
continue
if visited.get(neighbor, false):
continue
visited[neighbor] = true
_distance_field[neighbor] = current_dist + _get_move_cost(current, neighbor)
queue.append(neighbor)
# 构建流场
for cell in _distance_field.keys():
_flow_field[cell] = _calculate_flow_direction(cell)
field_ready.emit()
```
## 5. 完整示例:RTS 单位寻路系统
```gdscript
# rts_unit_pathfinding.gd
# 完整的RTS单位寻路系统
class_name RTSUnitPathfinding
extends CharacterBody2D
@export var move_speed: float = 150.0
@export var flow_field: FlowField
@export var unit_radius: float = 16.0
var _current_target: Vector2i = Vector2i(-1, -1)
var _is_selected: bool = false
var _formation_offset: Vector2 = Vector2.ZERO
@onready var selection_indicator: Sprite2D = $SelectionIndicator
@onready var unit_sprite: Sprite2D = $Sprite2D
func _ready() -> void:
selection_indicator.visible = false
func _physics_process(delta: float) -> void:
if _current_target != Vector2i(-1, -1):
_move_along_flow(delta)
func _move_along_flow(delta: float) -> void:
var flow_dir := flow_field.get_flow_direction(global_position)
if flow_dir.length() > 0.01:
# 应用编队偏移
var target_pos := global_position + flow_dir * move_speed * delta + _formation_offset * 0.1
# 简单避让
var avoidance := _calculate_avoidance()
target_pos += avoidance * 50.0 * delta
global_position = target_pos
# 旋转朝向移动方向
rotation = flow_dir.angle()
else:
# 到达目标
_current_target = Vector2i(-1, -1)
func _calculate_avoidance() -> Vector2:
var avoidance := Vector2.ZERO
var nearby_units := get_tree().get_nodes_in_group("rts_units")
for unit in nearby_units:
if unit == self:
continue
var dist := global_position.distance_to(unit.global_position)
if dist < unit_radius * 3:
var push_dir := (global_position - unit.global_position).normalized()
avoidance += push_dir * (1.0 - dist / (unit_radius * 3))
return avoidance
func set_target(world_position: Vector2) -> void:
_current_target = flow_field.tile_map.local_to_map(world_position)
func set_selected(selected: bool) -> void:
_is_selected = selected
selection_indicator.visible = selected
func set_formation_offset(offset: Vector2) -> void:
_formation_offset = offset
```
## 性能优化建议
1. **缓存寻路结果**:对于相同起点的查询,直接返回缓存路径
2. **批量更新流场**:多个单位共享同一流场,避免重复计算
3. **分层寻路**:远距离使用粗糙网格,近距离使用精细网格
4. **异步计算**:复杂寻路在后台线程计算,避免阻塞主线程
## 最佳实践
- TileMap 障碍物使用自定义数据 `obstacle: true` 标记
- 流场适合大量单位同时寻路的场景
- 六边形网格适合策略游戏
- 路径平滑使用视线检测去除多余拐点
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!