#Custom Resources - wrong tool for this job? (saving/loading)

1 messages · Page 1 of 1 (latest)

rocky willow
#

The short version:
If I have an array of Object-extending class instances containing data, some of which have been created by the user, how should I be trying to save that data? I've been trying to use a custom Resource with a single property that holds the array, but for a bunch of different reasons that hasn't worked and I feel like I'm missing something/trying to use the wrong tool for the job. Any thoughts?

The details:
I'm making a small app for meal planning. I have a Meal class which extends Object and stores data about a single meal (e.g. its ID, name, etc). Meals are stored in an array which is read from/written to during the usage of the app. There's supposed to be a short list of meals stored in this array when the app is first started, but more meals can be created by the user and added to it.

What I've been trying to make work is using a custom Resource to save and load my meal data. I have a script (meal_data.gd) which extends Resource and has a single property - an array called meals. In its _init(), it creates five Meals (the starting data) and adds them to the array. There is also meal_data.tres which uses the meal_data.gd script.

As I typed this up I realized that I don't have it working at all anymore, though it partially was earlier. I've done a bunch of reading on using Resources but I'm sure I'm missing something. Any suggestions as to how to restructure this or silly things I'm forgetting are very welcome.

#

Relevant code (condensed):

main.gd

extends Node

var meal_data: MealData

func _ready() -> void:
    load_meal_data()
    ...

func load_meal_data() -> void:
    if ResourceLoader.exists("user://meal_data.tres"):
        meal_data = ResourceLoader.load("user://meal_data.tres")
    else:
        meal_data = ResourceLoader.load("res://meal_data.tres")

func save_meal_data() -> void:
    ResourceSaver.save(meal_data, "user://meal_data.tres")

meal.gd

class_name Meal
extends Object

var id: int
...
var weight: float

func _init(p_id: int = 0, ... p_weight: float = 1.0) -> void:
    id = p_id
    ...
    weight = p_weight

meal_data.gd

class_name MealData
extends Resource

@export var meals: Array

func _init() -> void:
    meals.append(Meal.new(0, "Turkey with mashed potatoes", 0b1111011, "2025-03-15"))
    ...
    meals.append(Meal.new(4, "Meatball subs", 0b0000100, "2025-03-19"))
grizzled wigeon
#

why is meal extending object instead of resource?

rocky willow
#

Just because as far as I could tell it didn't need to extend Resource so I went with the least complex thing to inherit from

grizzled wigeon
#

I'm going to say, that's the mistake

#

I think for the purpose of saving data in ResourceLoader, all of the inherited bits of data also have to be either resources or literals

rocky willow
#

I tried changing it to extend Resource instead and exporting all its vars and it still didn't work though

grizzled wigeon
#

I think you can save and load other ways, but ResourceLoader specifically will not

#

did you change it to Resource and make its variables @exports?

rocky willow
#

Yes

grizzled wigeon
#

can you open the file that is created in the save directory?

rocky willow
#

Yeah, want to see it?

grizzled wigeon
#

sort of, does it have anything about the array

rocky willow
#

Yes, but I think it's empty

grizzled wigeon
#

lemme see if with turning your object into a resource

rocky willow
#

Yeah that was after I made that change

#
[gd_resource type="Resource" script_class="MealData" load_steps=3 format=3]

[ext_resource type="Script" path="res://meal.gd" id="1_pjfnr"]
[ext_resource type="Script" path="res://meal_data.gd" id="1_ud76l"]

[resource]
script = ExtResource("1_ud76l")
meals = Array[ExtResource("1_pjfnr")]([])
metadata/_custom_type_script = "uid://dgwrjq85qxk4u"
grizzled wigeon
#

definitely empty

#

oh

#

I don't think resources have an init that triggers...

rocky willow
#

See I wondered about that but earlier today it sure seemed to be running the _init()

#

It was loading just fine, it was when I tried to fix it not saving for some reason that I broke everything haha

grizzled wigeon
#

check your array right before it saves to see if it has anything

#

because, well, if it's empty

#

it's possible that as an object, it is initializing, but as a resource it is not

rocky willow
grizzled wigeon
#

func save_meal_data() -> void:
ResourceSaver.save(meal_data, "user://meal_data.tres")

#

check if meal_data has anything

#

before saving it

rocky willow
#

I've already gone through it with breakpoints and meal_data never has anything besides an empty array

