3
\$\begingroup\$

Can I instantiate a custom class during runtime through a String without knowing the value of the string when writing the code? Example:

var className: String
#Code...
#An unknown value will be assigned to the variable className
Instantiate(className)
#Is there a way to instantiate the class name contained in the value of the variable className?
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Are there any limitations on this value (e.g. it's one of n classes written for the purpose of being spawned this way)? Or do you need full generality where literally any class can be chosen? The more information you can share about your intended application, the better we can tailor proposed solutions to that use case. \$\endgroup\$
    – DMGregory
    Dec 23, 2023 at 21:34
  • \$\begingroup\$ Any class of the list I have could be chosen to be instantiate in the scene. These classes represent a game item subtype of another more general class that represent a category of game items. In the game I'm coding a shop and it has five items chosen randomly \$\endgroup\$
    – Gidan
    Dec 25, 2023 at 18:35

1 Answer 1

3
\$\begingroup\$

If it is a built-in class, you can use ClassDB.instantiate(name_of_class).


If you need to pick from a list of scripts, you can create a dictionary, for example:

static var script_map := {
    "Character": Character,
    "OtherClass": OtherClass
}


static func instantiate_class(name_of_class:String) -> Object:
    if script_map.has(name_of_class):
        var script := script_map[name_of_class] as GDScript
        if script != null:
            return script.new()

    return null

If you need to instantiate any class... Well, first let us figure out if it is a built-in class or not. If it is, we can instantiate it with ClassDB. Otherwise we need to get the Script from the name...

static func instantiate_class(name_of_class:String) -> Object:
    if name_of_class.is_empty():
        return null

    var result:Object = null
    if ClassDB.class_exists(name_of_class):
        result = ClassDB.instantiate(name_of_class)
    else:
        var script := get_script_by_name(name_of_class)
        if script is GDScript:
            result = (script as GDScript).new()
        else:
            return null

    return result

To get the script from the name, I'll start by trying to load it, which allows to pass a resource path ("res://..."), otherwise we search for it in project settings:

static func get_script_by_name(name_of_class:String) -> Script:
    if ResourceLoader.exists(name_of_class, "Script"):
        return load(name_of_class) as Script

    for global_class in ProjectSettings.get_global_class_list():
        var found_name_of_class:String = global_class["class"]
        var found_path:String = global_class["path"]
        if found_name_of_class == name_of_class:
            return load(found_path) as Script

    return null

Some optimizations are possible (e.g. loading the list of classes to a Dictionary - perhaps lazily - so we do not need to iterate over the list multiple times).


Notes:

  • As you know you can reference a class by name. If you need a class that does not have a name, you can use preload instead, which is a constant expression (so you can store it in a constant which you use as if it were the class name).
  • If the class _init has parameters, you would have to pass them in new, so the code above would fail.
  • The code above only supports GDScript classes and built-in classes. When you load a Script, you need to cast to the correct subclass to be able to call new on it, so if you need to support other languages you need to extend this code.
  • GDScript nested classes are not Scripts, and casting them to Script was a bug.
\$\endgroup\$
1
  • \$\begingroup\$ Thank you, I learned something new. That's what I was looking for \$\endgroup\$
    – Gidan
    Dec 25, 2023 at 18:37

You must log in to answer this question.

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