163 lines
5.2 KiB
GDScript
163 lines
5.2 KiB
GDScript
## Script that manages saving games.
|
|
class_name SaveGame extends Node
|
|
|
|
const ENABLED = true
|
|
const ENCRYPTION_KEY = "godotrules"
|
|
const SAVE_GAME_TEMPLATE = "savegame.save"
|
|
const SAVE_GROUP_NAME = "Persist"
|
|
const NODE_DATA = "node_data"
|
|
|
|
static func delete_save() -> void:
|
|
|
|
if not ENABLED:
|
|
return
|
|
|
|
DirAccess.remove_absolute("user://" + SAVE_GAME_TEMPLATE)
|
|
|
|
static func has_save() -> bool:
|
|
return FileAccess.file_exists("user://" + SAVE_GAME_TEMPLATE)
|
|
|
|
static func save_game(tree:SceneTree):
|
|
|
|
if not ENABLED:
|
|
return
|
|
|
|
print("Saving game to user://" + SAVE_GAME_TEMPLATE)
|
|
|
|
var save_file = null
|
|
|
|
if OS.is_debug_build():
|
|
save_file = FileAccess.open("user://" + SAVE_GAME_TEMPLATE, FileAccess.WRITE)
|
|
else:
|
|
save_file = FileAccess.open_encrypted_with_pass("user://" + SAVE_GAME_TEMPLATE, FileAccess.WRITE, ENCRYPTION_KEY)
|
|
|
|
var save_nodes = tree.get_nodes_in_group(SAVE_GROUP_NAME)
|
|
|
|
for node in save_nodes:
|
|
|
|
var save_data = {}
|
|
|
|
# Check the node is an instanced scene so it can be instanced again during load.
|
|
if not node.scene_file_path.is_empty():
|
|
save_data["scene_file_path"] = node.scene_file_path
|
|
|
|
if not node.get_path().is_empty():
|
|
save_data["path"] = node.get_path()
|
|
|
|
if not node.get_parent().get_path().is_empty():
|
|
save_data["parent"] = node.get_parent().get_path()
|
|
|
|
if "position" in node:
|
|
save_data["pos_x"] = node.position.x
|
|
save_data["pos_y"] = node.position.y
|
|
if node.position is Vector3:
|
|
save_data["pos_z"] = node.position.z
|
|
|
|
if node is Node2D:
|
|
save_data["rotation"] = node.rotation
|
|
elif node is Node3D:
|
|
save_data["rotation_x"] = node.rotation.x
|
|
save_data["rotation_y"] = node.rotation.y
|
|
save_data["rotation_z"] = node.rotation.z
|
|
|
|
if "scale" in node:
|
|
save_data["scale_x"] = node.scale.x
|
|
save_data["scale_y"] = node.scale.y
|
|
if node.scale is Vector3:
|
|
save_data["scale_z"] = node.scale.z
|
|
|
|
save_data["visible"] = node.visible
|
|
|
|
if node is CanvasItem:
|
|
save_data["modulate_r"] = node.modulate.r
|
|
save_data["modulate_g"] = node.modulate.g
|
|
save_data["modulate_b"] = node.modulate.b
|
|
save_data["modulate_a"] = node.modulate.a
|
|
|
|
# Call the node's save function.
|
|
if node.has_method("save_data"):
|
|
save_data["node_data"] = node.call("save_data")
|
|
|
|
# Store the save dictionary as a new line in the save file.
|
|
save_file.store_line(JSON.stringify(save_data))
|
|
|
|
static func load_game(tree:SceneTree) -> void:
|
|
|
|
if not ENABLED:
|
|
return
|
|
|
|
if not has_save():
|
|
print("No save game found. Skipped loading!")
|
|
return
|
|
|
|
print("Load game from user://" + SAVE_GAME_TEMPLATE)
|
|
|
|
var save_nodes = tree.get_nodes_in_group(SAVE_GROUP_NAME)
|
|
|
|
var nodes_by_path = {}
|
|
for node in save_nodes:
|
|
if not node.get_path().is_empty():
|
|
nodes_by_path[node.get_path()] = node
|
|
|
|
# Load the file line by line and process that dictionary to restore
|
|
# the object it represents.
|
|
var save_file = null
|
|
|
|
if OS.is_debug_build():
|
|
save_file = FileAccess.open("user://" + SAVE_GAME_TEMPLATE, FileAccess.READ)
|
|
else:
|
|
save_file = FileAccess.open_encrypted_with_pass("user://" + SAVE_GAME_TEMPLATE, FileAccess.READ, ENCRYPTION_KEY)
|
|
|
|
while save_file.get_position() < save_file.get_length():
|
|
# Get the saved dictionary from the next line in the save file
|
|
var test_json_conv = JSON.new()
|
|
test_json_conv.parse(save_file.get_line())
|
|
var save_data = test_json_conv.get_data()
|
|
|
|
# Firstly, we need to create the object and add it to the tree and set its position.
|
|
var node = null
|
|
|
|
if "path" in save_data and nodes_by_path.has(NodePath(save_data.path)):
|
|
node = nodes_by_path[NodePath(save_data.path)]
|
|
nodes_by_path.erase(NodePath(save_data.path))
|
|
elif "path" in save_data and "parent" in save_data and "scene_file_path" in save_data:
|
|
# node is not present in tree so it was dynamically added at runtime
|
|
var parent = tree.root.get_node(NodePath(save_data["parent"]))
|
|
node = load(save_data["scene_file_path"]).instantiate()
|
|
parent.add_child(node)
|
|
else:
|
|
push_warning("skipping loading node from save game: node got moved.")
|
|
continue
|
|
|
|
if "position" in node:
|
|
if node.scale is Vector2:
|
|
node.position = Vector2(save_data["pos_x"], save_data["pos_y"])
|
|
elif node.scale is Vector3:
|
|
node.position = Vector3(save_data["pos_x"], save_data["pos_y"], save_data["pos_z"])
|
|
|
|
if node is Node2D:
|
|
node.rotation = save_data["rotation"]
|
|
elif node is Node3D:
|
|
node.rotation = Vector3(save_data["rotation_x"], save_data["rotation_y"], save_data["rotation_z"])
|
|
|
|
if "scale" in node:
|
|
if node.scale is Vector2:
|
|
node.scale = Vector2(save_data["scale_x"], save_data["scale_y"])
|
|
elif node.scale is Vector3:
|
|
node.scale = Vector3(save_data["scale_x"], save_data["scale_y"], save_data["scale_z"])
|
|
|
|
if save_data.has("visible") and "visible" in node:
|
|
node.visible = save_data["visible"]
|
|
|
|
if node is CanvasItem:
|
|
node.modulate = Color(save_data["modulate_r"], save_data["modulate_g"], save_data["modulate_b"], save_data["modulate_a"])
|
|
|
|
if node.has_method("load_data") and save_data.has("node_data"):
|
|
node.call("load_data", save_data["node_data"])
|
|
|
|
# delete any node from scene that was not persisted into the save file
|
|
# but is currently tagged as "Persisted" -> this means node got removed in the meantime
|
|
for key in nodes_by_path:
|
|
var node = nodes_by_path[key]
|
|
node.queue_free()
|