When You Should Use class_name in Godot 3 and Why

In this quick tip I’ll explain what class_name in Godot 3.1+ does and when and why you should use it.

In Godot (talking mainly about GDscript) all scripts extend one of the base classes Object/Node with further extensions below that. Most scripts are fine being just an extension of one of the core classes however it can be useful to have a bit more control over identifying and instancing scripts with custom behaviour. Think of it this way. If you’re making a 2D platformer and one of your enemies just walks along the platform then back again you can probably leave your class as it is, its behaviour can be fairly safely self-contained, collision layers will probably be enough for any basic interaction checking, for example: was the collider in the player collision layer? Then deal damage to the player, simple.

extends Node2D # Enemy that just walks back and forth on a platform

# On enemy collision layer, player collision mask

func _physics_process(delta):
    var direction = get_direction() # Will check for position and change direction if needed
    move_and_slide(vel * direction, Vector2.UP)

func _on_Area_Body_entered(body): # Only bodies in the player collision layer can be detected, must be a player, do hurt
    if body.has_method("take_damage"): # basic sanity checking
        body.take_damage(1)

But now what if you need to identify the type of this enemy from another script, such as the player script, how will you do it? You could give the enemy class a var enemy_type field and assign it values such as “walker”, “seeker” etc. Upon player collision with the enemy you can just check the enemy_type and retrieve the relevant data. This will work well enough if you can guarantee that you’re interacting with an enemy but what if you collide with a wall or a trap which has completely different functionality to an enemy and therefore doesn’t use the same base script?

Well, one option is to check the script type of the object you’ve collided with.

if collider is preload(“res://Enemy.gd”): # Do the thing to the enemy

This is a fine solution but it can lead to lots of preloads around your code or moving everything up into an autoload where you have a class full of nothing but preloaded scripts to check objects against (see code below). As such it’s better left for really small prototype projects or one off cases where other solutions might be too heavy to engineer such as a mod or achievement that needs to check something very specific.

extends Node # Autoload script example

const Player = preload("res://Player.gd")
const BaseEnemy = preload("res://Enemy.gd")
const Trap = preload("res://Trap.gd")
const Pickup = preload("res://Pickup.gd")

As an alternative Godot has the group system. You can assign a node to any number of groups and therefore you could identify nodes by the group they’re in. All enemies would belong to the “enemy” group. All walkers to the “walker” group etc. You can then check if the colliding object is_in_group(<group_name>). You can find all objects in the SceneTree belonging to a single group, perhaps to connect signals to them by using get_tree().get_nodes_in_group(). This can work great for simple projects and is often my preferred way to do things in Godot. It comes with some pitfalls, however. There is no real sanity checking. For example group names are case sensitive: Enemy is not the same as enemy or EneMY.

Because you can add any node to any group, you can, and might, accidentally add the enemy’s collision object to the enemy group and all of a sudden if you’re expecting all enemies to be of type KinematicBody they’re now KinematicBodies and CollisionShapes and you’ve got a confusing mess on your hands (yes I’ve done this). It’s also quite hard to keep track of which group names you have already used and which you haven’t, leading to potential mix ups as in the point before. Was it enemy, Enemy or Walking_Enemy? Not to mention typos in your code, one misplaced letter or capitalisation and suddenly your otherwise perfectly working code will mis-identify your enemy every time.

So how do we fix this? The answer is class_name. Class_name allows you to assign a custom class identifier (that still extends one of the core Godot classes) . It is registered at a global level within the project.

extends KinematicBody2D
class_name Enemy

var hp = 10

func take_damage(amt):
    hp -= amt

You can then check if an object/node is of type as such:

func get_target(object: Node):
    if object is Enemy:
        print(“found an enemy, geeet’im!”)

This is very powerful as it bypasses both the need to check scripts with preloads (because all custom class_name scripts are autoloaded when the engine starts) and gives sanity checking where groups would not. Because the class_names are loaded at global level it allows for proper type checking and field/function hinting within the editor.

So should we use this for everything then? I’d say no. I’m sure there are people who make class_names for every one of their scripts/classes or the vast majority of them and that’s fine but I would argue that it overcomplicates things. It also clutters up the editor, as each class_name can be instatiated by the new node dialog and you can end up with hundreds/thousands of new node types, all nested below the built-in type they extend. Most of which you probably don’t need to add visually within the editor.

So when should you use class_name? I would say use it for objects where you definitely want to be able to create an instance of that specific object without assigning a custom script to it and for objects where you know you will definitely need to identify if it’s a specific type. For example say you have a UI element that has a custom script on it but it sits there doing its own thing not really interacting with anything else in your scene, it’s probably not worth adding a class_name to the script.

Now say you want to create a Tree for an RPG shop system. You can purchase weapons, armour or potions, each belonging in their own inventory slots. You attach the desired Weapon/Armour/Potion instance as the TreeItem metadata. The tree checks for a row being selected. In order to determine in which inventory slot the purchased item belongs you want to check the type of the metadata object the player just purchased. You could use groups or checking the script type but with class_name you just check for the metadata objects class_name as you would with any built-in class, much simpler. This is an overly complicated example, but hopefully by being so it gets the point across and it’s not too far from a solution I’ve had to implement for the current project on which I’m working.

An additional note is that you can get into a circlular reference nightmare using class_name. Say you have two scripts: class_name Alice and class_name Bob. If Bob references fields from inside Alice and Alice references fields then Godot will throw a cyclical reference error.

Conclusion

Groups – useful for simple projects

Checking against script resource – useful for simple projects and one-off edgecase checks.

class_name – Can be used everywhere but better left to objects/classes that need frequent instancing or type checking from code.

Support This Site

If you enjoyed this content consider giving a tip on Ko-fi to help us keep producing content alongside our products.

You may also like...