Godot has two ways of creating new classes:
- Create a new script. Each script is a class (optionally named with the
class_name
keyword).
- Use the
class
keyword in a script. It creates an inner class.
Here's an example with two inner classes:
class Person:
var name := ""
var age := 0
class Superhero extends Person:
var power := ""
Inner classes have a limitation compared to creating scripts: you cannot instantiate them from the editor, so you can't use them for nodes and resources that you'd like to interact with in the editor. You can only use them for data containers and helper classes.
But inner classes offer at least two benefits:
- They allow you to keep related classes grouped in a single file. It makes it easy to read and maintain related code.
- Their names are scoped locally, which helps to hide them from the rest of the project.
In this example, I define an inner class with a leading underscore, _Power
to share code between the Flight
and Fire
classes and hide it from the outside world:
class_name Powers:
class _Power:
var name := ""
class Flight extends _Power:
var duration := 10
class Fire extends _Power:
var strength := 5
With a script like this, anywhere in the project, you can call Powers.Flight.new()
to create a new instance of Flight
.
Grouping related classes together like this is something we call "namespacing". It compartmentalizes the code and helps to avoid naming conflicts. Imagine that you want a power named Fire
and that you also need to define the magical element Fire
in your game.
Because the Fire
power is an inner class, you access it as Powers.Fire
, and you can have two inner classes named Fire
in the same project without conflicts.
Another feature of inner classes is to create well-typed objects to store data. Suppose I have a complicated function to get user properties for a web server connection. I could return a dictionary:
func get_connect_properties() -> Dictionary:
return {
"user_name": "AzureDiamond"
"password": "hunter2"
"server_ip": "localhost"
"port": 3000,
"role": "moderator"
}
But then to know what keys are available, I need to look at the function's documentation or the source code. If I try to access a nonexistent key, I won't get any error until the game runs. It's a common source of bugs.
Instead, you can use an inner class to define the data structure:
class ConnectionProperties:
user_name: String
password: String
server_ip: String
port: int
role: String
func get_connect_properties() -> ConnectionProperties:
var properties := ConnectionProperties.new()
properties.user_name = "AzureDiamond"
properties.password = "hunter2"
properties.server_ip = "localhost"
properties.port = 3000
properties.role = "moderator"
return properties
Now, when someone uses get_connect_properties()
, they get a well-typed object with autocompletion and error reporting in the editor.See Also
Related terms in the Glossary