When exactly to use class_name?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By frozenberg

So I was banging my head against this for a while and while I got my code to work, I’m still unsure about when to use class_name.

Here is my use case: I have a UI node which is responsible for iterating over an array of Monster nodes and creating HealthBar nodes for each one. I declared class_name HealthBar and tried:

var healthbar = HealthBar.new()

which did not work. Then I tried

const HealthBar = preload("res://HealthBar.tscn") var health_bar = HealthBar.instance()

which did work. So my question is:

Is class_name meant to be used for custom data structures as opposed to full scenes with arbitrary numbers of nodes? And the preload/instance() route is meant for scenes/nodes? I’ve yet to find a succinct explanation of when to use one over the other.

Thanks!

:bust_in_silhouette: Reply From: Zylann

The problem you have is caused by the script referencing itself. Cyclic references are not well supported, and it’s something devs are working to improve in Godot 4.0. You should then be able to do what you said.

const HealthBar = preload("res://HealthBar.tscn") 
...
var health_bar = HealthBar.instance()

I think this code works because it does not asks the resource loader to load your script while it is already in the process of loading it.

An alternative is to do this:

var healthbar = load("health_bar.gd").new()

Sorry, I don’t think I was clear. This was not cyclic, I was calling HealthBar.new() from the UI.gd script.

I’m trying to avoid using strings with filenames, but I’m not sure if it’s avoidable. I really don’t like having to call preload("res://HealthBar.tscn") since there’s no guarantee I don’t move that file to another directory at some point.

frozenberg | 2020-03-11 17:07

var healthbar = HealthBar.new()which did not work.

What do you mean by not working then? I’m not aware of another case where this wouldn’t work.

In Godot, the only ways to not write the path (or relative path) to an asset inside a script is to use an export variable and assign it in the inspector, or use class_name in the case of scripts. However, if that causes a cyclic reference, then you have no choice but to write it in the script using load.

Zylann | 2020-03-11 19:18

My HealthBar script:

extends CanvasLayer

class_name HealthBar

func _ready():
    print("Health bar is ready!")

This is my UI script:

extends Control

var  X_OFFSET : int = 30

func add_monsters(monsters:Array):
    for monster in monsters:
        print("Adding this monster to the UI: %s" % monster.get_name())
    
        var health_bar := HealthBar.new()
        health_bar.offset = monster.position + Vector2(X_OFFSET, 0)
        add_child(health_bar)

and none of my HealthBar scenes get instanced/drawn.

frozenberg | 2020-03-11 20:20

Note that a script is not the same as a scene. When you create an instance of a script (with new()), it creates only the object this script extends. This is because the same script can be on multiple scenes, and scripts don’t necessarily extend nodes.

If what you want is to instanciate a health bar scene dynamically, then class_name is not what you are after. You have to either use an export to assign the scene in the inspector, or load it by path.

Zylann | 2020-03-11 20:39

Thanks, this is the confirmation I was looking for. class_name/new() is to be used for scripts/tools/custom data structures, while preload/load/instance() should be used for scenes with arbitrary node structures.

frozenberg | 2020-03-11 20:46