grizzled wigeon
#

if it has nothing before you save it, it's not going to save anything

#

I think maybe the thing you ran into was that when you ResourceLoad, I think _init happens in that situation

rocky willow
grizzled wigeon
#

this is really easy, if you want initial values in your resource, set them

#

@export var : int = 10 or whatever

rocky willow
#

Yeah I know. The problem is in this case my initial values are objects, not just just ints or strings etc

#

I want the app to start up with five Meals loaded into memory

grizzled wigeon
#

no, your initial items no longer are objects

#

Meal is a resource

rocky willow
#

Yeah I guess we did change that

grizzled wigeon
#

so make five Meal resources in your tree

rocky willow
#

So a separate resource for each meal then?

#

Yeah that

grizzled wigeon
#

and set the values for those five starter resources

#

I mean, start with one, get it to work

rocky willow
#

Yeah let me do one first

grizzled wigeon
#

after you have those, you can initialize you meals array in MealData with the five meals where you declare the variable

#

or well, one meal

#

that's assumign you want the meals array to always initialize with the same meals

#

if you only want the first mealdata object to have that array,. then I'd create a first mealdata object

#

and stick the five meals in the array in the inspector

rocky willow
#

The first time the app is opened it will be the short list of 5 meals, but the user can create new meals

grizzled wigeon
#

what's meal data for?

#

if it's only a container for the array of meals, then you might not want to be saving it

rocky willow
#

I was thinking it was the Resource I'm saving in order to preserve the state of the array between app runs

grizzled wigeon
#

if it's your main resource that everything encapsulate in, fine

#

I was just curious

#

there will then only ever be one mealdata per game session

rocky willow
#

I originally was just saving the meals in JSON but for various reasons I decided to switch to using a custom Resource

grizzled wigeon
#

it's definitely good to use a resource, especially after you figure them out at least

rocky willow
#

Yeah that was my impression, that's why I wanted to learn

grizzled wigeon
#

you will want to create a meal data resource as well

#

don't do it in code

#

put your meals in its array

#

and then create an @export variable to hold it in whatever your main function is

rocky willow
grizzled wigeon
#

yeah, and that shoudl all work then

rocky willow
#

Now can the user modify these Meal resources?

#

I want them to be able to change the name, weight, etc

#

Not to mention the weight will need to change programmatically anyway

#

The weight is for a random selection but it changes depending on how many days it's been since the meal was last eaten

grizzled wigeon
#

if they modify meals that are loaded into a variable, and those variables are saved back into your save, that's what will be saved

#

I don't htink the original meals are changed there because what you're loading into mealdata are instances of your starting meals

#

instances of a resource

#

but that doesn't matter, that's not what data you're going to use to populate your app's info

#

the data you're using in all your calculations will be whatever gets loaded/saved in mealdata, and that's always the same resources

#

it just means that when you're loading data, you have to check and see if there is user data, otherwise use the built in resource mealdata

#

(you just need to change your check not to "load" mealdata)

#
if ResourceLoader.exists("user://meal_data.tres"):
        meal_data = ResourceLoader.load("user://meal_data.tres") #no "else" statement neeeded```
rocky willow
#

Ah okay I see what you're saying here now

#

Still working out the modifying meals part

grizzled wigeon
#

did you get anything to save?

#

oo, be careful too

#

after you load, now that you're doing all this, check to see what the size of your loaded meal_data.meals is

#

or at least, delete you saved .tres every time you experiment

#

or you're going to be loading an empty data every time

rocky willow
#

So I'm working on pulling out a minimum reproducible example

grizzled wigeon
#

"if ResourceLoader.exists("user://meal_data.tres"):" is true right now, so you're going to load meal_data with that empty data every time

#

while experimenting, you've got to delete your .tres every time you want to "see" what would happen the first time a person uses your program

rocky willow
#

Oh

#

Yeah definitely

rocky willow
grizzled wigeon
#

sure, I might not respond right away, but I'll get back to you

rocky willow
#

Great, thanks

rocky willow
#

OR, the version saved in "user://" just contains a reference to the unedited version of the Meal resources?

#

To be clear, what's happening now:

  1. Load app fresh (without "user://meal_data.tres")
  2. Change name of meal (confirmed that this change is being applied to meal_data.meals)
  3. Save meal_data to "user://meal_data.tres"
  4. Quit app
  5. Load app (using "user://meal_data.tres")
  6. Meal name change has not persisted
