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

news2025/1/11 21:02:26

概述

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++for语句(2)

11.乘方计算 给出一个整数a和一个正整数n&#xff08;-1000000<a<1000000,1<n<100000&#xff09;&#xff0c;求乘方&#xff0c;即乘方的结果。最终结果的绝对值不超过1000000。 输入 一行&#xff0c;包含两个整数a和n&#xff08;-1000000<a<1000000,1…

网站安全监测:守护网络空间的坚实防线

随着互联网技术的飞速发展和广泛应用&#xff0c;网站已成为企业、机构和个人展示形象、提供服务、传递信息的重要平台。然而&#xff0c;与此同时&#xff0c;网站也面临着日益严重的安全威胁。黑客攻击、数据泄露、恶意软件等安全问题频发&#xff0c;给网站运营者带来了巨大…

redis发布订阅与stream类型

发布订阅 redis发布订阅(pub/sub)是一种消息通信模式&#xff1b;发送者(pub)发送消息&#xff0c;订阅者(sub)接收消息。redis客户端可以订阅任意数量的频道。 基础命令&#xff1a; 语法 redis publish命令基本语法如下&#xff1a; redis 127.0.0.1:6379> PUBLISH ch…

若你有才能,最好能遇上识才之人,高俅发迹的故事很好诠释了千里马与伯乐的关系

若你有才能&#xff0c;最好能遇上识才之人&#xff0c;高俅发迹的故事很好诠释了千里马与伯乐的关系 其实&#xff0c;“千里马”和“伯乐”都是中国古代传说里的角色。伯乐是古代一个善于相马&#xff08;识别马的好坏&#xff09;的人&#xff0c;而“千里马”则是指一匹能跑…

解决:由于找不到xinput1_3.dll,无法继续执行代码问题的方法

xinput1_3.dll文件的丢失是一个常见的问题&#xff0c;它会导致一些游戏应用程序无法正常运行或出现错误。为了解决这个问题&#xff0c;我们可以采取多种方法。下面将介绍几种常用的xinput1_3.dll丢失的解决方法&#xff0c;通过采用合适的方法&#xff0c;我们可以轻松解决该…

跳绳计数,YOLOV8POSE

跳绳计数&#xff0c;YOLOV8POSE 通过计算腰部跟最初位置的上下波动&#xff0c;计算跳绳的次数

Redis数据结构对象之字符串对象

字符串对象 字符串对象的编码可以是int、raw或者embstr 如果一个字符串对象保存的是整数值&#xff0c;并且这个整数值可以用long类型来表示&#xff0c;那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void *转换成long)&#xff0c;并且将字符串对象的编码设…

zookeeper快速入门四:在java客户端中操作zookeeper

系列文章&#xff1a; zookeeper快速入门一&#xff1a;zookeeper安装与启动-CSDN博客 zookeeper快速入门二&#xff1a;zookeeper基本概念-CSDN博客 zookeeper快速入门三&#xff1a;zookeeper的基本操作 先启动zookeeper服务端。 在maven引入zookeeper依赖。 <depende…

代码算法训练营day10 | 232.用栈实现队列、225. 用队列实现栈

day10: 232.用栈实现队列225. 用队列实现栈 232.用栈实现队列 题目链接 状态&#xff1a; 文档&#xff1a;programmercarl.com 思路&#xff1a; 用栈实现队列。要先明白两者的区别。 栈&#xff1a;单开门&#xff0c;先进后出&#xff0c;只有一端能进出。 队列&#xff1a;…

【晴问算法】入门篇—贪心算法—区间不相交问题

题目描述 给定n个开区间&#xff0c;从中选择尽可能多的开区间&#xff0c;使得这些开区间两两没有交集。 输入描述 输出描述 输出一个整数&#xff0c;表示最多选择的开区间个数。 样例1输入 4 1 3 2 4 3 5 6 7 输出 3 解释 最多选择(1,3)、(3,5)、(6,7)三个区间&#xff0c;它…

从零到一构建短链接系统(三)

