rewired signals and added savemanager and resource saving

This commit is contained in:
Luca 2024-09-26 21:37:19 +02:00
parent 0f5045dc22
commit f694d5c4f8
13 changed files with 138 additions and 87 deletions

View file

@ -1,12 +1,23 @@
extends CharacterBody3D extends CharacterBody3D
@onready var animated_mesh = $GobotSkin @onready var animated_mesh = $GobotSkin
var in_battle: bool = false var in_battle: bool = false
const SPEED = 5.0 const SPEED = 5.0
const JUMP_VELOCITY = 4.5 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: func _physics_process(delta: float) -> void:
if in_battle == true: if in_battle == true:
return return

View file

@ -6,7 +6,7 @@
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_xuba7"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_xuba7"]
height = 1.7 height = 1.7
[node name="Player" type="CharacterBody3D"] [node name="Player" type="CharacterBody3D" groups=["save_nodes"]]
axis_lock_angular_x = true axis_lock_angular_x = true
axis_lock_angular_y = true axis_lock_angular_y = true
axis_lock_angular_z = true axis_lock_angular_z = true

20
game.gd
View file

@ -10,14 +10,17 @@ func _ready() -> void:
if skip_menu == true: if skip_menu == true:
UI.show_ingame_controls(!skip_menu) UI.show_ingame_controls(!skip_menu)
UI.show_main_menu(!skip_menu) UI.show_main_menu(!skip_menu)
_on_ui_on_new_game_started() start_new_game()
return return
# On start of the game, the main menu is shown # 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() UI.show_main_menu()
# this event comes from the MainMenu Node in the UI # 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() var world = preload("res://worlds/debug_level.tscn").instantiate()
UI.show_ingame_controls() UI.show_ingame_controls()
@ -28,7 +31,16 @@ func _on_ui_on_new_game_started() -> void:
var player = preload("res://entities/player/player.tscn").instantiate() var player = preload("res://entities/player/player.tscn").instantiate()
world.add_child(player) 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 # event comes from the MainMenu Node in the UI
func _on_ui_on_game_continued() -> void: func continue_game() -> void:
SaveGame.load() SaveManager.load_game()
UI.show_ingame_controls() UI.show_ingame_controls()
UI.update()

View file

@ -5,6 +5,7 @@
[node name="Game" type="Node3D"] [node name="Game" type="Node3D"]
script = ExtResource("1_mfbtr") script = ExtResource("1_mfbtr")
skip_menu = false
[node name="UI" parent="." instance=ExtResource("2_ynhuf")] [node name="UI" parent="." instance=ExtResource("2_ynhuf")]

View file

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

View file

@ -18,9 +18,8 @@ config/icon="res://assets/logo/logo.png"
[autoload] [autoload]
SaveManager="*res://resources/save_game/save_manager.gd"
UI="*res://ui/ui.gd" UI="*res://ui/ui.gd"
SaveGame="*res://globals/save_game.gd"
SaveData="*res://resources/save_data.gd"
[display] [display]
@ -35,7 +34,6 @@ window/handheld/orientation=1
folder_colors={ folder_colors={
"res://assets/": "red", "res://assets/": "red",
"res://entities/": "blue", "res://entities/": "blue",
"res://globals/": "gray",
"res://materials/": "pink", "res://materials/": "pink",
"res://resources/": "yellow", "res://resources/": "yellow",
"res://ui/": "green" "res://ui/": "green"
@ -43,7 +41,7 @@ folder_colors={
[global_group] [global_group]
Persist="Contains all nodes that need to be saved" save_nodes=""
[input] [input]

View file

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

View file

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

View file

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

View file

@ -19,7 +19,7 @@ func _process(delta) -> void:
save_timer = 0.0 save_timer = 0.0
# Save the game # Save the game
SaveGame.save() SaveManager.save_game()
save_button_held_down = false save_button_held_down = false
else: else:

View file

@ -12,12 +12,12 @@ func _on_close_button_pressed() -> void:
pass # Replace with function body. pass # Replace with function body.
func _update_monster_list() -> void: func _update_monster_list() -> void:
for i in SaveData.monsters.size(): for i in SaveManager.current_save.party.size():
var monster = SaveData.monsters[i] var monster = SaveManager.current_save.party[i]
var entry = monster_list_entry.instantiate() var entry = monster_list_entry.instantiate()
# populate the new entry # populate the new entry
entry.populate(SaveData.monsters[i]) entry.populate(SaveManager.current_save.party[i])
# add it to the list # add it to the list
monster_list_entry_container.add_child(entry) monster_list_entry_container.add_child(entry)

View file

@ -5,7 +5,7 @@ signal on_game_continued
# On ready, check which buttons to show # On ready, check which buttons to show
func _ready() -> void: func _ready() -> void:
if SaveGame.savegame_exists(): if SaveManager.save_exists():
%ContinueGameButton.visible = true %ContinueGameButton.visible = true
%NewGameButton.visible = false %NewGameButton.visible = false

View file

@ -34,7 +34,7 @@ func _on_ingame_controls_menu_button_clicked() -> void:
ingame_menu.visible = true ingame_menu.visible = true
# Updates the whole UI # Updates the whole UI
func _update_ui() -> void: func update() -> void:
# Updating the player info # Updating the player info
ingame_menu._update_player_info() ingame_menu._update_player_info()