2

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):
       ...
cak3_lover
  • 1,440
  • 5
  • 26
  • I also found [this](https://godotengine.org/qa/24745/how-to-check-type-of-a-custom-class) – cak3_lover May 25 '23 at 06:09
  • The methods [`get_type`](https://docs.godotengine.org/en/2.1/classes/class_object.html#class-object-get-type) and [`is_type`](https://docs.godotengine.org/en/2.1/classes/class_object.html#class-object-is-type) are part of Godot 2. By the way, they just changed `godotengine.org/qa` to `ask.godotengine.org`, this is the fixed link: [How to check type of a custom Class?](https://ask.godotengine.org/24745/how-to-check-type-of-a-custom-class). – Theraot May 25 '23 at 13:36
  • @Theraot I meant the answer involving `if my_object is preload("custom_class.gd"):` but thank you for fixing the link! – cak3_lover May 28 '23 at 17:07

1 Answers1

5

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().

Theraot
  • 31,890
  • 5
  • 57
  • 86
  • How does the [is keyword](https://docs.godotengine.org/en/latest/tutorials/scripting/gdscript/gdscript_basics.html#inheritance) compare to this answer? The docs say it can be used "to check inheritance". Does it do less than your code? Edit: Ah wait, the difference is that turning a string into the target class is the hard part, right? – lucidbrot Aug 19 '23 at 07:00
  • 1
    @lucidbrot Yes. This answer is for the case where you only have a `String` that has the name of the class. Which would be the case when you only get it in runtime. – Theraot Aug 19 '23 at 14:19