While hacking away on my latest game PoC (A Thousand Tonks), I decided to take a look into how I can use inheritance in Godot. This is just a short article about what I found interesting.
What class inheritance is
For a more extensive explanation of inheritance, take a look at en.wikipedia.org, but here are the basics.
Inheritance is the mechanism of basing an object or class upon another object (prototype-based inheritance) or class (class-based inheritance), retaining similar implementation.
*
Or in other words: class inheritance in object-oriented programming allows a new class (subclass) to inherit properties and behaviors from an existing class (superclass), enabling code reuse and creating a hierarchy of classes with shared characteristics. Subclasses can extend the functionality of the superclass by adding new features or overriding existing ones while maintaining the core attributes and methods inherited from the superclass.
You'd use class inheritance to avoid repeating code by letting new classes borrow features from existing ones, making it easier to create similar but slightly different types of objects without starting from scratch.
Inheritance in GDScript
In GDScript, use the keyword extends
to inherit from a specific class:
extends Node2D # Inherits from the Global Node2D class
class_name Vehicle
extends Vehicle # Inherits from the Vehicle class
class_name Car
In the above code, Vehicle becomes the superclass and Car is the subclass that inherits class variables and functions from it's superclass.
It is possible to create multiple subclasses that all inherits from the same superclass, but not inherit from multiple superclasses.
The subclass can override functions from the superclass by simply writing the same function name.
It is also possible to call a function in the superclass from the subclass by using the super
keyword.
When writing this (February 2024), I couldn't find any documentation indicating that Interfaces or Abstract classes exist in GDScript at present. Additionally, it appears that private and protected variables have not been implemented.
Enemy example
With that basic explanation out of the way, I will show a small part of what I used in my previous Godot project.
In that game, I was going to spawn a number of enemies that all had the same exact variables (such as health) and functions (such as move), but different values and graphics. To acomplish this, I created a superclass Enemy in a separate file enemy.gd
.
extends Node2D # Inherits from the Global Node2D class
class_name Enemy # Superclass
const MAX_HEALTH:int = 200 # Constant value
var _health:int = 100 # "Private" variable (Privacy is not enforced though)
var health:int = _health: # Getter / Setter
get: return _health
set(val):
_health = clamp(val, 0, MAX_HEALTH)
func toString() -> void: # Simple function to print current health
print("Print from Enemy, health: %s" %[health])
The class contains a lot more than this, but I removed everything except the very basic stuff needed to explain inheritance.
Next, I created a new scene enemy_small.tscn
with a Sprite2D node for my enemy sprite. Then I attached a script enemy_small.gd
to the scene.
extends Enemy # Inherits from the Enemy class
class_name EnemySmall # Subclass
func _init() -> void: # "Constructor"
health = 110 # Set the value of superclass variable when object is created
func toString() -> void: # Override function ToString() from superclass
print("Print from EnemySmall, health: %s" %[health])
func toString2() -> void: # Function specific to subclass, but calls function from superclass
super.toString()
To test that everything worked, I added some temporary code in the main.gd
file.
# Create new object of superclass Enemy (Not something that will be used in the game)
var newEnemy:Enemy = Enemy.new()
newEnemy.toString() # Call function in superclass
# Create new object of subclass EnemySmall that inherit variables and functions from superclass Enemy
var newEnemySmall:EnemySmall = EnemySmall.new()
newEnemySmall.toString() # Call overridden function in subclass
newEnemySmall.toString2() # Call function specific to subclass, but that function calls superclass function
After running the code, this is the output:
Print from Enemy, health: 100
Print from EnemySmall, health: 110
Print from Enemy, health: 110
When to use it
I've been working with Object-Oriented languages for some time, and it makes sense to me to utilize class inheritance when shared code will be used in multiple parts of the project. While there are alternative approaches like composition to achieve similar effects, inheritance feels intuitive to me. In fact, based on discussions I've seen on various forums, a blend of inheritance and composition appears to be the most common approach employed by developers.
* https://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming)