【Godot4.0】自定义A*寻路拓展类TileMapAStar2D及其使用

本文介绍了Godot引擎中AStar2D和AStarGrid2D在2D寻路中的应用,强调了AStar2D的基础性和AStarGrid2D的自动化特性。作者提出扩展AStar2D以适应非方形图块的需求,并给出了使用示例和孤岛验证的方法。
摘要由CSDN通过智能技术生成

概述

Godot提供的AStar2DAStarGrid2D基本可以解决所有2D的A*寻路问题:

  • 前者提供了基础的A*寻路支持,但是需要手动处理很多内容
  • 后者针对基于方形图块的A*寻路,进行了很多自动化的工作,用起来十分简便。但是不使用于六边形、isometric之类的图块

所以针对AStar2DAStarGrid2D各自特性和现况,好的办法就是自己基于AStar2D扩展一个类似于AStarGrid2D自动化完成添加点和连线,又可以不限于仅在方形图块下使用的类型。


说明:原文写于2023年7月,Godot版本4.0,为了B友要求先发到CSDN中,有欠缺的部分后续再改。


源代码

# =====================================================
# TileMapAStar2D
# AStar2D扩展类型,用于为TileMap自动化的创建A*导航网格
# 作者:巽星石
# Godot版本:v4.0.3.stable.official [5222a99f5]
# 创建时间:202361718:33:28
# 最后修改时间:202361721: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为根节点的场景。
image.png
创建TileSet,并为可以通行的图块绘制导航多边形(默认形状即可)。
image.png
然后用可通行和不可通行的图块在TileMap上任意绘制一块地图区域。
image.png
为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

运行后就可以看到如下效果:
显示所有可通行位置的AStar2D的ID和对应TileMap的单元格坐标

显示导航网格

为TileMap添加一个Control类型的节点,起名叫debug。用来实现TileMapAStar2D自动生成的导航网格。
image.png

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导航网格。
利用debug层显示AStar网格
有了 TileMapAStar2DTileMap自动化的生成导航网格,以及利用其show_navigation_cells_posshow_navigation_cells_id属性,还有用debug层绘制导航网格,则基于TileMap进行A
导航就有了非常好的视觉基础。无论是学习还是使用都更直观了。
当然对于一般四边形TileSet应该也是适用的,只不过Godot4已经提供了AStarGrid2D专用于四边形TileSet,但是它只适用于四边形网格,而TileMapAStar2D则更具有宽泛的适用性。
TileMapAStar2D用于方形TileSet的效果
在Half-Offset下的验证
在isometric图块下的验证

孤岛验证

验证算法在任何孤立的导航区域都能正确生成A*导航网格。
六边形图块的孤岛验证

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巽星石

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值