From 567b2cb6897cbea7a7c0b0e2265dbeea86254ef4 Mon Sep 17 00:00:00 2001 From: Yan Wittmann Date: Mon, 6 Jan 2025 12:28:54 +0100 Subject: [PATCH] Player logic and pathfinding --- doc/planning.md | 33 +++--- project/assets/tilemap/tilemaps.aseprite | Bin 1421 -> 1505 bytes project/assets/tilemap/tilemaps/sprite.json | 49 +------- .../tilemap/tilemaps/tilemap_objects.png | Bin 313 -> 433 bytes project/assets/tilemap/tileset.tres | 11 +- project/main-scenes/island.tscn | 2 + project/scripts/global/GameManager.gd | 4 + project/scripts/player/PlayerManager.gd | 98 +++++++++++++++- project/scripts/tilemap/TileMapLayerAccess.gd | 4 +- project/scripts/tilemap/TileMapTileTypes.gd | 10 +- project/scripts/tilemap/World.gd | 107 ++++++++++++++---- 11 files changed, 227 insertions(+), 91 deletions(-) diff --git a/doc/planning.md b/doc/planning.md index 5c80dde..a20bda6 100644 --- a/doc/planning.md +++ b/doc/planning.md @@ -1,17 +1,11 @@ -- Sprites (dome) -- Initialize Tilemap (Yan) - - Script --> World (manages access to tilemap) - - Player is on tilemap - - Multiple layers - - Ground (Water, Ground) --> custom data (Walkable, Weight = 1) - - Obstacles (Stone, ...) - - Pickup (Berries, Trees, Boat parts) --> custom data (Type of collectable "tree", "berry", "boat") - - Temperature layer (normal and different levels of coldness, transparent solid color) +## Todo + +- Sprites (Dome) - Design a tilemap for the game - Player (Dome) + - Stats (see document) - As a layer in the tilemap - Implemented in the PlayerManager - - Stats (see document) - Inventory - with one slot - can pick up items from tilemap (Pickup) @@ -20,10 +14,6 @@ - Use tilemap layers to compute route - Support obstacles - Result must be an array of tile coordinates, length (array length) and the total cost (sum of weights) -- Decision Tree (Classes, etc.) (Yan) - - Reference Food Gatherer for implementation - - Child of player, serves as controller - - Script needs access to the scene, player and other objects/data - Implement Behaviours - Implement all kinds of Behaviours, see document - Visualization, make the simulation understandable @@ -31,3 +21,18 @@ - Distances - Current navigation path - ... + +## Done + +- Initialize Tilemap (Yan) + - Script --> World (manages access to tilemap) + - Player is on tilemap + - Multiple layers + - Ground (Water, Ground) --> custom data (Walkable, Weight = 1) + - Obstacles (Stone, ...) + - Pickup (Berries, Trees, Boat parts) --> custom data (Type of collectable "tree", "berry", "boat") + - Temperature layer (normal and different levels of coldness, transparent solid color) +- Decision Tree (Classes, etc.) (Yan) + - Reference Food Gatherer for implementation + - Child of player, serves as controller + - Script needs access to the scene, player and other objects/data diff --git a/project/assets/tilemap/tilemaps.aseprite b/project/assets/tilemap/tilemaps.aseprite index 154f1f24a9f055019231ed9632d77133298712bc..14f2f740c1fe2ff5e1d9a209ef7d333b168a74c5 100644 GIT binary patch delta 550 zcmV+>0@?kI3*id^;gJD<4PgZU0Q&JA0Av6F03DHyh7ZyK001K(000001^@s62$TH+ z8Goh$004NL?N>`m13?hYkwUKHB7)$;h5Kwhf%_nM0JC=I1tg0s#g*VGL}=+iOQooP z_w)?O6cnL*>b-iex@RDZ#T;|YajEfi*d&;DZ_8uieeD@lN7dqI7Cx;%~3y zXtdTwJlD>PJ1@)|<*q$Li&tu2>#f~mYJVoJ^hW06eRuKq;AOQs*5zN%cO=(C3|?FC zmOU%p+dq4bVsU@Yi!@hhc5uyU05pwq^zzm&d>N z>@Dw&hNbSZ75fL}A6pZX&;lNl&;lN_&;lX>lN1CHlL7=10Rxj21g;lH00005AOQdX o000300{{R30000000Wbe1R@S%U|?VaVs;<~003|R5R(T5hIo1Z>Hq)$ delta 455 zcmV;&0XY8Q3yli_jgbL=4GjeV0Q&J90Av6F034Bxh7XGY001K(000001^@s62a`?% z8Gl~^004NL?N>_*!Y~l^NTt_tp&+<$;ohw$a4&)f@C@F-g-e%$r*IQ#p%90e%qMA5 z+73J<>CBsXFKNNWVvafHm}*>}2j^pVuUf!;?irSc<-%tYKCXwAL!*o9MLpNdD9yEK zj`fQ=FUV`R9gINf67Oa+c6+)@Ay> zW*aMpd>?Y_*67cz+kH>j@w#q{^er)0!`Y# x4Ffg-lVt=DlT!o|0Rod|1g-}}00005AOVwN1u_d_U|?VaVgLXz011;@1%_oa(uM#4 diff --git a/project/assets/tilemap/tilemaps/sprite.json b/project/assets/tilemap/tilemaps/sprite.json index c06eee1..79d3f1f 100644 --- a/project/assets/tilemap/tilemaps/sprite.json +++ b/project/assets/tilemap/tilemaps/sprite.json @@ -1,48 +1 @@ -{ - "filename": "tilemaps.aseprite", - "height": 320, - "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 - } - ] - } - ], - "frames": [ - { - "duration": 0.1 - } - ] -} \ No newline at end of file +{"width":320,"layers":[{"cels":[{"image":"tilemaps\\tilemap_ground.png","frame":0}],"name":"ground"},{"cels":[{"image":"tilemaps\\tilemap_objects.png","frame":0}],"name":"objects"},{"cels":[{"image":"tilemaps\\tilemap_temperature.png","frame":0}],"name":"temperature"},{"cels":[{"image":"tilemaps\\tilemap_player.png","frame":0}],"name":"player"}],"frames":[{"duration":0.1}],"height":320,"filename":"tilemaps.aseprite"} \ No newline at end of file diff --git a/project/assets/tilemap/tilemaps/tilemap_objects.png b/project/assets/tilemap/tilemaps/tilemap_objects.png index 505cda28ac89333510a08e0e05bbc8ea4d05d077..65cd72d95f246c7444bb8f8f90caec6d4af613c8 100644 GIT binary patch literal 433 zcmV;i0Z#sjP)b-ZkJ_j!M$0Y6DW^M8a3%9nNh$xUodoCb(2~EhC03@r;S!lL@ z;m70k=;R#RIb80KsadXqJ-(jN9u%>#4&w-vV!R$55=9}!XT6>(d7?N%LBMc&hspm; z9~5DULGKe0HOo~PxR-?l0o~y{7+N~*hh{2?43fmiXd;*-dtTb9XcVOof1kV2;Tj^B zHrhISQzM^)DnM2uOed^TDUyhqOI<>qC~=8<@0}dDR&sbvWkTdAQK6hi3_r3RyD^Ly^ z(c09hL9&&wz!A|z<=y}d__~&+&u+7JW;16I=kF4+HDxo^#A)E3C_0!)a!ZYvqdU!s bkqzAk-Tp4phUgTh00000NkvXXu0mjfDpb8K literal 313 zcmV-90mlA`P)7CyHQXBwv*Re*g8sB8Skzj(pYt-(GeC?@3NGfhZPbYtd=vyK#;f zh1^~efvXQ|bKxrQx%Mm?D4Ec@;*N>L0JkIGm>=1uIb7vkE6T*cuMc+c-r`i`4$zPj zDow(Ja73|o@Nh(OrnDJQG+<0Q&$2dZ%+~x15`smxWlyGKt#Awe?DETHjFp7x00000 LNkvXXu0mjfkHv+X diff --git a/project/assets/tilemap/tileset.tres b/project/assets/tilemap/tileset.tres index d7eaa84..f22dda9 100644 --- a/project/assets/tilemap/tileset.tres +++ b/project/assets/tilemap/tileset.tres @@ -9,22 +9,27 @@ texture = ExtResource("1_ukrsa") 0:0/0 = 0 0:0/0/custom_data_0 = true +0:0/0/custom_data_2 = 1 1:0/0 = 0 +1:0/0/custom_data_2 = 1 2:0/0 = 0 +2:0/0/custom_data_2 = 1 3:0/0 = 0 3:0/0/custom_data_0 = true +3:0/0/custom_data_2 = 1 [sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_7jeam"] texture = ExtResource("2_o4fdg") 0:0/0 = 0 1:0/0 = 0 +2:0/0 = 0 [sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_1og8x"] texture = ExtResource("3_xap0v") 0:0/0 = 0 -0:0/0/custom_data_1 = 100 +0:0/0/custom_data_1 = 10 1:0/0 = 0 -1:0/0/custom_data_1 = -200 +1:0/0/custom_data_1 = 20 [sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_i41cv"] texture = ExtResource("4_f38wc") @@ -35,6 +40,8 @@ custom_data_layer_0/name = "walkable" custom_data_layer_0/type = 1 custom_data_layer_1/name = "temperature" custom_data_layer_1/type = 2 +custom_data_layer_2/name = "cost" +custom_data_layer_2/type = 2 sources/0 = SubResource("TileSetAtlasSource_114re") sources/1 = SubResource("TileSetAtlasSource_7jeam") sources/2 = SubResource("TileSetAtlasSource_1og8x") diff --git a/project/main-scenes/island.tscn b/project/main-scenes/island.tscn index e248ef9..37dc032 100644 --- a/project/main-scenes/island.tscn +++ b/project/main-scenes/island.tscn @@ -24,6 +24,7 @@ tile_map_data = PackedByteArray("AAAAAAAAAAACAAAAAAABAAAAAAACAAAAAAACAAEAAAABAAA tile_set = ExtResource("1_vlccq") [node name="NonInteractiveObjectsLayer" type="TileMapLayer" parent="Tileset"] +tile_map_data = PackedByteArray("AAAOAAkAAQACAAAAAAANAAkAAQACAAAAAAANAAoAAQACAAAAAAAMAAoAAQACAAAAAAAMAAsAAQACAAAAAAAMAAwAAQACAAAAAAAMAA0AAQACAAAAAAALAA0AAQACAAAAAAALAA4AAQACAAAAAAAKAA4AAQACAAAAAAAKAA8AAQACAAAAAAAKABAAAQACAAAAAAAJABAAAQACAAAAAAAJABEAAQACAAAAAAAJABIAAQACAAAAAAAJABMAAQACAAAAAAAJABQAAQACAAAAAAAIABQAAQACAAAAAAAIABMAAQACAAAAAAAIABIAAQACAAAAAAAIABEAAQACAAAAAAAIABAAAQACAAAAAAAIAA8AAQACAAAAAAAJAA8AAQACAAAAAAAJAA4AAQACAAAAAAAKAA0AAQACAAAAAAAKAAwAAQACAAAAAAAKAAsAAQACAAAAAAALAAsAAQACAAAAAAALAAwAAQACAAAAAAALAAoAAQACAAAAAAAMAAkAAQACAAAAAAANAAgAAQACAAAAAAAOAAgAAQACAAAAAAA=") tile_set = ExtResource("1_vlccq") [node name="InteractiveObjectsLayer" type="TileMapLayer" parent="Tileset"] @@ -33,6 +34,7 @@ tile_set = ExtResource("1_vlccq") tile_set = ExtResource("1_vlccq") [node name="TemperatureLayer" type="TileMapLayer" parent="Tileset"] +tile_map_data = PackedByteArray("AAAAAAAAAgABAAAAAAA=") tile_set = ExtResource("1_vlccq") [node name="PlayerManager" type="Node" parent="."] diff --git a/project/scripts/global/GameManager.gd b/project/scripts/global/GameManager.gd index cf54b03..61fbd06 100644 --- a/project/scripts/global/GameManager.gd +++ b/project/scripts/global/GameManager.gd @@ -17,3 +17,7 @@ func _process(delta: float) -> void: camera.print_config() if Input.is_action_just_pressed("force_game_tick"): player.game_tick() + +func player_health_depleted(): + # TODO + pass diff --git a/project/scripts/player/PlayerManager.gd b/project/scripts/player/PlayerManager.gd index 4f1e95d..6d04613 100644 --- a/project/scripts/player/PlayerManager.gd +++ b/project/scripts/player/PlayerManager.gd @@ -1,13 +1,24 @@ class_name PlayerManager extends Node +@export var food_damage: int = 1 +@export var temperature_damage: int = 1 +@export var temperature_endure: int = 50 + var tilemap_types: TileMapTileTypes = TileMapTileTypes.new() # var game_manager: GameManager = null -var board_position: Vector2 = Vector2(0, 0) +var board_position: Vector2i = Vector2i(8, 10) @onready var behavior_tree: BehaviorTree = $BehaviorTree +var food: int = 0 +# var water: int = 0 +var temperature_timer: int = 0 +var health: int = 0 +# +var inventory_slot: Vector2i = tilemap_types.EMPTY + func _ready() -> void: call_deferred("defer_ready") @@ -20,13 +31,96 @@ func defer_ready() -> void: 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) + # 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 + func update_board() -> void: game_manager.world.tilemap_player.clear_cells() game_manager.world.tilemap_player.set_cell(board_position, tilemap_types.PLAYER) +# SECTION: inventory system + +func pick_up_item(tilemap_pos: Vector2i) -> void: + var pick_up_cell: TileData = game_manager.world.tilemap_interactive.get_cell(tilemap_pos) + if not pick_up_cell: + push_warning("Player trying to pick up item that does not exist at ", tilemap_pos) + return + + var pick_up_item_type: Vector2i = game_manager.world.tilemap_interactive.tilemap.get_cell_atlas_coords(tilemap_pos) + + if inventory_slot != tilemap_types.EMPTY: + # set the type of the item on the tilemap to the one in the inventory, switching them + game_manager.world.tilemap_interactive.set_cell(tilemap_pos, inventory_slot) + + inventory_slot = pick_up_item_type + + +# SECTION: player movement + +func walk_towards(position: Vector2i) -> void: + var path: Array[Vector2i] = game_manager.world.find_path(board_position, position) + if len(path) > 1: + var next_position: Vector2i = path[1] + var direction: Vector2i = find_direction(board_position, next_position) + move_player(direction) + + +func move_player(direction: Vector2i) -> void: + var new_position: Vector2 = board_position + direction + if game_manager.world.is_walkable(new_position): + board_position = new_position + else: + push_warning("Player trying to move to non-walkable position, prevented ", new_position) + + +func find_direction(pos_a: Vector2i, pos_b: Vector2i) -> Vector2i: + var direction: Vector2i = Vector2i(0, 0) + if pos_a.x < pos_b.x: + direction.x = 1 + elif pos_a.x > pos_b.x: + direction.x = -1 + + if pos_a.y < pos_b.y: + direction.y = 1 + elif pos_a.y > pos_b.y: + direction.y = -1 + + return direction + + +# SECTION: game tick + +func tick_handle_temperature(cell_temperature: int): + if cell_temperature == 0: + temperature_timer = 0 + elif temperature_timer > temperature_endure: + temperature_timer += cell_temperature + health -= temperature_damage + + +func tick_handle_food(): + if food <= 0: + health -= food_damage; + pass + + 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) + 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 + tick_handle_temperature(cell_temperature) + else: + push_error("No player found on tilemap") + + tick_handle_food() + + if health < 0: + game_manager.player_health_depleted() diff --git a/project/scripts/tilemap/TileMapLayerAccess.gd b/project/scripts/tilemap/TileMapLayerAccess.gd index d45ae10..ba93596 100644 --- a/project/scripts/tilemap/TileMapLayerAccess.gd +++ b/project/scripts/tilemap/TileMapLayerAccess.gd @@ -22,8 +22,10 @@ func get_cells_by_custom_data(field_name: String, custom_data: Variant) -> Array return tiles_with_custom_data -func get_custom_data(coords: Vector2i, field_name: String) -> Variant: +func get_custom_data(coords: Vector2i, field_name: String, default_value: Variant) -> Variant: var tile_data: TileData = tilemap.get_cell_tile_data(coords) + if not tile_data: + return default_value return tile_data.get_custom_data(field_name) diff --git a/project/scripts/tilemap/TileMapTileTypes.gd b/project/scripts/tilemap/TileMapTileTypes.gd index 92e2dc4..07e9a48 100644 --- a/project/scripts/tilemap/TileMapTileTypes.gd +++ b/project/scripts/tilemap/TileMapTileTypes.gd @@ -1,5 +1,8 @@ class_name TileMapTileTypes +# global values +const EMPTY: Vector2i = Vector2i(-1, -1) +# # ground, sid = 0 const GROUND_GRASS: Vector2i = Vector2i(0, 0) const GROUND_WATER_SHALLOW: Vector2i = Vector2i(1, 0) @@ -7,8 +10,11 @@ const GROUND_WATER_DEEP: Vector2i = Vector2i(2, 0) const GROUND_SAND: Vector2i = Vector2i(3, 0) # # objects, sid = 1 -const OBJECT_RANDOM_1: Vector2i = Vector2i(0, 0) # testing only, to be removed -const OBJECT_RANDOM_2: Vector2i = Vector2i(1, 0) # testing only, to be removed +# 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) +# I = interactive # # temperature, sid = 2 const TEMPERATURE_NORMAL: Vector2i = Vector2i(-1, -1) diff --git a/project/scripts/tilemap/World.gd b/project/scripts/tilemap/World.gd index 96cfaa8..0e0dadd 100644 --- a/project/scripts/tilemap/World.gd +++ b/project/scripts/tilemap/World.gd @@ -11,27 +11,90 @@ 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 - # tilemap_temperature.fill_area(Vector2i(0, 0), Vector2i(10, 10), tilemap_types.TEMPERATURE_COLD_1) - # tilemap_temperature.fill_area(Vector2i(4, 4), Vector2i(6, 6), tilemap_types.TEMPERATURE_NORMAL) - # print(tilemap_non_interactive.get_cells_by_custom_data("walkable", true)) - # tilemap_ground.clear_cells() - # tilemap_ground.set_cell(Vector2i(0, 0), tilemap_types.GROUND_GRASS) - # print(tilemap_ground.local_to_cell(get_local_mouse_position())) + +# example usage +# tilemap_temperature.fill_area(Vector2i(0, 0), Vector2i(10, 10), tilemap_types.TEMPERATURE_COLD_1) +# tilemap_temperature.fill_area(Vector2i(4, 4), Vector2i(6, 6), tilemap_types.TEMPERATURE_NORMAL) +# print(tilemap_non_interactive.get_cells_by_custom_data("walkable", true)) +# tilemap_ground.clear_cells() +# tilemap_ground.set_cell(Vector2i(0, 0), tilemap_types.GROUND_GRASS) +# print(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 non_interactive_tile: TileData = tilemap_non_interactive.get_cell(position) + if non_interactive_tile: + return false + + return true + +var f_score: Dictionary = {} + + +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 _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