概述
Godot提供的AStar2D
和AStarGrid2D
基本可以解决所有2D的A*寻路问题:
- 前者提供了基础的A*寻路支持,但是需要手动处理很多内容
- 后者针对基于方形图块的A*寻路,进行了很多自动化的工作,用起来十分简便。但是不使用于六边形、isometric之类的图块
所以针对AStar2D
和AStarGrid2D
各自特性和现况,好的办法就是自己基于AStar2D
扩展一个类似于AStarGrid2D
自动化完成添加点和连线,又可以不限于仅在方形图块下使用的类型。
说明:原文写于2023年7月,Godot版本4.0,为了B友要求先发到CSDN中,有欠缺的部分后续再改。
源代码
# =====================================================
# TileMapAStar2D
# AStar2D扩展类型,用于为TileMap自动化的创建A*导航网格
# 作者:巽星石
# Godot版本:v4.0.3.stable.official [5222a99f5]
# 创建时间:2023年6月17日18:33:28
# 最后修改时间:2023年6月17日21:13:44
# ====================================================
extends AStar2D
class_name TileMapAStar2D
var _tile_map:TileMap
var _layer_id:int
var _pos_labs:Array[Label] = []
var _id_labs:Array[Label] = []
# 是否用Label显示所有可通行位置的单元格坐标
@export var show_navigation_cells_pos:bool = false:
set(val):
show_navigation_cells_pos = val
if val:
for cell in get_has_navigation_cells():
show_cell_pos(cell)
else:
for lab in _pos_labs:
lab.queue_free()
_pos_labs.clear()
# 是否用Label显示所有可通行位置的单元格坐标
@export var show_navigation_cells_id:bool = false:
set(val):
show_navigation_cells_id = val
if val:
for point_id in get_point_ids():
var cell = Vector2i(get_point_position(point_id))
if cell in get_has_navigation_cells():
show_cell_id(cell,point_id)
else:
for lab in _id_labs:
lab.queue_free()
_id_labs.clear()
# 基于TileMap,添加可通行点和连接线,创建Astar网格,并返回一个AStar2D实例
func _init(tile_map:TileMap,layer_id:int):
# 存储TileMap对象和layer_id
_tile_map = tile_map
_layer_id = layer_id
# 添加可通行点
for cell in get_has_navigation_cells():
var id = get_available_point_id()
add_point(id,cell)
# 遍历已经添加了的可通行点,进行连线
var points_ids = get_point_ids()
for point_id in points_ids:
var point_cell_pos = get_point_position(point_id) # id转为单元格坐标
var surround_cell_pos_arr = tile_map.get_surrounding_cells(point_cell_pos)# 获取周边6个位置
for cel in surround_cell_pos_arr:
var cel_id = get_closest_point(cel)# 尝试获取最接近的点的id
if Vector2i(get_point_position(cel_id)) in surround_cell_pos_arr: # 验证获取的点的ID在周围6个位置内
if cel_id in points_ids: # 验证点在已经添加的位置内
if !are_points_connected(cel_id,point_id): # 验证两个点之间不存在连接
connect_points(cel_id,point_id)
# 判断单元格是否存在导航多边形
func is_cell_has_navigation_polygon(cell:Vector2i):
var bol = false
var data = _tile_map.get_cell_tile_data(_layer_id,cell)
if data:
if data.get_navigation_polygon(_layer_id): # 有导航区域
bol = true
return bol
# 获取TileMap所有拥有导航网格的单元格
# 查找是在get_used_cells基础上进行的
func get_has_navigation_cells() -> Array[Vector2i]:
var cells:Array[Vector2i] = []
# 获取所有已经绘制的单元格
var used_cells:Array[Vector2i] = _tile_map.get_used_cells(_layer_id)
# 遍历所有网格,将带有导航区域的单元格位置添加为AStar的可通行点
for cell in used_cells:
if is_cell_has_navigation_polygon(cell): # 格子有导航多边形
cells.append(cell)
return cells
# =================================== 底层辅助函数 ===================================
func create_lab(content:String,font_color:Color = Color("#ffffff")):
var lab = Label.new()
lab.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
lab.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
# 构造LabelSettings
var lab_setting = LabelSettings.new()
lab_setting.font_color = font_color
lab_setting.font_size = _tile_map.tile_set.tile_size.x /8
lab_setting.outline_color = Color("#444444")
lab_setting.outline_size = lab_setting.font_size/3
lab_setting.shadow_color = Color("#3333339e")
lab_setting.shadow_size = 1
lab_setting.shadow_offset = Vector2(2,2)
# 其他配置
lab.label_settings = lab_setting
lab.text = content
return lab
# 显示TileMap对应单元格的位置信息
func show_cell_pos(cell:Vector2i):
var lab = create_lab(str(cell))
lab.position = _tile_map.map_to_local(cell)
_tile_map.add_child(lab)
_pos_labs.append(lab)
# 显示单元格对应的AStar2D的点ID
func show_cell_id(cell:Vector2i,id:int):
var lab = create_lab(str(id),Color.ORANGE)
lab.position = _tile_map.map_to_local(cell) - Vector2(_tile_map.tile_set.tile_size /5.5)
_tile_map.add_child(lab)
_id_labs.append(lab)
使用方法
在一个使用TileMap的场景中,比如这里直接有一个以TileMap为根节点的场景。
创建TileSet
,并为可以通行的图块绘制导航多边形(默认形状即可)。
然后用可通行和不可通行的图块在TileMap上任意绘制一块地图区域。
为TileMap添加如下脚本:
extends TileMap
var astar = TileMapAStar2D.new(self,0)
func _ready():
astar.show_navigation_cells_pos = true # 显示单元格的坐标
astar.show_navigation_cells_id = true # 显示TileMapAStar2D为单元个创建的位置ID
运行后就可以看到如下效果:
显示导航网格
为TileMap添加一个Control类型的节点,起名叫debug。用来实现TileMapAStar2D自动生成的导航网格。
extends Control
var astar:AStar2D
@export var path_color:Color = Color.YELLOW_GREEN
@export var point_color:Color = Color.YELLOW_GREEN
func _ready():
astar = get_parent().astar
func _draw():
if astar:
var map:TileMap = get_parent()
var points_ids = astar.get_point_ids()
# 先画线
for point_id in points_ids:
var cel_pos = astar.get_point_position(point_id) # 转为单元格位置坐标
var cel_screen_pos = map.map_to_local(cel_pos) # 转为屏幕坐标
# 获取连接信息
var connects = astar.get_point_connections(point_id)
for cnt_point_id in connects:
var cnt_pos = astar.get_point_position(cnt_point_id) # 转为单元格位置坐标
var cnt_screen_pos = map.map_to_local(cnt_pos) # 转为屏幕坐标
draw_line(cel_screen_pos,cnt_screen_pos,path_color,1)
# 后画点
for point_id in points_ids:
var cel_pos = astar.get_point_position(point_id) # 转为单元格位置坐标
draw_circle(map.map_to_local(cel_pos),5,point_color)
将TileMap的脚本改写为:
extends TileMap
var astar = TileMapAStar2D.new(self,0)
@onready var debug = $debug
func _ready():
debug.astar = astar
astar.show_navigation_cells_pos = true
astar.show_navigation_cells_id = true
debug.queue_redraw()
运行后就可以看到绘制出的A导航网格。
有了 TileMapAStar2D
为TileMap
自动化的生成导航网格,以及利用其show_navigation_cells_pos
和show_navigation_cells_id
属性,还有用debug
层绘制导航网格,则基于TileMap进行A导航就有了非常好的视觉基础。无论是学习还是使用都更直观了。
当然对于一般四边形TileSet应该也是适用的,只不过Godot4
已经提供了AStarGrid2D
专用于四边形TileSet
,但是它只适用于四边形网格,而TileMapAStar2D
则更具有宽泛的适用性。
孤岛验证
验证算法在任何孤立的导航区域都能正确生成A*导航网格。