Autoload Resource Singleton for keeping and saving game state

I am currently keeping the game state in an autoload singleton like this:

class_name GameContext

var score = 0
var some_other_stuff = "Hello World"

I want to save and load this state, which I know how to do with a Resource:

class_name SaveData
extends Resource

@export var score = 0
@export var some_other_stuff = "Hello World"

somewhere in the code I’d do

ResourceSaver.save(my_save_data, "user://save.tres")

How do I mash these two concepts together?
Solutions that don’t work for me:

  1. Using JSON - I have more complex data structures with other Resources, which means it would be too complex to use JSON or XML
  2. Adding extends Resource to the autoload script. Now I can’t call it like GameContext.score, but would require an instance with .new()
  3. 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
  4. 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

1 Like

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?

You can just use one resource and save everything in there directly

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.

Well then i think you have to go with this:

its also possible to pack-scenes and save them but i dont think this works with autoload.

You can also use a getter method:

GameContext.get("score")

and then return the score value from the resource and the same with setter

Yeah, looks like I have no other choice. But thanks.

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.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.