How can I pass arguments to a Resource's _init()?

Godot Version

4.3.stable

Question

In my game, I have a custom resource which holds a field of type Node. I want to set this field through the editor. Since the engine doesn’t allow Resources to export fields of type Node, I want to pass the value as a parameter of the _init() function, like so:

class_name MyResource extends Resource

var my_field: Node

func _init(init_my_field: Node) -> void:
    my_field = init_my_field

I create the Resource at the top of my main scene script like this:

var my_resource: MyResource = preload("res://resources/my_resource.tres").new(%MyNode)

The error I get is Invalid call. Nonexistent function 'new' in base 'Resource'. A pretty clear error message. But since Resource inherits from Object, I don’t understand how it can’t have a new() function. What am I doing wrong here?

I also tried to call _init() directly, in which case something funny happens:
The error log tells me 'Resource(my_resource.gd)::_init': Method expected 1 arguments, but called with 0.
The Stack Trace on the other hand tells me Too many arguments for "_init()" call. Expected at most 0 but received 1.

I suppose you’re not supposed to call _init() directly anyway, but I still find it funny that the error messages can’t agree on what the actual error is.

Anyways, how do I do this?

I’m not sure if it’s the expected way, but what I did when I needed that kind of thing was to make a separate function (maybe call it create() or something) which takes the arguments you want to supply:

func create_and_init(thing: String, arg: Node) -> Node:
    var res = preload(thing)
    res.some_var = arg
    return res

When you preload() a Resource it will automatically call _init() with no arguments. new() it’s not working because preload() preloads the Resource itself not the class. new() only works on scripts or classes.

You can add default values to the _init() parameters to avoid it failing to preload()

class_name MyResource extends Resource

var field:Node

func _init(init_field:Node = null) -> void:
	field = init_field

If you want to create a new MyResource you can do it like:

var my_resource = MyResource.new(node)
1 Like

So I just came back to this code and unfortunately this doesn’t solve my problem after all (after 4 hours of trying and failing I wasn’t able to spot this last time).

The problem is: I’m not instantiating the “base” resource, but a version of it with some preset fields I saved to file.

So my Resource code looks more like this:

class_name MyResource extends Resource

@export var preset_field: int
var dynamic_field:Node

func _init(init_dynamic_field:Node = null) -> void:
	dynamic_field = init_dynamic_field

Now, I have my_resource_1.tres with preset_field set to 1, and my_resource_2.tres with preset_field set to 2. What I want to do is get an instance of both of those resources while passing a Node to them. Is that still possible?

No. load() or preload() already instance the resource itself.

You can do what @hexgrid said. Maybe make it static so you can do it in one line MyResource.load_and_init("res://my_resource.tres", other_node) or create a method that sets the node and returns the resource like:

class_name MyResource extends Resource

@export var value:int
var other_node:Node

func with_node(other:Node) -> MyResource:
	other_node = node
	return self

And then you can do: var my_resource:MyResource = preload("res://my_resource.tres").with_node(my_node)

if you still want to do it in one line for whatever reason.

1 Like