From 51a04ca40337710913fb428280de64127b1c5295 Mon Sep 17 00:00:00 2001 From: Yan Wittmann Date: Thu, 9 Jan 2025 10:55:44 +0100 Subject: [PATCH] Added walk up to tile feature and navigation visualization --- project/assets/tilemap/tilemaps.aseprite | Bin 8972 -> 9030 bytes project/assets/tilemap/tilemaps/sprite.json | 2 +- .../tilemap/tilemaps/tilemap_temperature.png | Bin 98 -> 163 bytes project/assets/tilemap/tileset.tres | 20 ++- project/main-scenes/island.tscn | 18 ++- project/project.godot | 1 + project/scripts/global/GameManager.gd | 8 +- project/scripts/player/PlayerManager.gd | 27 ++-- project/scripts/player/tree/BehaviorTree.gd | 1 + .../player/tree/impl/context/WalkToMouse.gd | 16 --- .../{context => testing}/Task5050Running.gd | 0 .../{context => testing}/Task5050Success.gd | 0 .../player/tree/impl/testing/WalkToMouse.gd | 17 +++ .../player/tree/impl/testing/WalkUpToMouse.gd | 17 +++ project/scripts/tilemap/TileMapLayerAccess.gd | 4 + project/scripts/tilemap/TileMapTileTypes.gd | 74 +++++++---- project/scripts/tilemap/TilemapNavigation.gd | 125 ++++++++++++++++++ project/scripts/tilemap/World.gd | 75 +---------- 18 files changed, 270 insertions(+), 135 deletions(-) delete mode 100644 project/scripts/player/tree/impl/context/WalkToMouse.gd rename project/scripts/player/tree/impl/{context => testing}/Task5050Running.gd (100%) rename project/scripts/player/tree/impl/{context => testing}/Task5050Success.gd (100%) create mode 100644 project/scripts/player/tree/impl/testing/WalkToMouse.gd create mode 100644 project/scripts/player/tree/impl/testing/WalkUpToMouse.gd create mode 100644 project/scripts/tilemap/TilemapNavigation.gd diff --git a/project/assets/tilemap/tilemaps.aseprite b/project/assets/tilemap/tilemaps.aseprite index 654c4bca71dda305bfee72d6cfea7c92d97f34e2..6287ad6b8927025a39e4b9c3a501d53107f84cf3 100644 GIT binary patch delta 170 zcmeBiJLbmdHj%NO_m~m`!>^B?3@Ho@44xZ%w@dLS16j%nj6jkNgeRYtmb5kpvMT1h zJ!~j=KtX`TA!IK@RmhSqwJ4EGbrU&u-kmAS?D6$Y{;G}rvv%g|nihXu-S$q)^b3Eq z%j@Zr7f8Bo-Xk5v%E&aCUr`ZA8Y+4+GHtF_WMO3Vo!p=#%FN8bG)0S12pUa delta 145 zcmX@+*5k&=Gm){Lw?~PA;nznuh7<+{2Dgp9+okvefGlMNMj*)s#LScbNK0y{09h4t zk`p9Y7bl4HgiNscz5lX?+|k7qM;@szt`O;eG_jMB(eQ!ovUUQ&MaD}RVcIcbcU$RBT;9g`2S}AQ%-NPb)GtN^2dGCG;dx%`GcEhfuw_RLmI void: + tilemap_navigation.world = world + tilemap_navigation.player = player player.game_manager = self game_ticker.start() @@ -30,6 +32,8 @@ func player_health_depleted(): 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" + timer_on_game_tick_timeout.display_name = "game tick duration" + tilemap_navigation.game_tick_start() player.game_tick() + tilemap_navigation.game_tick_end() timer_on_game_tick_timeout.stop() diff --git a/project/scripts/player/PlayerManager.gd b/project/scripts/player/PlayerManager.gd index 700d841..c487bc2 100644 --- a/project/scripts/player/PlayerManager.gd +++ b/project/scripts/player/PlayerManager.gd @@ -10,8 +10,13 @@ extends Node var tilemap_types: TileMapTileTypes = TileMapTileTypes.new() # -var game_manager: GameManager = null -var board_position: Vector2i = Vector2i(8, 10) +var game_manager: GameManager = null +var last_board_position: Vector2i = Vector2i(0, 0) +var board_position: Vector2i = Vector2i(8, 10): + set(value): + last_board_position = board_position + board_position = value + update_board() @onready var behavior_tree: BehaviorTree = $BehaviorTree @@ -20,7 +25,10 @@ var food: int = 0 var temperature_timer: int = 0 var health: int = 0 # -var inventory_slot: Vector2i = tilemap_types.EMPTY +var inventory_slot: Vector2i = tilemap_types.EMPTY: + set(value): + inventory_slot = value + update_board() func _ready() -> void: @@ -38,19 +46,19 @@ func _process(delta: float) -> void: if Input.is_action_just_pressed("key_5"): pick_up_item(Vector2i(5, 8)) pick_up_item(Vector2i(9, 9)) - update_board() if Input.is_action_just_pressed("key_4"): var nearest: Vector2i = find_nearest_object([game_manager.world.tilemap_types.OBJECT_I_TREE_1]) # nearest.x = nearest.x - 1 walk_towards(nearest) - update_board() # SECTION: board access/mangement func update_board() -> void: game_manager.world.tilemap_player.clear_cells() - game_manager.world.tilemap_player.set_cell(board_position, tilemap_types.PLAYER) + # decide what direction the player is facing tilemap_types.PLAYER_DOWN, ... + var direction: Vector2i = find_direction(last_board_position, board_position) + game_manager.world.tilemap_player.set_cell(board_position, tilemap_types.player_sprite_from_direction(direction)) if inventory_slot and inventory_slot != tilemap_types.EMPTY: inventory_label.text = str(inventory_slot) else: @@ -101,7 +109,7 @@ 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) + var path: Array[Vector2i] = game_manager.tilemap_navigation.find_path(board_position, position) walk_along(path) @@ -110,6 +118,7 @@ func walk_along(path: Array[Vector2i]) -> void: var next_position: Vector2i = path[1] var direction: Vector2i = find_direction(board_position, next_position) move_player(direction) + game_manager.tilemap_navigation.chosen_path = path else: push_warning("walk_along path is empty") @@ -136,7 +145,7 @@ func find_nearest_object(object_collection: Array[Vector2i]) -> Vector2i: var shortest_distance: float = 99999999 for position in object_positions: - var distance: float = game_manager.world.manhattan_distance(board_position, position) + var distance: float = game_manager.tilemap_navigation.manhattan_distance(board_position, position) if closest_object == tilemap_types.NO_TILE_FOUND or distance < shortest_distance: closest_object = position shortest_distance = distance @@ -178,7 +187,7 @@ func tick_handle_food(): func game_tick() -> void: behavior_tree.game_tick() - var player_positon_array: Array[Vector2i] = game_manager.world.tilemap_player.get_cells_by_type(tilemap_types.PLAYER) + var player_positon_array: Array[Vector2i] = game_manager.world.tilemap_player.get_cells_by_type(tilemap_types.PLAYER_DOWN) if len(player_positon_array) > 0: var player_positon: Vector2i = player_positon_array[0] var cell_temperature: int = game_manager.world.tilemap_temperature.get_custom_data(player_positon, "temperature", 0) as int diff --git a/project/scripts/player/tree/BehaviorTree.gd b/project/scripts/player/tree/BehaviorTree.gd index 5fc4c4d..e1a7f8d 100644 --- a/project/scripts/player/tree/BehaviorTree.gd +++ b/project/scripts/player/tree/BehaviorTree.gd @@ -22,6 +22,7 @@ func populate_blackboard(): blackboard["world"] = game_manager.world blackboard["player"] = game_manager.player blackboard["camera"] = game_manager.camera + blackboard["navigation"] = game_manager.tilemap_navigation func game_tick() -> void: diff --git a/project/scripts/player/tree/impl/context/WalkToMouse.gd b/project/scripts/player/tree/impl/context/WalkToMouse.gd deleted file mode 100644 index 748a686..0000000 --- a/project/scripts/player/tree/impl/context/WalkToMouse.gd +++ /dev/null @@ -1,16 +0,0 @@ -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 diff --git a/project/scripts/player/tree/impl/context/Task5050Running.gd b/project/scripts/player/tree/impl/testing/Task5050Running.gd similarity index 100% rename from project/scripts/player/tree/impl/context/Task5050Running.gd rename to project/scripts/player/tree/impl/testing/Task5050Running.gd diff --git a/project/scripts/player/tree/impl/context/Task5050Success.gd b/project/scripts/player/tree/impl/testing/Task5050Success.gd similarity index 100% rename from project/scripts/player/tree/impl/context/Task5050Success.gd rename to project/scripts/player/tree/impl/testing/Task5050Success.gd diff --git a/project/scripts/player/tree/impl/testing/WalkToMouse.gd b/project/scripts/player/tree/impl/testing/WalkToMouse.gd new file mode 100644 index 0000000..9dd44c8 --- /dev/null +++ b/project/scripts/player/tree/impl/testing/WalkToMouse.gd @@ -0,0 +1,17 @@ +class_name WalkToMouse +extends Task + +func run(p_blackboard: Dictionary) -> void: + var world: World = p_blackboard["world"] + var player: PlayerManager = p_blackboard["player"] + var tilemap_navigation: TilemapNavigation = p_blackboard["navigation"] + + world.tilemap_temperature.clear_cells() + var path: Array[Vector2i] = tilemap_navigation.find_path(player.board_position, world.tilemap_mouse_position(), player.view_distance) + 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 diff --git a/project/scripts/player/tree/impl/testing/WalkUpToMouse.gd b/project/scripts/player/tree/impl/testing/WalkUpToMouse.gd new file mode 100644 index 0000000..35f4c56 --- /dev/null +++ b/project/scripts/player/tree/impl/testing/WalkUpToMouse.gd @@ -0,0 +1,17 @@ +class_name WalkUpToMouse +extends Task + +func run(p_blackboard: Dictionary) -> void: + var world: World = p_blackboard["world"] + var player: PlayerManager = p_blackboard["player"] + var tilemap_navigation: TilemapNavigation = p_blackboard["navigation"] + + world.tilemap_temperature.clear_cells() + var path: Array[Vector2i] = tilemap_navigation.find_path_allow_neighbors(player.board_position, world.tilemap_mouse_position(), player.view_distance) + 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 diff --git a/project/scripts/tilemap/TileMapLayerAccess.gd b/project/scripts/tilemap/TileMapLayerAccess.gd index 94e00eb..e54c5c7 100644 --- a/project/scripts/tilemap/TileMapLayerAccess.gd +++ b/project/scripts/tilemap/TileMapLayerAccess.gd @@ -40,6 +40,10 @@ func get_cell(position: Vector2i) -> TileData: return tilemap.get_cell_tile_data(position) +func get_cell_atlas_coords(position: Vector2i) -> Vector2i: + return tilemap.get_cell_atlas_coords(position) + + func set_cell(position: Vector2i, atlas_coords: Vector2i) -> void: tilemap.set_cell(position, sid, atlas_coords) diff --git a/project/scripts/tilemap/TileMapTileTypes.gd b/project/scripts/tilemap/TileMapTileTypes.gd index 6a19ac8..61b3285 100644 --- a/project/scripts/tilemap/TileMapTileTypes.gd +++ b/project/scripts/tilemap/TileMapTileTypes.gd @@ -13,40 +13,58 @@ const GROUND_DOCK: Vector2i = Vector2i(3, 0) # # objects, sid = 1 # NI = not interactive -const OBJECT_NI_RANDOM_1: Vector2i = Vector2i(0, 0) # testing only, to be removed -const OBJECT_NI_RANDOM_2: Vector2i = Vector2i(1, 0) # testing only, to be removed -const OBJECT_NI_ROCK_1: Vector2i = Vector2i(2, 0) -const OBJECT_NI_FIREPIT_ON: Vector2i = Vector2i(7, 1) -const OBJECT_NI_TREE_CUT: Vector2i = Vector2i(2, 0) -const OBJECT_NI_BOAT_NO_ENIGNE: Vector2i= Vector2i(4, 4) - - +const OBJECT_NI_RANDOM_1: Vector2i = Vector2i(0, 0) # testing only, to be removed +const OBJECT_NI_RANDOM_2: Vector2i = Vector2i(1, 0) # testing only, to be removed +const OBJECT_NI_ROCK_1: Vector2i = Vector2i(2, 0) +const OBJECT_NI_FIREPIT_ON: Vector2i = Vector2i(7, 1) +const OBJECT_NI_TREE_CUT: Vector2i = Vector2i(2, 0) +const OBJECT_NI_BOAT_NO_ENIGNE: Vector2i = Vector2i(4, 4) +# # I = interactive -const OBJECT_I_BOAT_ENGINE: Vector2i = Vector2i(0, 1) -const OBJECT_I_FUEL: Vector2i = Vector2i(1, 1) -const OBJECT_I_ANCHOR: Vector2i = Vector2i(2, 1) -const OBJECT_I_EMPTY_BUSH: Vector2i = Vector2i(3, 0) -const OBJECT_I_FILLED_BUSH: Vector2i = Vector2i(3, 1) -const OBJECT_I_BERRY: Vector2i = Vector2i(0, 5) -const OBJECT_I_STICK: Vector2i = Vector2i(1, 5) -const OBJECT_I_TREE_1: Vector2i = Vector2i(4, 0) -const OBJECT_I_CHEST: Vector2i = Vector2i(0, 2) -const OBJECT_I_GEARS: Vector2i = Vector2i(1, 2) -const OBJECT_I_MEDIKIT: Vector2i = Vector2i(2, 2) -const OBJECT_I_PADDLE: Vector2i = Vector2i(3, 2) -const OBJECT_I_GAS_STOVE: Vector2i = Vector2i(4, 2) -const OBJECT_I_TENT: Vector2i = Vector2i(6, 2) - -const OBJECT_I_BOAT_WITH_ENGINE: Vector2i = Vector2i(6,4) -const OBJECT_I_FIREPIT_OFF: Vector2i = Vector2i(6, 1) - +const OBJECT_I_BOAT_ENGINE: Vector2i = Vector2i(0, 1) +const OBJECT_I_FUEL: Vector2i = Vector2i(1, 1) +const OBJECT_I_ANCHOR: Vector2i = Vector2i(2, 1) +const OBJECT_I_EMPTY_BUSH: Vector2i = Vector2i(3, 0) +const OBJECT_I_FILLED_BUSH: Vector2i = Vector2i(3, 1) +const OBJECT_I_BERRY: Vector2i = Vector2i(0, 5) +const OBJECT_I_STICK: Vector2i = Vector2i(1, 5) +const OBJECT_I_TREE_1: Vector2i = Vector2i(4, 0) +const OBJECT_I_CHEST: Vector2i = Vector2i(0, 2) +const OBJECT_I_GEARS: Vector2i = Vector2i(1, 2) +const OBJECT_I_MEDIKIT: Vector2i = Vector2i(2, 2) +const OBJECT_I_PADDLE: Vector2i = Vector2i(3, 2) +const OBJECT_I_GAS_STOVE: Vector2i = Vector2i(4, 2) +const OBJECT_I_TENT: Vector2i = Vector2i(6, 2) +const OBJECT_I_BOAT_WITH_ENGINE: Vector2i = Vector2i(6, 4) +const OBJECT_I_FIREPIT_OFF: Vector2i = Vector2i(6, 1) # collections const OBJECT_COLLECTION_BERRY_SOURCE: Array[Vector2i] = [OBJECT_I_FILLED_BUSH, OBJECT_I_BERRY] # # temperature, sid = 2 -const TEMPERATURE_NORMAL: Vector2i = Vector2i(-1, -1) +const TEMPERATURE_NORMAL: Vector2i = EMPTY const TEMPERATURE_COLD_1: Vector2i = Vector2i(0, 0) const TEMPERATURE_COLD_2: Vector2i = Vector2i(1, 0) # +const NAVIGATION_CHECKED: Vector2i = Vector2i(0, 1) +const NAVIGATION_CHOSEN: Vector2i = Vector2i(1, 1) +const NAVIGATION_FAILED: Vector2i = Vector2i(2, 1) +# # player, sid = 3 -const PLAYER: Vector2i = Vector2i(0, 0) +const PLAYER_DOWN: Vector2i = Vector2i(0, 0) +const PLAYER_UP: Vector2i = Vector2i(1, 0) +const PLAYER_LEFT: Vector2i = Vector2i(2, 0) +const PLAYER_RIGHT: Vector2i = Vector2i(3, 0) +# +const PLAYER_COLLECTION: Array[Vector2i] = [PLAYER_DOWN, PLAYER_UP, PLAYER_LEFT, PLAYER_RIGHT] + + +func player_sprite_from_direction(direction: Vector2i) -> Vector2i: + if direction == Vector2i(0, 1): + return PLAYER_DOWN + if direction == Vector2i(0, -1): + return PLAYER_UP + if direction == Vector2i(-1, 0): + return PLAYER_LEFT + if direction == Vector2i(1, 0): + return PLAYER_RIGHT + return PLAYER_DOWN diff --git a/project/scripts/tilemap/TilemapNavigation.gd b/project/scripts/tilemap/TilemapNavigation.gd new file mode 100644 index 0000000..9e50c2b --- /dev/null +++ b/project/scripts/tilemap/TilemapNavigation.gd @@ -0,0 +1,125 @@ +class_name TilemapNavigation +extends Node + +var tilemap_types: TileMapTileTypes = TileMapTileTypes.new() +# +var world: World = null +var player: PlayerManager = null +# +# Dictionary[Vector2i, Array[Vector2i]] (target, path) +var found_paths: Dictionary = {} +var failed_positions: Array[Vector2i] = [] +var chosen_path: Array[Vector2i] = [] + + +func game_tick_start() -> void: + found_paths = {} + failed_positions = [] + chosen_path = [] + + +func game_tick_end() -> void: + world.tilemap_nav_vis.clear_cells() + # use tilemap_types.NAVIGATION_CHECKED, tilemap_types.NAVIGATION_FAILED and tilemap_types.NAVIGATION_CHOSEN + for path in found_paths.values(): + for pos in path: + world.tilemap_nav_vis.set_cell(pos, tilemap_types.NAVIGATION_CHECKED) + for pos in failed_positions: + world.tilemap_nav_vis.set_cell(pos, tilemap_types.NAVIGATION_FAILED) + for pos in chosen_path: + world.tilemap_nav_vis.set_cell(pos, tilemap_types.NAVIGATION_CHOSEN) + + +func is_within_radius(position: Vector2i, center: Vector2i, radius: int) -> bool: + return manhattan_distance(position, center) <= radius + + +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_allow_neighbors(start_position: Vector2i, end_position: Vector2i, max_radius: int = -1) -> Array[Vector2i]: + if world.is_walkable(end_position): + # check the tile itself first, then check the four surrounding tiles + var path: Array[Vector2i] = find_path(start_position, end_position, max_radius) + if path.size() != 0: + return path + + else: + for direction in walking_directions: + var neighbor: Vector2i = end_position + direction + var path: Array[Vector2i] = find_path(start_position, neighbor, max_radius) + if path.size() != 0: + return path + + return [] + + +func find_path(start_position: Vector2i, end_position: Vector2i, max_radius: int = -1) -> Array[Vector2i]: + var path: Array[Vector2i] = _find_path_internal(start_position, end_position, max_radius) + if path.size() > 0: + found_paths[end_position] = path + else: + failed_positions.append(end_position) + return path + + +func _find_path_internal(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 world.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] = world.is_walkable(neighbor) + + if not walkable_cache[neighbor]: + continue + + var cost: int = world.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 [] diff --git a/project/scripts/tilemap/World.gd b/project/scripts/tilemap/World.gd index 51599b9..2fd1fe2 100644 --- a/project/scripts/tilemap/World.gd +++ b/project/scripts/tilemap/World.gd @@ -6,6 +6,7 @@ var tilemap_non_interactive: TileMapLayerAccess = TileMapLayerAccess.new() var tilemap_interactive: TileMapLayerAccess = TileMapLayerAccess.new() var tilemap_player: TileMapLayerAccess = TileMapLayerAccess.new() var tilemap_temperature: TileMapLayerAccess = TileMapLayerAccess.new() +var tilemap_nav_vis: TileMapLayerAccess = TileMapLayerAccess.new() # var tilemap_types: TileMapTileTypes = TileMapTileTypes.new() @@ -21,6 +22,8 @@ func _ready() -> void: tilemap_player.tilemap = $PlayerLayer tilemap_temperature.sid = 2 tilemap_temperature.tilemap = $TemperatureLayer + tilemap_nav_vis.sid = 2 + tilemap_nav_vis.tilemap = $NavigationVisualization tilemap_ground.setup() tilemap_non_interactive.setup() @@ -28,6 +31,8 @@ func _ready() -> void: tilemap_player.setup() tilemap_temperature.setup() + print(tilemap_interactive.get_cell_atlas_coords(Vector2i(17, 6))) + # example usage # tilemap_temperature.fill_area(Vector2i(0, 0), Vector2i(10, 10), tilemap_types.TEMPERATURE_COLD_1) @@ -56,73 +61,3 @@ func is_walkable(position: Vector2i) -> bool: var interactive_walkable: bool = tilemap_interactive.get_custom_data(position, "walkable", true) return ground_tile_walkable and non_interactive_walkable and interactive_walkable - - -func is_within_radius(position: Vector2i, center: Vector2i, radius: int) -> bool: - return manhattan_distance(position, center) <= radius - - -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 [] -