Search code examples
godotgdscript

Check if an object is of a class given the class name in string?


Let's say I have an object and I'm given a class name in string
How do I check if the object is of that class?

extends Node2D

func _ready():
    var given_class_name="KinematicBody2D"
    if($Obj is given_class_name):
       ...

Solution

  • You can use get_class() to find the class of an Object, ignoring Scripts. Which in this case should be sufficient:

    var given_class_name="KinematicBody2D"
    prints($Obj.get_class() == given_class_name)
    

    But that is not every case, is it? Perhaps you want to check a base class, then this does not work:

    var given_class_name="PhysicsBody2D"
    prints($Obj.get_class() == given_class_name)
    

    Instead we can use is_class:

    var given_class_name="PhysicsBody2D"
    prints($Obj.is_class(given_class_name))
    

    Or we can ask ClassDB:

    var given_class_name="PhysicsBody2D"
    var obj_class := $Obj.get_class()
    prints(
        obj_class == given_class_name
        or ClassDB.is_parent_class(given_class_name, obj_class)
    )
    

    But what about Script classes?

    Perhaps you remember that we can find the class name of a script by looking at _global_script_classes, something like this:

    var obj_class_name := $Obj.get_class()
    var script:Script = $Obj.get_script()
    if script != null:
        obj_class_name = script.resource_path
        for x in ProjectSettings.get_setting("_global_script_classes"):
            if str(x["path"]) == script.resource_path:
                script_class_name = str(x["class"])
    

    The above code will set obj_class_name to either:

    • The class name of the script of $Obj, if it has any.
    • The path of the script of $Obj, if it has any.
    • The build-in class of of $Obj.

    However, since we want to check if it is of a given type that is not sufficient. Since we also need to check Script inheritance. As it turn out it is more convenient to figure out what kind of class is the name we are given and work from there:

    func is_instance_of(obj:Object, given_class_name:String) -> bool:
        if ClassDB.class_exists(given_class_name):
            # We have a build in class
            return obj.is_class(given_class_name)
        else:
            # We don't have a build in class
            # It must be a Script class
            # Try to find the Script
            var class_script:Script
            for x in ProjectSettings.get_setting("_global_script_classes"):
                if str(x["class"]) == obj_class_name:
                    class_script = load(str(x["path"]))
                    break
    
            if class_script == null:
                # Unknown class
                return false
    
            # Get the script of the object and try to match it
            var check_script := obj.get_script()
            while check_script != null:
                if check_script == class_script:
                    return true
    
                check_script = check_script.get_base_script()
    
            # Match not found
            return false
    

    We are almost there. As you know, in GDScript you can extend using a script path (see Classes). So we might consider resource paths of script valid class names... We can support that too:

    func is_instance_of(obj:Object, given_class_name:String) -> bool:
        if ClassDB.class_exists(given_class_name):
            # We have a build in class
            return obj.is_class(given_class_name)
        else:
            # We don't have a build in class
            # It must be a script class
            var class_script:Script
            # Assume it is a script path and try to load it
            if ResourceLoader.exists(given_class_name):
                class_script = load(given_class_name) as Script
    
            if class_script == null:
                # Assume it is a class name and try to find it
                for x in ProjectSettings.get_setting("_global_script_classes"):
                    if str(x["class"]) == obj_class_name:
                        class_script = load(str(x["path"]))
                        break
    
            if class_script == null:
                # Unknown class
                return false
    
            # Get the script of the object and try to match it
            var check_script := obj.get_script()
            while check_script != null:
                if check_script == class_script:
                    return true
    
                check_script = check_script.get_base_script()
    
            # Match not found
            return false
    

    You could go the extra mile and support a Variant instead of an Object. Then you can check its type with typeof... If it is TYPE_OBJECT then you proceed to check for a class. If it isn't, you would need a giant switch to check the names of build it types (similar to the one here, but with the type names).


    For anybody trying to do this in GODOT 4: ProjectSettings.get_setting("_global_script_classes") does not work anymore. Instead use ProjectSettings.get_global_class_list().