Bite-Sized Godot: Using class_name to enable static typing


Sample project

This is going to be a shorter post today, as there’s not that much to the class_name keyword, but it can come in handy quite often so I’m going to go ahead and cover it. I’ll be using some assets provided by Kenney today.

The basics

Simply put, the class_name keyword lets you use static typing in Godot for your custom classes (or scripts, as all scripts ARE classes in Godot). Doing this is as simple as adding class_name MyClass to the top of your script. You may also optionally pass in the path to an image to get a custom icon for the script as well by using the format class_name MyClass, "res://my_cool_icon.png. In the source code for today’s sample project, for instance, the RigidBody2D node representing a ball looks like the following:

extends RigidBody2D
class_name Ball, "res://assets/ball/ball_red_large_alt.png"

# We'll look at this function in just a bit
func do_something():
	print('Did something!')

With that script, the Ball class is now registered in the global namespace, and we can reference it anywhere else in our game. For instance, there is anzArea2D node that should take an action when a ball enters it. The script for it is written as follows:

extends Area2D

func _ready():
	connect("body_entered", self, "on_body_entered")

func on_body_entered(body: PhysicsBody2D):
	if body is Ball:
		print('Ball entered area')
		# ...
		# other code for later
		# ...

You can also get code completion suggestions when a script uses the class_name keyword, just like you would with a built-in type. For instance, the Ball class has a method named do_something(). We can therefore get a suggestion for it when writing some code elsewhere:

And since we passed in the icon parameter for our custom class, we’ll see (after restarting the editor) our custom icon both in the tree and in the scene tab with its icon, making it easy to quickly find.

Other uses

Using the class_name keyword also lets you use the extends keyword to subclass your custom classes instead of referencing a specific path to a script:

extends Ball
class_name BlueBall, "res://assets/ball/ball_blue_large_alt.png"

And you can instance your script at runtime using the format var util = MyCustomUtil.new(). The one thing to note is that this method ONLY instantiates the script, not the scene the script is attached to. Instantiating the ball class (var myBall = Ball.new()), for instance, would give us a script for A rigidbody, but not THE SCENE made previously. So be mindful of that!

Gotchas

There are two main gotchas to be aware of when using class_name:

  • The global namespace
  • Cyclic dependencies

The global namespace

As mentioned above, the class is registered in the global namespace so that it’s available everywhere. This is a generally a good thing as it’s what makes class_name useful. The issue it brings is that every class must have a unique name across your entire application. This isn’t really a problem for high-level classes, such as a Player class, a SkeletonEnemy class, a Knight class, etc, but it can be a problem if you’re doing something like a state machine where each entity may have a state with the same name. All of the previous examples could have a MoveState class, for instance, but that is not allowed in the global state. You could give each state a unique name, such as PlayerMoveState, but really, that’s a situation where you shouldn’t need a global reference to the class anyways.

You also have to make sure not to use a class name that is already in use in Godot, such as “Sprite” or “Timer”. So just think about the implications of throwing a class into the global namespace before doing it.

Cyclic dependencies

These are the bane of every developer using Godot at some point or another. Let’s say we have a couple of utility classes in our game that we want available globally:

  • PolygonMath for doing calculations for a given polygon
  • LineMath for doing calculations on a given line

Even just writing the code below starts spitting out errors in Godot:

# polygon.gd
extends Node
class_name PolygonMath

func intersects_line(line: LineMath):
	pass
# line.gd
extends Node
class_name LineMath

func intersects_polygon(target: PolygonMath):
	pass

res://assets/circular_dependencies_example/polygon.gd:4 - Parse Error: The class “LineMath” couldn’t be fully loaded (script error or cyclic dependency). modules/gdscript/gdscript.cpp:585 - Method failed. Returning: ERR_PARSE_ERROR

The issue? Godot tries to load the PolygonMath class and sees that this class requires the LineMath class, so it goes to load up the LineMath class and sees it requires the PolygonMath class. It doesn’t know how to load the scripts since they both rely on one another and therefore spits out the cyclic dependency error. According to the Godot developers, this will be fixed in Godot 4.0. In the meantime, you’ll just have to be mindful of how and when you use the class_name keyword, and occasionally just accept duck typing instead of strict typing to avoid errors.

Conclusion

And that’s it for this post! Just a short (and hopefully sweet) look at typing your custom classes in Godot. Check out the source code for this post for concrete examples if you’re still uncertain of how this all works together.