1.根据数据库表&#xff0c;利用在线网站https://jully.top/generator/ 根据数据库Info自动生成代码 2.在entity中创建UserDO Data TableName("t_user") public class UserDO { /** * id */ private Long id; /** * 用户名 */ private String username; /** * 密码…

图解Kafka架构学习笔记(一)

本文参考尚硅谷大数据技术之Kafka。 消息队列 &#xff08;1&#xff09;点对点模式&#xff08;一对一&#xff0c;消费者主动拉取数据&#xff0c;消息收到后消息清除&#xff09; 点对点模型通常是一个基于拉取或者轮询的消息传送模型&#xff0c;这种模型从队列中请求信息…

[嵌入式系统-40]:龙芯1B 开发学习套件 -10-PMON启动过程start.S详解

目录 一、龙芯向量表与启动程序的入口&#xff08;复位向量&#xff09; 1.1 复位向量&#xff1a; 1.2 代码执行流程 1.3 计算机的南桥 VS 北桥 二、PMON代码执行流程 三、Start.S详解 3.1 CPU初始化时所需要的宏定义 &#xff08;1&#xff09;与CPU相关的一些宏定义…

node.js快速入门-day03

个人名片&#xff1a; &#x1f60a;作者简介&#xff1a;一名大二在校生 &#x1f921; 个人主页&#xff1a;坠入暮云间x &#x1f43c;座右铭&#xff1a;给自己一个梦想&#xff0c;给世界一个惊喜。 &#x1f385;**学习目标: 坚持每一次的学习打卡 文章目录 web服务器创建…

brpc之ResourcePool

简介 ResourcePool用于管理资源&#xff0c;负责资源的分配以及回收 结构 BlockGroup&#xff1a;资源池中包含多个BlockGroup&#xff0c;最多65536个 Block&#xff1a;一个BlockGroup中包含多个Block&#xff0c;最多(1<<16)个&#xff1b;1个Block中包含BLOCK_NITE…

代码随想录算法训练营第二十二天 | 235. 二叉搜索树的最近公共祖先, 701.二叉搜索树中的插入操作, 450.删除二叉搜索树中的节点

这道题和寻找二叉树的最近祖先可以用同一套解法&#xff0c;也就是说&#xff0c;考虑当你站在一个节点上的时候&#xff0c;应该干啥&#xff1a;看当前节点是不是指定的孩子p&#xff0c;q&#xff0c;然后检查左子树有无指定节点&#xff0c;检查右子树有无指定节点&#xf…

HashMap底层是如何实现的?

1、典型回答 不同的JDK 版本&#xff0c;HashMap 的底层实现是不一样的&#xff0c;总体来说&#xff1a;在JDK 1.8 之前(不包含JDK 1.8)&#xff0c;HashMap 使用的是数组 链表实现的&#xff0c;而JDK 1.8之后(包含JDK 1.8)使用的是数组 链表或红黑树实现的 HashMap 在JD…

鸿蒙Next 支持数据双向绑定的组件:Checkbox--Search--TextInput

Checkbox $$语法&#xff0c;$$绑定的变量发生变化时&#xff0c;会触发UI的刷新 Entry Component struct MvvmCase { State isMarry:boolean falseStatesearchText:string build() {Grid(){GridItem(){Column(){Text("checkbox 的双向绑定")Checkbox().select($$…

python学习笔记------函数进阶

函数多返回值 函数返回多个返回值格式 def 函数名(): return 1,2,......,n x,x1,......xn函数名&#xff08;&#xff09; 按顺序接受 函数的多种传参方式 位置参数&#xff1a;调用函数时根据函数定义的参数位置来传递参数 传递的参数和定义的参数的顺序及个数必须一致…

简介:项目管理九大知识五大过程

前言 项目管理&#xff08;Project Management,PM/Management by Projects,MBP&#xff09; 在有限的资源约束下&#xff0c;运用系统的观点、方法和理论&#xff0c;对项目涉及的全部工作进行有效地管理。即从项目的投资决策开始到项目结束的全过程进行计划、组织、指挥、协调…