1
\$\begingroup\$

I have this specific Resource script:

class_name MyDataStructure
extends Resource

@export var title: String = "Default Title"
@export var medals_per_world = {
    "world1": {
        "bronze": 0,
        "silver": 0,
        "gold": 0,
    },
    # Etc.
}

func _init(_some_title: String):
    title = _some_title

And I'm trying to save/load it with these methods:

const SAVE_DATA_PATH := "user://save_game.tres"
var _data: MyDataStructure

func _on_button_save_pressed():
    ResourceSaver.save(_data, SAVE_DATA_PATH)

func _on_button_load_pressed():
    if not ResourceLoader.exists(SAVE_DATA_PATH):
        return
    _data = load(SAVE_DATA_PATH)

Saving works fine and gives me this type of file:

[gd_resource type="Resource" script_class="MyDataStructure" load_steps=2 format=3]

[ext_resource type="Script" path="res://my_data_structure.gd" id="1_e2e3s"]

[resource]
script = ExtResource("1_e2e3s")
title = "Foo title"
medals_per_world = {
"world1": {
"bronze": 3,
"gold": 2,
"silver": 4
}
}

Loading it fails with this error:

E 0:00:07:0699   root.gd:31 @ _on_button_load_pressed(): Error constructing a GDScriptInstance.
  <C++ Error>    Method/function failed. Returning: nullptr
  <C++ Source>   modules/gdscript/gdscript.cpp:183 @ _create_instance()
  <Stack Trace>  root.gd:31 @ _on_button_load_pressed()

I recently introduced the nested dictionary medals_per_world so perhaps that's causing me issues?

Either way, how can I properly load such a file with the ResourceLoader? The fact that it saves properly suggests that loading it should also be possible? Or is my only workaround/solution to define the entire data structure with dto Resource scripts for each nested level if the dictionary is the cause?

I'm working with Godot 4 so can use any recently released GDScript feature if needed.

\$\endgroup\$

1 Answer 1

5
\$\begingroup\$

Godot will try to call your _init method while loading the Resource. But it can't because it has a non-optional parameter (Godot has no means to figure out what to pass).

Thus, a possible solution is to make it optional:

func _init(_some_title: String = ""):
    title = _some_title

Alternatively, you could remove it entirely.


You could also take control of how the Resource is loaded by implementing a ResourceFormatLoader which you register with ResourceLoader.add_resource_format_loader.

Alternatively - in particular if you need to be able to load the Resource from the editor - you could be interested in implementing EditorImportPlugin which you register from an EditorPlugin using EditorPlugin.add_import_plugin.

Then your implementation can use your _init correctly.


As an alternative to adding parameters to _init you can add static methods to build your type※:

static func with_title(_some_title:String) -> MyDataStructure:
    var result = MyDataStructure.new()
    result.title = _some_title
    return result

※: Doing this in Godot 3.x would have required a helper class, because Godot would not resolve the name of the class while parsing the class.

That might also improve readability of your code. For example, anybody unfamiliar with your code, and without looking up the definition, could guess that argument is a title:

var instance := MyDataStructure.with_title("BLAH")

By the way: Godot does not support named arguments.

\$\endgroup\$
1
  • \$\begingroup\$ Cheers! Classic mistake by me, I changed two things at once (nested dictionary and another _init parameter) and blamed one of them for the error. Incorrectly, it seems. Heck, even in my repro I neatly repeated the double mistake. 😅 The constructor argument lacking a default was indeed the issue! \$\endgroup\$
    – Jeroen
    Apr 10, 2023 at 19:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .