1
0
Fork 0

Added walk up to tile feature and navigation visualization

Yan Wittmann 2025-01-09 10:55:44 +01:00
parent 22fa9eb33e
commit 51a04ca403
18 changed files with 270 additions and 135 deletions

View File

@ -1 +1 @@
{"height":320,"filename":"tilemaps.aseprite","frames":[{"duration":0.1}],"width":320,"layers":[{"name":"ground","cels":[{"image":"tilemaps\\tilemap_ground.png","frame":0}]},{"name":"objects","cels":[{"image":"tilemaps\\tilemap_objects.png","frame":0}]},{"name":"temperature","cels":[{"image":"tilemaps\\tilemap_temperature.png","frame":0}]},{"name":"player","cels":[{"image":"tilemaps\\tilemap_player.png","frame":0}]}]}
{"width":320,"height":320,"layers":[{"name":"ground","cels":[{"image":"tilemaps\\tilemap_ground.png","frame":0}]},{"name":"objects","cels":[{"image":"tilemaps\\tilemap_objects.png","frame":0}]},{"name":"temperature","cels":[{"image":"tilemaps\\tilemap_temperature.png","frame":0}]},{"name":"player","cels":[{"image":"tilemaps\\tilemap_player.png","frame":0}]}],"frames":[{"duration":0.1}],"filename":"tilemaps.aseprite"}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 B

After

Width:  |  Height:  |  Size: 163 B

View File

@ -30,11 +30,9 @@ texture = ExtResource("2_15xge")
2:0/0 = 0
0:1/0 = 0
0:2/0 = 0
0:3/0 = 0
1:3/0 = 0
0:4/size_in_atlas = Vector2i(2, 1)
0:4/0 = 0
0:5/0 = 0
0:6/0 = 0
0:7/0 = 0
3:0/0 = 0
1:1/0 = 0
@ -44,6 +42,19 @@ texture = ExtResource("2_15xge")
2:2/0 = 0
3:2/0 = 0
4:2/0 = 0
2:4/size_in_atlas = Vector2i(2, 1)
2:4/0 = 0
4:4/size_in_atlas = Vector2i(2, 1)
4:4/0 = 0
6:4/size_in_atlas = Vector2i(2, 1)
6:4/0 = 0
6:2/0 = 0
1:5/0 = 0
0:6/size_in_atlas = Vector2i(2, 1)
0:6/0 = 0
5:1/0 = 0
6:1/0 = 0
7:1/0 = 0
4:0/size_in_atlas = Vector2i(1, 2)
4:0/0 = 0
@ -53,6 +64,9 @@ texture = ExtResource("3_xap0v")
0:0/0/custom_data_1 = 10
1:0/0 = 0
1:0/0/custom_data_1 = 20
2:1/0 = 0
1:1/0 = 0
0:1/0 = 0
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_i41cv"]
texture = ExtResource("4_f38wc")

File diff suppressed because one or more lines are too long

View File

@ -104,5 +104,6 @@ key_6={
[rendering]
textures/canvas_textures/default_texture_filter=0
renderer/rendering_method="gl_compatibility"
renderer/rendering_method.mobile="gl_compatibility"

View File

@ -5,9 +5,11 @@ extends Node
@onready var player: PlayerManager = $PlayerManager
@onready var camera: Camera2D = $Camera2D
@onready var game_ticker: Timer = $GameTick
var tilemap_navigation: TilemapNavigation = TilemapNavigation.new()
func _ready() -> 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()

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 []

View File

@ -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 []