I’m using resources to save game states. So far everything is working fine, but it seems like my nested resources aren’t saved.
Here’s a rough example of what I’m trying to do:
“Fridge” object has a resource: “Content”, which has a resource inside “water bottle”
When I use ResourceLoader, I’m able to save and load the Content resource as expected (it has water bottle in it)
But the data of the water bottle resource (full/half-full/empty) is NOT being saved in this case.
Is there a way to get this to save too? Or do I need to save the nested resources separately?
I’ve exported all needed variables, to no effect.
I’ll look into the PROPERTY_USAGE_STORAGE to learn more about it.
I’ve created the smallest possible project I could think of to show my issue:
In this project there are 2 custom resources, one is nested within the other. I can load the main resource, but its values or the values of the child resource do not seem to get loaded/saved properly. I assume my thinking is wrong on how this should work and would appreciate any pointers.
Well, I confess your example code confused me somewhat. In the end I just hacked my own script to try help you. It uses print, so see the output there.
You need a scene with save and load buttons. Wire them to this script:
extends Control
## Demo of saving a resource with a sub-resource
## Be sure to open dbat_resource.tres in an editor to see how Godot does it.
var fridge:Fridge
## Using ready to fill the fridge
func _ready() -> void:
fridge = Fridge.new() #<-- this will detect the Fridge class automagically.
var waterbottle = Thing.new() #<-- same
var apple = Thing.new()
waterbottle.name = "expensive water"
apple.name = "a mouldy apple"
fridge.contents.append(waterbottle)
fridge.contents.append(apple)
print("Fridge is full, hit save, then load.")
func _on_save_pressed() -> void:
var err = ResourceSaver.save(fridge,
"res://dbat/dbat_resource.tres",
ResourceSaver.FLAG_CHANGE_PATH)
if not err:
print("saved ok")
else:
print(err)
func _on_load_pressed() -> void:
var tmp = ResourceLoader.load(
"res://dbat/dbat_resource.tres")
if tmp:
fridge = tmp
print("loaded ok")
print(" fridge:", fridge)
for thing in fridge.contents:
print("contents:", thing.name)
else:
print("oops")
Here’s the Fridge:
class_name Fridge
extends Resource
@export var contents:Array[Thing]
and the Things that go in it:
class_name Thing
extends Resource
@export var name:String
To truly test this for my purposes, I added another button that calls the following function to your script:
func _on_create_new_thing_pressed() -> void:
var coin_flip = randi_range(0,1)
var random_value = randi_range(1,10)
var new_thing = Thing.new()
if coin_flip == 0:
new_thing.name = "waterbottle"
else:
new_thing.name = "apple"
new_thing.value = random_value
print("Created new thing: ", new_thing.name, " with value ", new_thing.value)
fridge.contents.clear()
fridge.contents.append(new_thing)
print("Fridge emptied and new thing added.")
I’ve also added another value to the Thing class, it now looks like this:
class_name Thing
extends Resource
@export var name:String
@export var value:int
When I now run this and save a randomly generated thing in the fridge, I can load the correct thing, but the things value is incorrect / not saved.
This is the main issue I run into.
Seems ok to me. Here’s the code in full. (Thing and Fridge you already have.)
extends Control
## Demo of saving a resource with a sub-resource
## Be sure to open dbat_resource.tres in an editor to see how Godot does it.
var fridge:Fridge
## Using ready to fill the fridge
func _ready() -> void:
fridge = Fridge.new()
func _on_create_new_thing_pressed() -> void:
var coin_flip = randi_range(0,1)
var random_value = randi_range(1,10)
var new_thing = Thing.new()
if coin_flip == 0:
new_thing.name = "waterbottle"
else:
new_thing.name = "apple"
new_thing.value = random_value
print("Created new thing: ", new_thing.name, " with value ", new_thing.value)
fridge.contents.clear()
fridge.contents.append(new_thing)
print("Fridge emptied and new thing added.")
func _on_save_pressed() -> void:
var err = ResourceSaver.save(fridge,
"res://dbat/dbat_resource.tres",
ResourceSaver.FLAG_CHANGE_PATH)
if not err:
print("saved ok")
else:
print(err)
func _on_load_pressed() -> void:
var tmp = ResourceLoader.load(
"res://dbat/dbat_resource.tres")
if tmp:
fridge = tmp
print("loaded ok")
print(" fridge:", fridge)
for thing in fridge.contents:
print("contents:", thing.name, " value:", thing.value)
else:
print("oops")
I run it and press Add then Save and then Load. Output:
Created new thing: apple with value 10
Fridge emptied and new thing added.
saved ok
loaded ok
fridge:<Resource#-9223372001354185480>
contents:apple value:10
Created new thing: waterbottle with value 8
Fridge emptied and new thing added.
saved ok
loaded ok
fridge:<Resource#-9223372001354185480>
contents:waterbottle value:8
First of all, thanks for still looking into this with me, appreciate the help!
In your run, it seems like you create a new thing and then immediately save it, so the saved fridge and the temp fridge contents are always the same.
When I save contents, then create a new thing and then load the fridge without saving it, i don’t get the expected results:
Fridge is full, hit save, then load.
saved ok
loaded ok
fridge:<Resource#-9223372003954653958>
contents:expensive water with value 5
contents:a mouldy apple with value 3
Created new thing: waterbottle with value 10
Fridge emptied and new thing added.
loaded ok
fridge:<Resource#-9223372003954653958>
contents:waterbottle with value 10
As you see in the printout above, after loading, the fridge should have the expensive water and apple, not the water bottle. At least that’s what I’m trying to achieve.
I think the fix is ResourceLoader.CACHE_MODE_IGNORE in ResourceLoader.load
extends Control
## Demo of saving a resource with a sub-resource
## Be sure to open dbat_resource.tres in an editor to see how Godot does it.
var fridge:Fridge
func _on_create_new_thing_pressed() -> void:
if not fridge:
fridge = Fridge.new()
var coin_flip = randi_range(0,1)
var random_value = randi_range(1,10)
var new_thing = Thing.new()
if coin_flip == 0:
new_thing.name = "waterbottle"
else:
new_thing.name = "apple"
new_thing.value = random_value
print("\nCreated new thing: ", new_thing.name, " with value ", new_thing.value)
fridge.contents.clear()
fridge.contents.append(new_thing)
print("Fridge restocked.")
func _on_save_pressed() -> void:
if not fridge:
print("Add or Load first.")
return
var err = ResourceSaver.save(fridge,
"res://dbat/dbat_resource.tres",
ResourceSaver.FLAG_CHANGE_PATH)
if not err:
print("saved ok")
else:
print(err)
func _on_load_pressed() -> void:
var tmp:Fridge = ResourceLoader.load(
"res://dbat/dbat_resource.tres", "",
ResourceLoader.CACHE_MODE_IGNORE #<-- seems to make the difference!
)
if tmp:
fridge = tmp
print("Loaded ok")
print(" Fridge:", fridge)
for thing in fridge.contents:
print(" Contents:", thing.name, " value:", thing.value)
else:
print("Oops")
Output edited:
(Started with Add)
Created new thing: waterbottle with value 1
Fridge restocked.
(Attempted a Load here - I had deleted the tres file)
ERROR: blah blah
Oops
(Then I Saved)
saved ok
(Now a Load)
Loaded ok
Fridge:<Resource#-9223371994827848500>
Contents:waterbottle value:1
(Now Add to restock)
Created new thing: apple with value 5
Fridge restocked.
(Save)
saved ok
(Load)
Loaded ok
Fridge:<Resource#-9223371984912513794>
Contents:apple value:5
Thanks for this!
This works on my end as well, just as expected!
The bad news: In my main project that I’m trying to get this to work, it still does not seem to function that way.
As @mrcdk mentioned, CACHE_MODE_IGNORE still seems to have some issues, so that might be the cause, hoping it will be resolved in 4.3
In the meantime, I save the specific values I need from my nested resources separately and overwrite them after I’ve loaded them as a workaround. Not ideal, but does the job.
Yeah, Godot 4 has been a truck and I’ve been hanging onto a rope off the back, being dragged over all kinds of nasty stuff for months now. Each time I have a thing working, something else breaks.