Function in custom resources not working

Godot Version

4.5

Question

I created a custom resource that contains a non-static function, and I gave it a class_name. I should be able to call that function from other scripts that use the resource, but there seems to be a bug in Godot that doesn’t recognize the custom class. Everytime I call the function I get the following error:

Invalid call. Nonexistent function ‘loadScene’ in base ‘Resource’.

It doesn’t seem to recognize the resource’s custom class, it still just treats it as simply Resource.

Here’s the resource’s basic code:

class_name CustomResource
extends Resource

@export var scene: PackedScene

func loadScene() -> Node3D:
	return scene.instantiate()

Maybe I’m doing something wrong. Am I missing another step when calling functions inside resources?

Can you share your code calling this custom resource’s function? How do you create the custom resource that is being used?

This is how I call the resource’s function. The following is in a regular Node3D, with an exported variable that takes the CustomResource I created:


@export var item: CustomResource


func addItem() -> void:
    var itemScene = item.loadScene()
    add_child(itemScene)

I create the resource through “New Resource” in the files dock, then I drag it to the exported variable in the relevant nodes.

Since “item” is of type CustomResource, I should be able to call its custom functions, but it gets treated as just Resource by the engine

After creating this resource did you fill in the item property in the inspector? Are you sure you made a “CustomResource” or just created a “Resource”? What script is attached to your Resource?

1 Like

Yes, the item property is filled in.
The script attached is the one that contains the function, and starts with class_name CustomResource, which I thought was enough to create a custom resource.

To create the resource I right click in the file dock, go to New Resource, and select my custom resource which now appears in the menu.

I don’t know what else I’m supposed to do.

Can you show us its actual code, instead of a stripped down or generalized version?

I suspect there’s something else in that file that you’re doing that’s causing the problem.

I will tell you I use a lot of complex custom resources, and I never have the problem you are having.

This may be your problem. It should be saying New CustomResource. I suspect your resource is of type Resource and not of type CustomResource.

Again, I think it would help not to obfuscate your code in this post. And if CustomResource is actually the name of your class, consider making it more descriptive of what’s inside it. It will help you down the road when you have multiple custom resources.

This is a typical “symptom” of circular resource dependency, but hard to tell for certain without seeing the whole thing.

1 Like

Yeah I guess it’s better to just show the whole code. The _init doesn’t really serve a purpose here:

class_name Weapon
extends Resource

@export var scene: PackedScene
@export var name: StringName
@export var damage: int

func _init(_scene = null, _name = "", _damage := 0) -> void:
	scene = _scene
	damage = _damage
	name = _name

func loadWeapon() -> Node3D:
	return scene.instantiate()

This is how I create a new one:


And as expected it shows up in the menu:

Don’t think it matters but it does say Resource when I hover over the file instead of Weapon:

Here’s how it shows in the inspector:

How do you know that this particular resource object is causing the error?

Here’s an example of my Song resource. When the export variable is empty, it says “New” then under that “Song” with the icon I have loaded in the script. See the screen shot below.

The fact that yours does not indicates the system isn’t picking it up.

In this case, I think there are two things that might be tripping you up. First, you are using a StringName for an @export variable. While technically you can do that, when creating it in the editor, you will always be using a String. So you’re not really saving anything. I do not think this is causing your problem, but it might cause you problems in the future. It’s a premature optimization.

Second, I believe it is specifically your _init() function that is causing the problem. First, it serves little to no purpose. You cannot instantiate a Resource the way you can a Node. You can use new, and pass values in, but to do so, you need to be very careful. If you want to initialize values in the constructor using _init(), typically it only works if they are straight up vars. @export and @onready variables may not work because init happens before _ready() and so nothing else exists in the object yet.

I suspect if you remove the code, your Weapon resource will start working. Then if you really have a reason for the _init(), you can debug making that work. I recommend starting with this code and seeing if it works:

class_name Weapon extends Resource

@export var scene: PackedScene
@export var name: String
@export var damage: int

func loadWeapon() -> Node3D:
	return scene.instantiate()

Here’s how it works at runtime. When a resource object is created either by loading a tres or via new(), the constructor _init() is first always called. For new()'d resources, nothing else happens. For loaded resources, all @export vars will be re-initialized if they have non-default values (aka you messed with them in the inspector), effectively overriding their initializations done by _init(). For @export vars whose values are keept at default, the results of _init() won’t be overridden. If _init() has arguments, and there are no default values provided for all of them, loading a resource from tres will throw an error and fail, because the engine expects to be able to call the constructor with no arguments.

@seabask Some puzzle pieces are still missing here. Can you make a minimal reproduction project?

1 Like

Unfortunately removing the _init doesn’t work. Also, the export variable does work like yours, see here in this exported dictionary:

Also just tried creating a resource in code with new() and for some reason it works!!

var weapon = Weapon.new()
weapon.scene = preload("uid://b124v4ijo0daq")
handL.add_child(weapon.loadWeapon())

But this should not be necessary at all, I’m starting to suspect it’s a bug in Godot

This is not calling loadWeapon().

Whoops, just fixed it. Same function, just renamed it

Is there something you’re not telling us? :smiley:

Can you reproduce the problem in a fresh project?

Yknow what, as a matter of fact, I realized I was doing something different, and I may have fixed it in a new test project :slight_smile:

I omitted this little function in my custom resource, the apparent culprit of it all:

enum WEAPONS {SHURIKEN, BOMB}

const WEAPONS_RESOURCES: Dictionary[WEAPONS, Weapon] = {
	WEAPONS.SHURIKEN: preload("uid://cib828ujr1x4f")
}


static func getWeapon(weapon: WEAPONS) -> Weapon:
	return WEAPONS_RESOURCES[weapon]

This function is what I was using to get the resource during runtime, not from an exported variable. For some reason this function is spitting out a plain Resource instead of a Weapon.

Next time I won’t try to simplify my code when asking for help lol

This does beg the question of why the function is not returning Weapon as instructed, tho

1 Like

Circular dependency, as already foreseen by the oracle.

2 Likes

Yep. So a custom resource cannot return its own type?

It can. It’s not strictly about type. You’re referencing a specific object, that in turn references itself again. Turtles all the way down.

You may try making that dict not const and use load instead of preload. Making the dict static may work as well. Looks like a bad design choice though. Maybe it’s time to rethink the whole thing.

3 Likes

To build on normalized’s response preload and @exporting specifically can create a circular dependency, both load the selected resource with the script, Godot attempts to package anything preloaded together with the object. But now your weapons are preloading themselves, sadly Godot resolves this by silently removing the circular dependency after one loop.

Using load will attempt to load the object as soon as possible without a dependency, but I’d recommend storing the UID strings and loading as the weapons are needed.

enum WEAPONS {SHURIKEN, BOMB}

const WEAPONS_RESOURCES: Dictionary[WEAPONS, Weapon] = {
	WEAPONS.SHURIKEN: "uid://cib828ujr1x4f"
}


static func getWeapon(weapon: WEAPONS) -> Weapon:
	return load(WEAPONS_RESOURCES[weapon])
4 Likes