How do I mash these two concepts together?
Solutions that don’t work for me:
Using JSON - I have more complex data structures with other Resources, which means it would be too complex to use JSON or XML
Adding extends Resource to the autoload script. Now I can’t call it like GameContext.score, but would require an instance with .new()
Having the Resource inside the singleton as a variable - This makes it too much of a hassle to get to. Imagine GameContext.save_data.score
Having all variables twice - This is what I’m doing right now. I just have a SaveData Resource AND the singleton, both with the same variables. This works, but it’s tedious and error-prone.
How do I solve this or is there a better way? (maybe something I’m missing?)
So you want to get all the variables in the global-script and put them into the resource?
When you want to save you can call “GameContext.get_property_list()” and go through every property with a for-loop and save the variable into the Resource if the resource also has that property
Yes, this would save me the hassle of copying each variable, but it doesn’t solve the deeper problem. I would still need 2 variables for each “thing”. Is there a way to combine them? In other words, instead of having a separate Resource and a separate singleton, can I merge them?
That would be ideal, but I need to access the data from multiple scenes and scripts, which is why I previously used autoload. I need the Resource to exist just once and have its reference at multiple places.
For anyone looking for the same solution, here’s how I fixed the problem:
extends Node
const SAVE_PATH := "user://save.tres"
var turn_number: int
var budget: int
var happiness: float
var temperature: int
var humidity: float
var criticality: float
var upgrade_data: Dictionary
var upgrade_queue: Array[UpgradeConstructionData]
func initialize() -> void:
turn_number = 0
budget = 1_000_000
happiness = randf()
temperature = randi_range(10, 40)
humidity = randf()
criticality = 0.1
# convert the array of upgrades into a dict
var upgrade_group: ResourceGroup = load("res://data/upgrade_group.tres")
var upgrade_array := []
upgrade_group.load_all_into(upgrade_array)
for upgrade: Upgrade in upgrade_array:
# note: .duplicate() prevents from modifying the reference
# to the Resource file directly, otherwise loading the resource again won't reset its data
upgrade_data[upgrade.id] = upgrade.duplicate()
func save_data() -> void:
var save_data := SaveData.new()
# set all SaveData properties to their equivalent in the GameContext
for property in get_property_list():
# check that the property is from this script
if property["usage"] != PROPERTY_USAGE_SCRIPT_VARIABLE:
continue
var prop_name: StringName = property["name"]
save_data.set(prop_name, get(prop_name))
ResourceSaver.save(save_data, SAVE_PATH)
func load_data() -> void:
var save_data: SaveData = ResourceLoader.load(SAVE_PATH)
# set all GameContext properties to their equivalent in the SaveData
for property in get_property_list():
# check that the property is from this script
if property["usage"] != PROPERTY_USAGE_SCRIPT_VARIABLE:
continue
var prop_name: StringName = property["name"]
set(prop_name, save_data.get(prop_name))
class_name SaveData
extends Resource
# NOTE: having each property twice (once in GameContext and once here) is a bit shameful,
# but it was the best solution (my thread: https://forum.godotengine.org/t/autoload-resource-singleton-for-keeping-and-saving-game-state/78981/1)
@export var turn_number: int
@export var budget: int
@export var happiness: float
@export var temperature: int
@export var humidity: float
@export var criticality: float
@export var upgrade_data: Dictionary
@export var upgrade_queue: Array[UpgradeConstructionData]
You can ignore the specific properties I use in my game and the initialize() function and just focus on the save_data() and load_data() functions. The rest is just for context.