forked from 2121578/gai-ca2
Pathfinding improvements
parent
027257fcb6
commit
cfab98a8d0
|
@ -9,7 +9,7 @@
|
||||||
texture = ExtResource("1_ukrsa")
|
texture = ExtResource("1_ukrsa")
|
||||||
0:0/0 = 0
|
0:0/0 = 0
|
||||||
0:0/0/custom_data_0 = true
|
0:0/0/custom_data_0 = true
|
||||||
0:0/0/custom_data_2 = 1
|
0:0/0/custom_data_2 = 4
|
||||||
1:0/0 = 0
|
1:0/0 = 0
|
||||||
1:0/0/custom_data_2 = 1
|
1:0/0/custom_data_2 = 1
|
||||||
2:0/0 = 0
|
2:0/0 = 0
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -4,9 +4,13 @@ extends Node
|
||||||
@onready var world: World = $Tileset
|
@onready var world: World = $Tileset
|
||||||
@onready var player: PlayerManager = $PlayerManager
|
@onready var player: PlayerManager = $PlayerManager
|
||||||
@onready var camera: Camera2D = $Camera2D
|
@onready var camera: Camera2D = $Camera2D
|
||||||
|
@onready var game_ticker: Timer = $GameTick
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
player.game_manager = self
|
player.game_manager = self
|
||||||
|
game_ticker.start()
|
||||||
|
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
if Input.is_action_just_pressed("key_1"):
|
if Input.is_action_just_pressed("key_1"):
|
||||||
|
@ -18,6 +22,14 @@ func _process(delta: float) -> void:
|
||||||
if Input.is_action_just_pressed("force_game_tick"):
|
if Input.is_action_just_pressed("force_game_tick"):
|
||||||
player.game_tick()
|
player.game_tick()
|
||||||
|
|
||||||
|
|
||||||
func player_health_depleted():
|
func player_health_depleted():
|
||||||
# TODO
|
# TODO
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func _on_game_tick_timeout() -> void:
|
||||||
|
var timer_on_game_tick_timeout: PerformanceTimer = PerformanceTimer.new()
|
||||||
|
timer_on_game_tick_timeout.display_name = "_on_game_tick_timeout"
|
||||||
|
player.game_tick()
|
||||||
|
timer_on_game_tick_timeout.stop()
|
||||||
|
|
|
@ -29,11 +29,10 @@ func defer_ready() -> void:
|
||||||
update_board()
|
update_board()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
if Input.is_action_just_pressed("key_3"):
|
if Input.is_action_just_pressed("key_3"):
|
||||||
# game_manager.camera.go_to_zooming(game_manager.world.tilemap_player.cell_to_local(board_position), 2)
|
game_manager.camera.go_to_zooming(game_manager.world.tilemap_player.cell_to_local(board_position), 2)
|
||||||
for pos in game_manager.world.find_path(board_position, Vector2i(13, 15)):
|
|
||||||
game_manager.world.tilemap_temperature.set_cell(pos, tilemap_types.TEMPERATURE_COLD_1)
|
|
||||||
|
|
||||||
|
|
||||||
# SECTION: board access/mangement
|
# SECTION: board access/mangement
|
||||||
|
@ -63,7 +62,9 @@ func pick_up_item(tilemap_pos: Vector2i) -> void:
|
||||||
# SECTION: player movement
|
# SECTION: player movement
|
||||||
|
|
||||||
func walk_towards(position: Vector2i) -> void:
|
func walk_towards(position: Vector2i) -> void:
|
||||||
var path: Array[Vector2i] = game_manager.world.find_path(board_position, position)
|
walk_along(game_manager.world.find_path(board_position, position))
|
||||||
|
|
||||||
|
func walk_along(path: Array[Vector2i]) -> void:
|
||||||
if len(path) > 1:
|
if len(path) > 1:
|
||||||
var next_position: Vector2i = path[1]
|
var next_position: Vector2i = path[1]
|
||||||
var direction: Vector2i = find_direction(board_position, next_position)
|
var direction: Vector2i = find_direction(board_position, next_position)
|
||||||
|
@ -123,3 +124,5 @@ func game_tick() -> void:
|
||||||
|
|
||||||
if health < 0:
|
if health < 0:
|
||||||
game_manager.player_health_depleted()
|
game_manager.player_health_depleted()
|
||||||
|
|
||||||
|
update_board()
|
||||||
|
|
|
@ -3,6 +3,7 @@ extends Node
|
||||||
|
|
||||||
enum {FAILURE = -1, SUCCESS = 1, RUNNING = 0}
|
enum {FAILURE = -1, SUCCESS = 1, RUNNING = 0}
|
||||||
var status: int = FAILURE
|
var status: int = FAILURE
|
||||||
|
var tilemap_types: TileMapTileTypes = TileMapTileTypes.new()
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
class_name WalkToMouse
|
||||||
|
extends Task
|
||||||
|
|
||||||
|
func run(p_blackboard: Dictionary) -> void:
|
||||||
|
var world: World = p_blackboard["world"]
|
||||||
|
var player: PlayerManager = p_blackboard["player"]
|
||||||
|
|
||||||
|
world.tilemap_temperature.clear_cells()
|
||||||
|
var path: Array[Vector2i] = world.find_path(player.board_position, world.tilemap_mouse_position(), 200)
|
||||||
|
if len(path) == 0:
|
||||||
|
status = FAILURE
|
||||||
|
|
||||||
|
for pos in path:
|
||||||
|
world.tilemap_temperature.set_cell(pos, tilemap_types.TEMPERATURE_COLD_1)
|
||||||
|
player.walk_along(path)
|
||||||
|
status = SUCCESS
|
|
@ -11,22 +11,22 @@ var tilemap_types: TileMapTileTypes = TileMapTileTypes.new()
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
tilemap_ground.sid = 0
|
tilemap_ground.sid = 0
|
||||||
tilemap_ground.tilemap = $GroundLayer
|
tilemap_ground.tilemap = $GroundLayer
|
||||||
tilemap_non_interactive.sid = 1
|
tilemap_non_interactive.sid = 1
|
||||||
tilemap_non_interactive.tilemap = $NonInteractiveObjectsLayer
|
tilemap_non_interactive.tilemap = $NonInteractiveObjectsLayer
|
||||||
tilemap_interactive.sid = 1
|
tilemap_interactive.sid = 1
|
||||||
tilemap_interactive.tilemap = $InteractiveObjectsLayer
|
tilemap_interactive.tilemap = $InteractiveObjectsLayer
|
||||||
tilemap_player.sid = 3
|
tilemap_player.sid = 3
|
||||||
tilemap_player.tilemap = $PlayerLayer
|
tilemap_player.tilemap = $PlayerLayer
|
||||||
tilemap_temperature.sid = 2
|
tilemap_temperature.sid = 2
|
||||||
tilemap_temperature.tilemap = $TemperatureLayer
|
tilemap_temperature.tilemap = $TemperatureLayer
|
||||||
|
|
||||||
tilemap_ground.setup()
|
tilemap_ground.setup()
|
||||||
tilemap_non_interactive.setup()
|
tilemap_non_interactive.setup()
|
||||||
tilemap_interactive.setup()
|
tilemap_interactive.setup()
|
||||||
tilemap_player.setup()
|
tilemap_player.setup()
|
||||||
tilemap_temperature.setup()
|
tilemap_temperature.setup()
|
||||||
|
|
||||||
|
|
||||||
# example usage
|
# example usage
|
||||||
|
@ -37,64 +37,87 @@ func _ready() -> void:
|
||||||
# tilemap_ground.set_cell(Vector2i(0, 0), tilemap_types.GROUND_GRASS)
|
# tilemap_ground.set_cell(Vector2i(0, 0), tilemap_types.GROUND_GRASS)
|
||||||
# print(tilemap_ground.local_to_cell(get_local_mouse_position()))
|
# print(tilemap_ground.local_to_cell(get_local_mouse_position()))
|
||||||
|
|
||||||
|
func local_mouse_position() -> Vector2:
|
||||||
|
return get_local_mouse_position()
|
||||||
|
|
||||||
|
|
||||||
|
func tilemap_mouse_position() -> Vector2i:
|
||||||
|
return tilemap_ground.local_to_cell(get_local_mouse_position())
|
||||||
|
|
||||||
|
|
||||||
func is_walkable(position: Vector2i) -> bool:
|
func is_walkable(position: Vector2i) -> bool:
|
||||||
var ground_tile_walkable: bool = tilemap_ground.get_custom_data(position, "walkable", false)
|
var ground_tile_walkable: bool = tilemap_ground.get_custom_data(position, "walkable", false)
|
||||||
if not ground_tile_walkable:
|
var non_interactive_walkable: bool = tilemap_non_interactive.get_custom_data(position, "walkable", true)
|
||||||
return false
|
var interactive_walkable: bool = tilemap_interactive.get_custom_data(position, "walkable", true)
|
||||||
|
|
||||||
var non_interactive_tile: TileData = tilemap_non_interactive.get_cell(position)
|
return ground_tile_walkable and non_interactive_walkable and interactive_walkable
|
||||||
if non_interactive_tile:
|
|
||||||
return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
|
|
||||||
var f_score: Dictionary = {}
|
|
||||||
|
|
||||||
|
|
||||||
func find_path(start_position: Vector2i, end_position: Vector2i) -> Array[Vector2i]:
|
func is_within_radius(position: Vector2i, center: Vector2i, radius: int) -> bool:
|
||||||
var open_list: Array = []
|
return manhattan_distance(position, center) <= radius
|
||||||
var came_from: Dictionary = {}
|
|
||||||
var g_score: Dictionary = {}
|
|
||||||
f_score = {}
|
|
||||||
|
|
||||||
open_list.append(start_position)
|
|
||||||
g_score[start_position] = 0
|
|
||||||
f_score[start_position] = start_position.distance_to(end_position)
|
|
||||||
|
|
||||||
while open_list.size() > 0:
|
|
||||||
open_list.sort_custom(Callable(self, "_compare_f_score"))
|
|
||||||
var current: Vector2i = open_list[0]
|
|
||||||
|
|
||||||
if current == end_position:
|
|
||||||
var path: Array[Vector2i] = []
|
|
||||||
while current in came_from:
|
|
||||||
path.insert(0, current)
|
|
||||||
current = came_from[current]
|
|
||||||
path.insert(0, start_position)
|
|
||||||
return path
|
|
||||||
|
|
||||||
open_list.erase(current)
|
|
||||||
|
|
||||||
for direction in [Vector2i(0, -1), Vector2i(0, 1), Vector2i(-1, 0), Vector2i(1, 0)]:
|
|
||||||
var neighbor: Vector2i = current + direction
|
|
||||||
|
|
||||||
if not is_walkable(neighbor):
|
|
||||||
continue
|
|
||||||
|
|
||||||
var cost: int = tilemap_ground.get_custom_data(neighbor, "cost", 1)
|
|
||||||
var tentative_g_score: int = g_score.get(current, INF) + cost
|
|
||||||
|
|
||||||
if tentative_g_score < g_score.get(neighbor, INF):
|
|
||||||
came_from[neighbor] = current
|
|
||||||
g_score[neighbor] = tentative_g_score
|
|
||||||
f_score[neighbor] = tentative_g_score + neighbor.distance_to(end_position)
|
|
||||||
if not neighbor in open_list:
|
|
||||||
open_list.append(neighbor)
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
func _compare_f_score(a: Vector2i, b: Vector2i) -> int:
|
func manhattan_distance(a: Vector2i, b: Vector2i) -> int:
|
||||||
var score_a: float = f_score.get(a, INF)
|
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||||
var score_b: float = f_score.get(b, INF)
|
|
||||||
return score_a - score_b
|
|
||||||
|
var walking_directions: Array[Vector2i] = [Vector2i(0, -1), Vector2i(0, 1), Vector2i(-1, 0), Vector2i(1, 0)]
|
||||||
|
var f_score: Dictionary = {}
|
||||||
|
|
||||||
|
|
||||||
|
func find_path(start_position: Vector2i, end_position: Vector2i, max_radius: int = -1) -> Array[Vector2i]:
|
||||||
|
if max_radius > -1 and not is_within_radius(end_position, start_position, max_radius):
|
||||||
|
return []
|
||||||
|
if not is_walkable(end_position):
|
||||||
|
return []
|
||||||
|
|
||||||
|
var check_nodes = PriorityQueue.new() # lowest f_score
|
||||||
|
var came_from: Dictionary = {}
|
||||||
|
var g_score: Dictionary = {}
|
||||||
|
var walkable_cache: Dictionary = {}
|
||||||
|
f_score = {}
|
||||||
|
|
||||||
|
var visited_nodes: Dictionary = {}
|
||||||
|
|
||||||
|
check_nodes.insert(start_position, 0)
|
||||||
|
g_score[start_position] = 0
|
||||||
|
f_score[start_position] = manhattan_distance(start_position, end_position) * 1.1 # Heuristic weighting
|
||||||
|
|
||||||
|
while not check_nodes.empty():
|
||||||
|
var current: Vector2i = check_nodes.extract()
|
||||||
|
|
||||||
|
if current == end_position:
|
||||||
|
var path: Array[Vector2i] = []
|
||||||
|
while current in came_from:
|
||||||
|
path.insert(0, current)
|
||||||
|
current = came_from[current]
|
||||||
|
path.insert(0, start_position)
|
||||||
|
return path
|
||||||
|
|
||||||
|
visited_nodes[current] = true
|
||||||
|
|
||||||
|
for direction in walking_directions:
|
||||||
|
var neighbor: Vector2i = current + direction
|
||||||
|
|
||||||
|
# Combine checks for early skipping
|
||||||
|
if neighbor in visited_nodes or (max_radius > -1 and not is_within_radius(neighbor, start_position, max_radius)):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not walkable_cache.has(neighbor):
|
||||||
|
walkable_cache[neighbor] = is_walkable(neighbor)
|
||||||
|
|
||||||
|
if not walkable_cache[neighbor]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
var cost: int = tilemap_ground.get_custom_data(neighbor, "cost", 1)
|
||||||
|
var tentative_g_score: int = g_score.get(current, INF) + cost
|
||||||
|
|
||||||
|
if tentative_g_score < g_score.get(neighbor, INF):
|
||||||
|
came_from[neighbor] = current
|
||||||
|
g_score[neighbor] = tentative_g_score
|
||||||
|
f_score[neighbor] = tentative_g_score + manhattan_distance(neighbor, end_position) * 1.1 # Heuristic weighting
|
||||||
|
if not check_nodes.contains(neighbor):
|
||||||
|
check_nodes.insert(neighbor, f_score[neighbor])
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
class_name PerformanceTimer
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
var start_time: float = 0.0
|
||||||
|
var accumulated_time: float = 0.0
|
||||||
|
var display_name: String = ""
|
||||||
|
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
start_time = Time.get_unix_time_from_system()
|
||||||
|
display_name = ""
|
||||||
|
|
||||||
|
|
||||||
|
func start() -> void:
|
||||||
|
start_time = Time.get_unix_time_from_system()
|
||||||
|
|
||||||
|
|
||||||
|
func stop(printme: bool = true) -> void:
|
||||||
|
var end_time: float = Time.get_unix_time_from_system()
|
||||||
|
var duration_ms: float = (end_time - start_time) * 1000
|
||||||
|
accumulated_time += duration_ms
|
||||||
|
if printme:
|
||||||
|
printme()
|
||||||
|
|
||||||
|
func printme() -> void:
|
||||||
|
print(display_name, " ", accumulated_time, "ms")
|
||||||
|
|
||||||
|
func per_count(n: String, count: int) -> void:
|
||||||
|
print(display_name, " ", accumulated_time / count, "ms per ", n, " (", count, ")")
|
|
@ -0,0 +1,71 @@
|
||||||
|
class_name PriorityQueue
|
||||||
|
|
||||||
|
var _data: Array = []
|
||||||
|
var _set: Dictionary = {}
|
||||||
|
|
||||||
|
func insert(element: Vector2, cost: float) -> void:
|
||||||
|
if element in _set:
|
||||||
|
return
|
||||||
|
# Add the element to the bottom level of the heap at the leftmost open space
|
||||||
|
self._data.push_back(Vector3(element.x, element.y, cost))
|
||||||
|
self._set[element] = true
|
||||||
|
var new_element_index: int = self._data.size() - 1
|
||||||
|
self._up_heap(new_element_index)
|
||||||
|
|
||||||
|
func extract():
|
||||||
|
if self.empty():
|
||||||
|
return null
|
||||||
|
var result: Vector3 = self._data.pop_front()
|
||||||
|
_set.erase(Vector2(result.x, result.y))
|
||||||
|
# If the tree is not empty, replace the root of the heap with the last
|
||||||
|
# element on the last level.
|
||||||
|
if not self.empty():
|
||||||
|
self._data.push_front(self._data.pop_back())
|
||||||
|
self._down_heap(0)
|
||||||
|
return Vector2(result.x, result.y)
|
||||||
|
|
||||||
|
func contains(element: Vector2) -> bool:
|
||||||
|
return element in _set
|
||||||
|
|
||||||
|
func empty() -> bool:
|
||||||
|
return self._data.size() == 0
|
||||||
|
|
||||||
|
func _get_parent(index: int) -> int:
|
||||||
|
# warning-ignore:integer_division
|
||||||
|
return (index - 1) / 2
|
||||||
|
|
||||||
|
func _left_child(index: int) -> int:
|
||||||
|
return (2 * index) + 1
|
||||||
|
|
||||||
|
func _right_child(index: int) -> int:
|
||||||
|
return (2 * index) + 2
|
||||||
|
|
||||||
|
func _swap(a_idx: int, b_idx: int) -> void:
|
||||||
|
var a = self._data[a_idx]
|
||||||
|
var b = self._data[b_idx]
|
||||||
|
self._data[a_idx] = b
|
||||||
|
self._data[b_idx] = a
|
||||||
|
|
||||||
|
func _up_heap(index: int) -> void:
|
||||||
|
# Compare the added element with its parent; if they are in the correct order, stop.
|
||||||
|
var parent_idx = self._get_parent(index)
|
||||||
|
if self._data[index].z >= self._data[parent_idx].z:
|
||||||
|
return
|
||||||
|
self._swap(index, parent_idx)
|
||||||
|
self._up_heap(parent_idx)
|
||||||
|
|
||||||
|
func _down_heap(index: int) -> void:
|
||||||
|
var left_idx: int = self._left_child(index)
|
||||||
|
var right_idx: int = self._right_child(index)
|
||||||
|
var smallest: int = index
|
||||||
|
var size: int = self._data.size()
|
||||||
|
|
||||||
|
if right_idx < size and self._data[right_idx].z < self._data[smallest].z:
|
||||||
|
smallest = right_idx
|
||||||
|
|
||||||
|
if left_idx < size and self._data[left_idx].z < self._data[smallest].z:
|
||||||
|
smallest = left_idx
|
||||||
|
|
||||||
|
if smallest != index:
|
||||||
|
self._swap(index, smallest)
|
||||||
|
self._down_heap(smallest)
|
Loading…
Reference in New Issue