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

概述

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*导航网格。
六边形图块的孤岛验证

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/464631.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

人人站CMS后台登不进去解决方案(已解决)

公司有一个网站使用的是人人站CMS,最近发现后台登录不进去,有以下报错 发生以下错误: file get contents(http://www.rrzcms.com/Public/cms/config/config.ison): failed to open stream: HTTP reguest failed! 请求的URL导致内部服务器错误。 如果您反…

3.4 bp,si,di寄存器,寻址方式,寄存器总结

汇编语言 1. [bxidata] 我们可以用[bx]来指明一个内存单元我们也可以用[bxidata]来表示一个内存单元,它的偏移地址为bx中的数值加上idata mount c d:masm c: debug r d 2000:1000 e 2000:1000 12 34 56 78 a mov ax,2000 mov ds,ax mov bx,1000 mov ax,[bx] mov c…

如何创建用户流(User Flow):分步指南

原文作者:Camren Browne,CareerFoundry 翻译:数字营销工兵 (sources: 图片来源于网络) 用户流(User Flow)是当今用户体验行业中最有用但被误解的工具之一。资深设计师经常避开它们,而初级设计师则很难抓住它们。 事…

代码算法训练营day7 | 454.四数相加II、383. 赎金信、15. 三数之和、18. 四数之和

day7: 剩下的两题: 15. 三数之和18. 四数之和 15. 三数之和 题目链接 状态: 文档:programmercarl.com 注意: 这和第一题中的四数相加Ⅱ很像,如果用哈希算法的思路就是: 两层for循环就可以确定 a 和b 的数值…

C++面向对象程序设计 - 创建学生类

在20世纪80年代提出了面向对象的程序设计(Object oriented programming, OOP)思想,在此形势下,C由AT&TBell(贝尔)实验室于20世纪80年代初在C语言的基础上开发成功,C保留了C语言原有的所有优…

(C语言)整数在内存中的存储与大小端

1. 整数在内存中的存储 整数的2进制表示方法有三种 ,即 原码、反码和补码 有符号类型数据三种表示方法均有符号位和数值位两部分 ,符号位都是用0表示“正” ,用1表示“负” ,最高位的一位是被当做符号位 ,剩余的都是…

智慧公厕建设的主要目标是什么?

随着城市化进程的不断推进,公共厕所作为城市基础设施的重要组成部分,也变得越来越重要。为了提升公共厕所的管理水平、提供更好的服务质量,智慧公厕应运而生。智慧公厕的建设旨在通过信息化手段实现公共厕所的全面感知监测,实现公…

VGG论文学习笔记

题目:VERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE-SCALE IMAGE RECOGNITION 论文下载地址:VGG论文 摘要 目的:研究深度对精度的影响 方法:使用3*3滤波器不断增加深度,16和19效果显著 成绩:在ImageNet 20…

C++ 智能指针的使用

智能指针类型 在C程序中,普通变量使用栈内存,为函数运行时专用,结束后会自动释放,无须考虑内存释放问题。 但堆内存是共用的,其使用是通过指针变量的new来分配,使用delete来释放,因指针使用方便…

AI预测-一文解析AI预测数据工程

AI预测相关目录 AI预测流程,包括ETL、算法策略、算法模型、模型评估、可视化等相关内容 最好有基础的python算法预测经验 EEMD策略及踩坑VMD-CNN-LSTM时序预测对双向LSTM等模型添加自注意力机制K折叠交叉验证optuna超参数优化框架多任务学习-模型融合策略Transform…

Flink程序员开发利器本地化WebUI生成

前言 在flink程序开发或者调试过程中,每次部署到集群上都需要不断打包部署,其实是比较麻烦的事情,其实flink一直就提供了一种比较好的方式使得开发同学不用部署就可以观察到flink执行情况。 上代码 第一步:开发之前需要引入在本…

中间件漏洞(redis)

目录 1.Redis服务器被挖矿案例 2.redis常见用途 3.redis环境配置 4.redis的持久化机制 5.redis动态修改配置 6.webshell提权案例 7.定时任务bash反弹连接提权案例 8.SSH Key提权案例 9.redis安全加固分析 1.Redis服务器被挖矿案例 我没有体验过,那就看看别…

Flutter:构建美观应用的跨平台方案

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…

【Fitten Code】“吊打“Github Copilot的国内免费代码辅助插件

🌻个人主页:相洋同学 🥇学习在于行动、总结和坚持,共勉! 目录 1.Github Copilot 2.Fitten Code 2.1 对话体验: 2.2 代码补全体验: 2.3 Pycharm安装方法: 2.4 Vscode安装方法…

git基础命令(一)

目录 基础概念git statusgit addgit diffgit loggit commit文件可以处于以下三种状态之一远程存储库与本地存储库参考 用于知识记录。后续有新的的内容,例子,将持续更新本文档。 基础概念 工作树:git add 之前,变动内容的文件列表…

Linux课程_____用户的管理

一、规则 用户至少属于一个组,在创建时如果不指定组,将会创建同名的组 用户只能有一个基本组(主组),但可以隶属于多个附加组 如果一个组作为某用户的基本组,此组将不能被删除 UID: 用户标识 GID: 组的标识 root管理员的uid及gid 都为0 二、用户的配置文件 1./etc/passwd …

<c语言学习> 整数和浮点数的存储方式

1.整数 有符号整数 第一位为符号位 1代表负数 0代表正数 举例: signed char 8 ---------------------> 0000 1000 -8 ----------------------> 1111 1000 (补码形式存储) 补码存储(计算)的妙处&…

Discourse 分类图片

我们可以在 Discourse 上为分类添加图片。 进入分类编辑界面,然后选择 Image 标签。 在 Images 标签下,上传分类需要的图片。 图片大小 图片的大小是 Discourse 进行控制的,高度为 150 PX 像素。 如果上传的图片大于 150 px 的高度像素&…

【JavaSE】类与对象

前言 Java是一门纯面向对象的语言,在面向对象的世界里,一切都为对象。它是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。类与对象是我们学习面向对象最基础的知识,是面向对象实现的基石,可见它是有多么重…

打破数据孤岛,TDengine 与 Tapdata 实现兼容性互认证

当前,传统行业正面临着数字化升级的紧迫需求,但海量时序数据的处理以及数据孤岛问题却日益突出。越来越多的传统企业选择引入时序数据库(Time Series Database,TSDB)升级数据架构,同时,为了克服…