Godot使用AStar导航六边形瓦片
简述
在godot中,内置的导航有以下特点:
- 要么导航路线经过某个边缘的点或边的中心点,要么所有区域形状连在一起。
- 无论如何都无法经过某个区域的中心点。
在使用六边形的瓦片地图中,往往希望棋子在移动时通过格子的中心点。
所以此处使用AStar2D实现。
相关代码
代码
gdscript
extends Node2D
@onready var tile_map_layer: TileMapLayer = $TileMapLayer
@onready var player: Player = $Player
@onready var astar = AStar2D.new()
var id_map: Dictionary = {}
#var points = []
#var links = []
var navigation_path_point = []
var current_target = Vector2.ZERO
var navigating: bool = false
func _ready() -> void:
var start_time = Time.get_unix_time_from_system()
# 初始化玩家位置
player.global_position = tile_map_layer.map_to_local(Vector2i.ZERO)
var cells = tile_map_layer.get_used_cells()
#points = cells
# 在astar中添加点
for index in cells.size():
var cell = cells[index]
# 需要调整权重就在这里调
astar.add_point(index, cell, 1.0)
# 存储id,方便后续使用
id_map[cell] = index
# 在astar中添加连线
for cell in cells:
# 每个图块只需要处理右、右下、左下三个方向,减少重复链接
var right_cell = tile_map_layer.get_neighbor_cell(cell, TileSet.CellNeighbor.CELL_NEIGHBOR_RIGHT_SIDE) # 右侧
var left_down_cell = tile_map_layer.get_neighbor_cell(cell, TileSet.CellNeighbor.CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) # 左下
var right_down_cell = tile_map_layer.get_neighbor_cell(cell, TileSet.CellNeighbor.CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) # 右下
if tile_map_layer.get_cell_tile_data(right_cell):
astar.connect_points(id_map[cell], id_map[right_cell])
#links.append([cell, right_cell])
if tile_map_layer.get_cell_tile_data(left_down_cell):
astar.connect_points(id_map[cell], id_map[left_down_cell])
#links.append([cell, left_down_cell])
if tile_map_layer.get_cell_tile_data(right_down_cell):
astar.connect_points(id_map[cell], id_map[right_down_cell])
#links.append([cell, right_down_cell])
print("消耗:", Time.get_unix_time_from_system() - start_time, "s")
#print("共添加", points.size(), "个点,", links.size(), "个边")
func _input(event: InputEvent) -> void:
# 点击鼠标事件后开始导航
if event.is_action_pressed("click"):
# 获得鼠标点击位置判断是否可以导航
var mouse_pos := get_global_mouse_position()
var map_pos := tile_map_layer.local_to_map(mouse_pos)
if id_map.has(map_pos):
# 获取当前位置和目标位置的id
var from_pos = tile_map_layer.local_to_map(player.global_position)
var from_id = id_map[from_pos]
var to_id = id_map[map_pos]
var path_points = astar.get_id_path(from_id, to_id)
print("导航点:", path_points)
# 将导航点转换为普通array,方便后续增删操作
navigation_path_point = Array(path_points)
# 重置current_target为当前位置
current_target = player.global_position
# 设置navigating使可以导航
navigating = true
func _process(delta: float) -> void:
if navigating:
# 判断玩家当前位置和current_target是否一致
# 一致则重新生成current_target
if (player.global_position - current_target).length() < 0.5:
# 如果有需要导航的路径
if navigation_path_point.size() != 0:
var new_target_point_id = navigation_path_point.pop_front()
var new_target_point_pos = astar.get_point_position(new_target_point_id)
current_target = tile_map_layer.map_to_local(new_target_point_pos)
else:
navigating = false
# 不一致则向current_target移动
else:
var direction = ((current_target - player.global_position) as Vector2).normalized()
player.global_position += direction * delta * 50
# 调试展示网格之间的点和线
#func _draw() -> void:
#for point in points:
#draw_circle(tile_map_layer.map_to_local(point), 2, Color.RED)
#for link in links:
#draw_line(tile_map_layer.map_to_local(link[0]), tile_map_layer.map_to_local(link[1]), Color(1,1,1,0.5), 1)
重点说明
- 通过
tile_map_layer.get_used_cells()
获得地图中所有的瓦片,可以少一些性能消耗 - 只遍历了某个瓦片A的右侧、右下、左下三个方向的瓦片,是因为如果另三个方向如果有瓦片B,则这个瓦片B就可以创建到瓦片A的链接,在设置双向链接的情况下,这种设置可以减少创建的链接次数,减少性能消耗。
- 存储点的id到Dictionary中是为了方便的根据位置获得添加的id。不需要多余的遍历。