rocky willow
#

Progress - Looks like when saving you need to use the ResourceSaver.FLAG_BUNDLE_RESOURCES flag to save the edited subresources. Opening "user://meal_data.tres" confirms that it's saving the data this way.

Only problem is that it's now throwing a parser error when trying to use "user://meal_data.tres": 'Class "Meal" hides a global script class.' Looking into this now.

rocky willow
#

Seems to be because saving things of type Meal inside meal_data.tres duplicates the meal.gd script inside of meal_data.tres? Not sure what to make of that or what to do about it

#

Yeah, FLAG_BUNDLE_RESOURCES includes a copy of the script belonging to the subresource. I don't see a way to exclude the script (unclear if that would actually work anyway). I found this on S/O which seems to discuss the problem.

sharp abyss
#

Did you also set the variables of Meal to exports?

rocky willow
#

Yep

sharp abyss
#

and are you still defining the _init inside the script?

rocky willow
#

Nope

#

Current code for the two resources:

meal_data.gd

class_name MealData
extends Resource


@export var meals: Array[Meal]

meal.gd

extends Resource


@export var id: int
...
@export var weight: float
#

Update: Slightly different behavior after reloading the editor - it's now opening the duplicated/bundled copy of the script in the script editor when the error occurs:

#

Don't think this matters much but it is new

sharp abyss
#

I'm guessing this is with the Bungle Flag? Try not using it

rocky willow
sharp abyss
rocky willow
#

Now that I've reloaded the editor you mean?

sharp abyss
#

Yea

#

Since youre getting different results, the previous way may give different results

rocky willow
#

I just removed the bundle flag and wiped the user:// directory and it's no longer saving the subresources

#

No parser error but the functionality doesn't work now

sharp abyss
rocky willow
#

Without the bundle flag correct?

sharp abyss
rocky willow
#

Will do. Pretty sure it's just 0/OK but I will try it again

#

Confirmed it returns 0/OK

sharp abyss
#

save with the bundle flag

#

when loading

meal_data = ResourceLoader.load(...) as MealData
#

Hopefully this works, I think I've done this before because it looks so familiar lmfao but maybe I'm just hullucinating

rocky willow
#

Same result haha, the parser error

sharp abyss
rocky willow
#

"one" being the meal.gd script?

#

or the meal_data.tres?

#

Well regardless it is loading from user:// yes

sharp abyss
#

So does it detect the file existing?

#

Ah ok

rocky willow
#

Wait sorry

#

It doesn't get that far

#

because of the parser error

sharp abyss
#

Click on the meal_data.tres and what script does it show?

rocky willow
#

Click on it in the filesystem dock?

sharp abyss
#

Yes, and look at its script in the Inspector

rocky willow
#

Yeah it's meal_data.gd

sharp abyss
#

Alright, so the parsing error occurs when you start up the game?

#

Or when you press the load button or whatever you use to trigger it

rocky willow
#

On startup, pretty much immediately

sharp abyss
#

You dont have to, but if you were to delete it would it work?

#

I mean only way to figure out is to delete it lol

rocky willow
#

Delete which thing, meal_data.gd?

sharp abyss
#

.tres

rocky willow
#

The one in res:// or user://?

sharp abyss
#

res

rocky willow
#

Shouldn't change anything as long as the one in user is untouched, let me confirm

#

Same problem

sharp abyss
#

How are you sure that its not reaching the loading part?

rocky willow
#

I guess I'm technically not sure, it just happens immediately upon running the project and doesn't show me a line on which the error occurred, as though it didn't even get that far

sharp abyss
rocky willow
#

Hmm okay

#

Yeah I just did a breakpoint to double check

#

It DOES reach the load line in _ready()

#

Yeah a few more breakpoints confirm that it is happening when I load from user://, sorry. Really thought it was before that since it didn't show me a specific line for the error.

sharp abyss
#

Alright, yeah you're fine. Hmm

#

Instead of using resource loader just do load(...)?

rocky willow
#

Same result :/

#

Even with as MealData on the end

sharp abyss
#

hmmm okay, imma try to setup a quick project and debug it myself, I'll let you know if i figure out anything lol

rocky willow
#

Sounds good, thanks so much for your time so far!

#

