forked from 2121578/gai-ca2
Pathfinding improvements
parent
027257fcb6
commit
cfab98a8d0
|
@ -9,7 +9,7 @@
|
|||
texture = ExtResource("1_ukrsa")
|
||||
0:0/0 = 0
|
||||
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/custom_data_2 = 1
|
||||
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 player: PlayerManager = $PlayerManager
|
||||
@onready var camera: Camera2D = $Camera2D
|
||||
@onready var game_ticker: Timer = $GameTick
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
player.game_manager = self
|
||||
game_ticker.start()
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
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"):
|
||||
player.game_tick()
|
||||
|
||||
|
||||
func player_health_depleted():
|
||||
# TODO
|
||||
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()
|
||||
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
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)
|
||||
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)
|
||||
game_manager.camera.go_to_zooming(game_manager.world.tilemap_player.cell_to_local(board_position), 2)
|
||||
|
||||
|
||||
# SECTION: board access/mangement
|
||||
|
@ -63,7 +62,9 @@ func pick_up_item(tilemap_pos: Vector2i) -> void:
|
|||
# SECTION: player movement
|
||||
|
||||
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:
|
||||
var next_position: Vector2i = path[1]
|
||||
var direction: Vector2i = find_direction(board_position, next_position)
|
||||
|
@ -123,3 +124,5 @@ func game_tick() -> void:
|
|||
|
||||
if health < 0:
|
||||
game_manager.player_health_depleted()
|
||||
|
||||
update_board()
|
||||
|
|
|
@ -3,6 +3,7 @@ extends Node
|
|||
|
||||
enum {FAILURE = -1, SUCCESS = 1, RUNNING = 0}
|
||||
var status: int = FAILURE
|
||||
var tilemap_types: TileMapTileTypes = TileMapTileTypes.new()
|
||||
|
||||
|
||||
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:
|
||||
tilemap_ground.sid = 0
|
||||
tilemap_ground.tilemap = $GroundLayer
|
||||
tilemap_non_interactive.sid = 1
|
||||
tilemap_non_interactive.tilemap = $NonInteractiveObjectsLayer
|
||||
tilemap_interactive.sid = 1
|
||||
tilemap_interactive.tilemap = $InteractiveObjectsLayer
|
||||
tilemap_player.sid = 3
|
||||
tilemap_player.tilemap = $PlayerLayer
|
||||
tilemap_temperature.sid = 2
|
||||
tilemap_temperature.tilemap = $TemperatureLayer
|
||||
tilemap_ground.sid = 0
|
||||
tilemap_ground.tilemap = $GroundLayer
|
||||
tilemap_non_interactive.sid = 1
|
||||
tilemap_non_interactive.tilemap = $NonInteractiveObjectsLayer
|
||||
tilemap_interactive.sid = 1
|
||||
tilemap_interactive.tilemap = $InteractiveObjectsLayer
|
||||
tilemap_player.sid = 3
|
||||
tilemap_player.tilemap = $PlayerLayer
|
||||
tilemap_temperature.sid = 2
|
||||
tilemap_temperature.tilemap = $TemperatureLayer
|
||||
|
||||
tilemap_ground.setup()
|
||||
tilemap_non_interactive.setup()
|
||||
tilemap_interactive.setup()
|
||||
tilemap_player.setup()
|
||||
tilemap_temperature.setup()
|
||||
tilemap_ground.setup()
|
||||
tilemap_non_interactive.setup()
|
||||
tilemap_interactive.setup()
|
||||
tilemap_player.setup()
|
||||
tilemap_temperature.setup()
|
||||
|
||||
|
||||
# example usage
|
||||
|
@ -37,64 +37,87 @@ func _ready() -> void:
|
|||
# tilemap_ground.set_cell(Vector2i(0, 0), tilemap_types.GROUND_GRASS)
|
||||
# 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:
|
||||
var ground_tile_walkable: bool = tilemap_ground.get_custom_data(position, "walkable", false)
|
||||
if not ground_tile_walkable:
|
||||
return false
|
||||
var ground_tile_walkable: bool = tilemap_ground.get_custom_data(position, "walkable", false)
|
||||
var non_interactive_walkable: bool = tilemap_non_interactive.get_custom_data(position, "walkable", true)
|
||||
var interactive_walkable: bool = tilemap_interactive.get_custom_data(position, "walkable", true)
|
||||
|
||||
var non_interactive_tile: TileData = tilemap_non_interactive.get_cell(position)
|
||||
if non_interactive_tile:
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
var f_score: Dictionary = {}
|
||||
return ground_tile_walkable and non_interactive_walkable and interactive_walkable
|
||||
|
||||
|
||||
func find_path(start_position: Vector2i, end_position: Vector2i) -> Array[Vector2i]:
|
||||
var open_list: Array = []
|
||||
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 is_within_radius(position: Vector2i, center: Vector2i, radius: int) -> bool:
|
||||
return manhattan_distance(position, center) <= radius
|
||||
|
||||
|
||||
func _compare_f_score(a: Vector2i, b: Vector2i) -> int:
|
||||
var score_a: float = f_score.get(a, INF)
|
||||
var score_b: float = f_score.get(b, INF)
|
||||
return score_a - score_b
|
||||
func manhattan_distance(a: Vector2i, b: Vector2i) -> int:
|
||||
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||
|
||||
|
||||
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