From f694d5c4f8e058d42c24490e0c833f855834074e Mon Sep 17 00:00:00 2001 From: Luca Junge Date: Thu, 26 Sep 2024 21:37:19 +0200 Subject: [PATCH] rewired signals and added savemanager and resource saving --- entities/player/player.gd | 15 +++++- entities/player/player.tscn | 2 +- game.gd | 20 ++++++-- game.tscn | 1 + globals/save_game.gd | 70 --------------------------- project.godot | 6 +-- resources/save_game/save_game.gd | 15 ++++++ resources/save_game/save_manager.gd | 74 +++++++++++++++++++++++++++++ resources/save_game/saved_data.gd | 10 ++++ ui/ingame_controls.gd | 2 +- ui/ingame_menu.gd | 6 +-- ui/main_menu.gd | 2 +- ui/ui.gd | 2 +- 13 files changed, 138 insertions(+), 87 deletions(-) delete mode 100644 globals/save_game.gd create mode 100644 resources/save_game/save_game.gd create mode 100644 resources/save_game/save_manager.gd create mode 100644 resources/save_game/saved_data.gd diff --git a/entities/player/player.gd b/entities/player/player.gd index 5c08fd4..b3c9765 100644 --- a/entities/player/player.gd +++ b/entities/player/player.gd @@ -1,12 +1,23 @@ extends CharacterBody3D @onready var animated_mesh = $GobotSkin - var in_battle: bool = false - const SPEED = 5.0 const JUMP_VELOCITY = 4.5 +func on_save_game(saved_data : Array[SavedData]): + var data = SavedData.new() + data.position = global_position + data.scene_path = scene_file_path + saved_data.append(data) + +func on_before_load_game(): + get_parent().remove_child(self) + queue_free() + +func on_load_game(saved_data: SavedData): + global_position = saved_data.position + func _physics_process(delta: float) -> void: if in_battle == true: return diff --git a/entities/player/player.tscn b/entities/player/player.tscn index 3755f51..01870c8 100644 --- a/entities/player/player.tscn +++ b/entities/player/player.tscn @@ -6,7 +6,7 @@ [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_xuba7"] height = 1.7 -[node name="Player" type="CharacterBody3D"] +[node name="Player" type="CharacterBody3D" groups=["save_nodes"]] axis_lock_angular_x = true axis_lock_angular_y = true axis_lock_angular_z = true diff --git a/game.gd b/game.gd index 8d0c685..603bc2a 100644 --- a/game.gd +++ b/game.gd @@ -10,14 +10,17 @@ func _ready() -> void: if skip_menu == true: UI.show_ingame_controls(!skip_menu) UI.show_main_menu(!skip_menu) - _on_ui_on_new_game_started() + start_new_game() return # On start of the game, the main menu is shown + UI.connect("on_new_game_started", start_new_game) + UI.connect("on_game_continued", continue_game) UI.show_main_menu() + # this event comes from the MainMenu Node in the UI -func _on_ui_on_new_game_started() -> void: +func start_new_game() -> void: var world = preload("res://worlds/debug_level.tscn").instantiate() UI.show_ingame_controls() @@ -27,8 +30,17 @@ func _on_ui_on_new_game_started() -> void: # Add the starting player to the starting world var player = preload("res://entities/player/player.tscn").instantiate() world.add_child(player) + + # Give the player a monster + var monster = MonsterData.new() + monster.set_data("debuggy") + SaveManager.current_save.party.push_back(monster) + + # Update the UI once + UI.update() # event comes from the MainMenu Node in the UI -func _on_ui_on_game_continued() -> void: - SaveGame.load() +func continue_game() -> void: + SaveManager.load_game() UI.show_ingame_controls() + UI.update() diff --git a/game.tscn b/game.tscn index fb4aa8f..4949935 100644 --- a/game.tscn +++ b/game.tscn @@ -5,6 +5,7 @@ [node name="Game" type="Node3D"] script = ExtResource("1_mfbtr") +skip_menu = false [node name="UI" parent="." instance=ExtResource("2_ynhuf")] diff --git a/globals/save_game.gd b/globals/save_game.gd deleted file mode 100644 index 6c41d21..0000000 --- a/globals/save_game.gd +++ /dev/null @@ -1,70 +0,0 @@ -extends Node -# save_game.gd - Responsible for saving and loading the game using a SaveData resource - -const SAVE_GAME_PATH := "user://savegame.save" -const VERSION: int = 1 - -func save() -> void: - var save_file = FileAccess.open(SAVE_GAME_PATH, FileAccess.WRITE) - - # Save the meta data like save game version and timestamps - var meta_data = { - "meta": { - "version" : VERSION, - "timestamp" : Time.get_datetime_string_from_system() - } - } - var meta_json_string = JSON.stringify(meta_data) - save_file.store_line(meta_json_string) - - # Save the player node's position - var player = get_tree().get_root().find_child("Player", true, false) - var player_node_data = player.save() - var player_json = JSON.stringify(player_node_data) - save_file.store_line(player_json) - - # Save all other stuff from the SaveData - var save_data = SaveData.call("save") - var json_string = JSON.stringify(save_data) - save_file.store_line(json_string) - -func load() -> void: - if not FileAccess.file_exists(SAVE_GAME_PATH): - print("No save file exists.") - return - - # NEXT STEP: How to load correctly and in which order? - # first the current world, then the player and their position and then the inventory? - - # Load the save file line by line and process the dictionary to restore - # the object it represents - var save_file = FileAccess.open(SAVE_GAME_PATH, FileAccess.READ) - while save_file.get_position() < save_file.get_length(): - var json_string = save_file.get_line() - - # Creates the helper class to interact with JSON - var json = JSON.new() - - # Check if there is any error while parsing the JSON string, skip in case of failure - var parse_result = json.parse(json_string) - if not parse_result == OK: - print("JSON Parse Error: ", json.get_error_message(), " in ", json_string, " at line", json.get_error_line()) - continue - - # Get the data from the JSON object - var node_data = json.get_data() - - if node_data.has("meta"): - print("Meta data ignored.") - - # handle the player data (name, money, etc.) - if node_data.has("player_data"): - var world_to_load = load(node_data["player_data"].world) - var instance = world_to_load.instantiate() - instance.add_child(load("res://scenes/player/player.tscn").instantiate()) - var level_container = get_tree().get_root().find_child("CurrentLevel", true, false) - level_container.add_child(instance) - - -func savegame_exists() -> bool: - return FileAccess.file_exists(SAVE_GAME_PATH) diff --git a/project.godot b/project.godot index c31bff6..0206d18 100644 --- a/project.godot +++ b/project.godot @@ -18,9 +18,8 @@ config/icon="res://assets/logo/logo.png" [autoload] +SaveManager="*res://resources/save_game/save_manager.gd" UI="*res://ui/ui.gd" -SaveGame="*res://globals/save_game.gd" -SaveData="*res://resources/save_data.gd" [display] @@ -35,7 +34,6 @@ window/handheld/orientation=1 folder_colors={ "res://assets/": "red", "res://entities/": "blue", -"res://globals/": "gray", "res://materials/": "pink", "res://resources/": "yellow", "res://ui/": "green" @@ -43,7 +41,7 @@ folder_colors={ [global_group] -Persist="Contains all nodes that need to be saved" +save_nodes="" [input] diff --git a/resources/save_game/save_game.gd b/resources/save_game/save_game.gd new file mode 100644 index 0000000..135b650 --- /dev/null +++ b/resources/save_game/save_game.gd @@ -0,0 +1,15 @@ +class_name SavedGame +extends Resource +# This resource collects all data to save, from the player and other nodes + +# Player info to save +@export var player_money : int = 0 +@export var party: Array[MonsterData] = [] + +# Environment info to save (daytime, current world, weather) +@export var world : String = "" +@export var daytime : float = 12.0 +@export var weather : String = "this should be a global enum" + +# All other nodes's data is collected here +@export var saved_data: Array[SavedData] = [] diff --git a/resources/save_game/save_manager.gd b/resources/save_game/save_manager.gd new file mode 100644 index 0000000..1bbcc68 --- /dev/null +++ b/resources/save_game/save_manager.gd @@ -0,0 +1,74 @@ +extends Node + +# The group of nodes to save is named "save_nodes" + +# Reference to the currently loaded save game +@onready var current_save : SavedGame = SavedGame.new() + +var world + +func save_game(): + # Create a new empty save game + var saved_game := SavedGame.new() + + # Save the environment data + saved_game.world = "res://worlds/debug_level.tscn" #SceneManager.get_world() + saved_game.daytime = 12.5 + saved_game.weather = "this should be an enum" + + # Save the party data + for monster in SaveManager.current_save.party: + saved_game.party.append(monster) + + # Create an array to store the data of all other nodes + var saved_data : Array[SavedData] = [] + + # Use a group call to collect all the data from the other objects + get_tree().call_group("save_nodes", "on_save_game", saved_data) + + # Store the array in the saved game + saved_game.saved_data = saved_data + + # Store it into a file + ResourceSaver.save(saved_game, "user://savegame.tres") + + +func load_game(): + # Load the saved game file + var saved_game : SavedGame = load("user://savegame.tres") as SavedGame + + # Return if no save file is found + if saved_game == null: + print("No savegame found.") + return + + # Save a reference to the current save game + current_save = saved_game + print(saved_game.party[0].display_name) + + # Clean up all non-static objects from the scene + get_tree().call_group("save_nodes", "on_before_load_game") + + # Restore world data + # doing it via get_tree, until the SceneManager exists + var _world : Node = load(saved_game.world).instantiate() + get_node("/root/Game/CurrentLevel").add_child(_world) + world = _world + #SceneManager.set_world(saved_game.world) + + # Restore all other nodes + for node in saved_game.saved_data: + # Instantiate the scene for the node + var scene := load(node.scene_path) as PackedScene + var restored_node = scene.instantiate() + + # Add the item to the world + # todo: add parent as value to save + world.add_child(restored_node) + + # run a callback to restore the item's state + if restored_node.has_method("on_load_game"): + restored_node.on_load_game(node) + +func save_exists(): + return FileAccess.file_exists("user://savegame.tres") diff --git a/resources/save_game/saved_data.gd b/resources/save_game/saved_data.gd new file mode 100644 index 0000000..be69f60 --- /dev/null +++ b/resources/save_game/saved_data.gd @@ -0,0 +1,10 @@ +class_name SavedData +extends Resource +# Main class that can save a node's information +# Every node's save data extends from this class + +# Position of the node to save +@export var position : Vector3 = Vector3.ZERO + +# Path to the scene of the node +@export var scene_path : String = "" diff --git a/ui/ingame_controls.gd b/ui/ingame_controls.gd index 8d860d4..3d21595 100644 --- a/ui/ingame_controls.gd +++ b/ui/ingame_controls.gd @@ -19,7 +19,7 @@ func _process(delta) -> void: save_timer = 0.0 # Save the game - SaveGame.save() + SaveManager.save_game() save_button_held_down = false else: diff --git a/ui/ingame_menu.gd b/ui/ingame_menu.gd index 2461141..c2d6928 100644 --- a/ui/ingame_menu.gd +++ b/ui/ingame_menu.gd @@ -12,12 +12,12 @@ func _on_close_button_pressed() -> void: pass # Replace with function body. func _update_monster_list() -> void: - for i in SaveData.monsters.size(): - var monster = SaveData.monsters[i] + for i in SaveManager.current_save.party.size(): + var monster = SaveManager.current_save.party[i] var entry = monster_list_entry.instantiate() # populate the new entry - entry.populate(SaveData.monsters[i]) + entry.populate(SaveManager.current_save.party[i]) # add it to the list monster_list_entry_container.add_child(entry) diff --git a/ui/main_menu.gd b/ui/main_menu.gd index c7cd744..d08bb98 100644 --- a/ui/main_menu.gd +++ b/ui/main_menu.gd @@ -5,7 +5,7 @@ signal on_game_continued # On ready, check which buttons to show func _ready() -> void: - if SaveGame.savegame_exists(): + if SaveManager.save_exists(): %ContinueGameButton.visible = true %NewGameButton.visible = false diff --git a/ui/ui.gd b/ui/ui.gd index e56785f..e391273 100644 --- a/ui/ui.gd +++ b/ui/ui.gd @@ -34,7 +34,7 @@ func _on_ingame_controls_menu_button_clicked() -> void: ingame_menu.visible = true # Updates the whole UI -func _update_ui() -> void: +func update() -> void: # Updating the player info ingame_menu._update_player_info()