Long thread about (AFAICT) this exact issue

#

No resolution yet, last post was a month ago

sharp abyss
#

Yeah I remember coming across this way back when some other person was having issues with this exact issue lol, but idk if there was ever like a good solution to it

rocky willow
#

I was about to upload an MRP I made earlier to save you some time but Discord is not happy with it

#

No .zip uploads here?

sharp abyss
#

Hmmm not sure

#

So for me it works lol

rocky willow
#

Wut

sharp abyss
rocky willow
#

Where's your meal_data.tres?

#

Did you just create one in the Inspector?

sharp abyss
#

Yeah I created it in the inspector

rocky willow
#

Hmm

sharp abyss
#

I created it, saved it with one meal 'Pie' then set it to 'Cake' so it confirms that its being overwritten

rocky willow
#

What about saving during the game with the bundle flag? That's what's causing the problem on my end

#

*Saving with the bundle flag then trying to run the app again

sharp abyss
#

I dont use the bundle flag, but im guessing it will cause the same issues

#

Ill try

rocky willow
#

When does the user://meal_data.tres get created in your project?

sharp abyss
#

Yeah i get the same errors likes you

sharp abyss
rocky willow
#

Ah okay that just wasn't there before

sharp abyss
#

Yeah, but shouldnt matter, because now that its saved there and I just load it it loads fine

rocky willow
#

Right

#

Can you change meal.name, save, then restart the project and have it print the new name?

#

Trying to do that is what led me to using the bundle flag

sharp abyss
#

Yeah, I changed it to pizza, saved it, and loaded it and it gives me Pizza

#

Lol

rocky willow
#

wtf haha

#

You restarted the whole project after saving it?

sharp abyss
#

Yeah lol

rocky willow
#

You changed the name to pizza in the code, not the inspector right?

rocky willow
#

Hmm.

sharp abyss
#

Does your MealData have the init defined?

#

nvm

#

you dont

#

or do you

rocky willow
#

Nope

#

What does your user://meal_data.tres look like after the name change?

sharp abyss
#
[gd_resource type="Resource" script_class="MealData" load_steps=4 format=3]

[ext_resource type="Script" path="res://class/meal.gd" id="1_pjfnr"]
[ext_resource type="Script" path="res://class/meal_data.gd" id="2_1uxhl"]

[sub_resource type="Resource" id="Resource_lquwl"]
script = ExtResource("1_pjfnr")
id = 0
name = "Corndog"
weight = 1.0
metadata/_custom_type_script = "uid://dmg3otbat0krg"

[resource]
script = ExtResource("2_1uxhl")
meals = Array[ExtResource("1_pjfnr")]([SubResource("Resource_lquwl")])
metadata/_custom_type_script = "uid://cb2y3frk80th0"
#

changed it to Corndog in script, to make sure lol

rocky willow
#

Hmm. I have a file meal_0.tres which is a predefined meal. I added that to the meal_data in the inspector instead of creating a meal via the inspector.

#

Maybe that's the problem? Let me try the other way

#

That was it

sharp abyss
#

Interesting lol

rocky willow
#

If I create the meal via the inspector instead of dragging and dropping my meal_0.tres into the inspector, it works

sharp abyss
#

Hmmm okay

rocky willow
#

This is without the bundle flag

#

Weirddddd

#

It seems like the way I did it to start with ought to work, though, no?

#

Like why wouldn't you create some .tres files for specific objects in your game and then potentially store those in another resource? Like an inventory that extends Resource and then items in it that also do...

#

Regardless

#

THANK you

sharp abyss
#

Hmm I'm guessing it didnt seem to "Load" was because it was loading the predefined item and it didnt really "save" a seperate resource of it

rocky willow
#

Yeah that too

sharp abyss
#

So no matter what changes you did to it it didnt load the item you thought you saved, but it loaded the .tres you had dragged onto it

rocky willow
#

Yep

sharp abyss
#

A way to counter that would be to duplicate it, I think

rocky willow
#

That was what started me trying to bundle them

#

Hmm maybe

#

It seems to work the way I need it to now

sharp abyss
#

Well, glad we got something working lol

rocky willow
#

Me too haha

#

Thanks again

grizzled wigeon
#

@rocky willow hey, good job getting it to work...it's still a little weird that it's creating an error for you in a particular way

#

hopefully it won't matter, good luck!