Godot 4.3 Understanding Parent and Child Relationships Through an Entity System
In our previous section, we introduced custom classes in Godot by moving the get_properties()
function into its own dedicated class, ObjectAnalyzer
. This helped us keep main.gd
clean while improving code organization.
Now, we’re ready to take a big step forward: understanding parent and child relationships in Godot. This will serve as the foundation for inheritance — a key concept in game development that allows us to structure our projects efficiently.
Rather than having separate, unrelated objects like a player and NPCs, we’ll create a general Entity
class that all characters can inherit from. This will allow us to share common behavior across different types of game objects.
What Are Parent and Child Relationships in Godot?
In Godot, parent-child relationships are crucial for structuring objects in both the scene tree and codebase .
- A parent node controls its children.
- If a parent node moves, its children move with it.
- If a parent node is deleted, its children are also deleted.
- A child node inherits from its parent but can also have unique behaviors.
- A
Player
and anNPC
might both move, but the player might jump while the NPC might wander around .
- A
This idea extends beyond the scene tree and applies to scripts through inheritance .
Step 1: Creating a General Entity
Class
Instead of treating the player and NPCs as completely separate objects, let’s first create a generalized Entity
class. This will serve as the parent of all characters.
1.1 What Is a Class?
Before we dive into creating Entity
, let’s break down what a class is and why we use it.
Understanding Classes in GDScript
A class is a blueprint that defines how an object should behave. Every object that belongs to a class will share the same properties (data values) and methods (functions that perform actions). However, each object instance can still hold unique values.
Let’s look at a simple example of a class definition:
class_name Car # Defines a reusable class
var speed: int = 0 # Property to track speed
func drive():
print("The car is moving at ", speed, " km/h")
class_name Car
→ This tells Godot that we are defining a new class namedCar
.var speed
→ This defines a property calledspeed
that tracks how fast the car is moving.func drive()
→ This is a method that prints how fast the car is moving.
Each time we create a new instance of Car
, it will have its own speed
value, but it will still use the shared drive()
method.
1.2 Defining Our Entity
Class
Now that we understand how classes work, let’s create a base class called Entity
. This class will define shared properties and behaviors for all game characters.
Steps to Create Entity
- Inside Visual Studio Code , create a new script file called
entity.gd
. - Write the following code:
extends Node2D
class_name Entity # Defines the Entity class
var health: int = 100
var speed: float = 50.0
func move(direction: Vector2):
position += direction * speed * get_process_delta_time()
print(name, " moved to ", position)
1.3 Understanding New Concepts in Entity
Now that we’ve written the Entity
class, let’s go over each new concept in detail.
New Function: move()
The move()
function allows the entity to move in a given direction based on:
- The direction it should move.
- The speed at which it moves.
- The frame time to ensure smooth movement.
func move(direction: Vector2):
position += direction * speed * get_process_delta_time()
Let’s break this down further.
1. direction: Vector2
A Vector2
represents 2D movement with x
and y
values.
Direction | Vector2 Representation |
---|---|
Right | Vector2(1, 0) |
Left | Vector2(-1, 0) |
Up | Vector2(0, -1) |
Down | Vector2(0, 1) |
When we multiply direction
by speed
, we scale the movement based on how fast the entity should move.
2. speed: float = 50.0
The speed
variable controls how fast the entity moves.
- Higher speed → Moves faster.
- Lower speed → Moves slower.
3. get_process_delta_time()
This is a global function in Godot that returns the time elapsed since the last frame.
Why is this important?
- If the game runs at 60 FPS , then
get_process_delta_time()
returns 1/60 = 0.0166 . - If the game runs at 30 FPS , then
get_process_delta_time()
returns 1/30 = 0.0333 .
💡 Why use this function?
Without get_process_delta_time()
, movement would depend on frame rate , meaning:
- Objects move faster on lower FPS systems .
- Objects move slower on higher FPS systems .
Multiplying by get_process_delta_time()
ensures consistent movement across all frame rates.
1.4 A Math Scenario to Understand Movement
Now that we’ve introduced movement calculations, let’s test your understanding with some applied math.
Scenario: Moving an Entity
We have an entity with the following properties:
speed = 50.0
- The entity moves in the direction
Vector2(1, 0)
(Right). - The game runs at 60 FPS , so
get_process_delta_time()
returns0.0166
.
Question 1: How much does the entity move per frame?
We use the formula:
movement = direction * speed * get_process_delta_time()
Substituting values:
movement = Vector2(1, 0) * 50.0 * 0.0166
- What is the resulting movement per frame?
- How does changing
speed
affect this?
Question 2: How far does the entity move in one second?
- Since we are running at 60 FPS , the entity moves 60 times per second .
- Multiply your answer from Question 1 by 60 .
- What is the total movement in one second ?
Question 3: What happens if we double the speed?
- If
speed = 100.0
, how much does the entity move per frame ? - How much does it move per second now?
Take a moment to calculate these answers . Understanding movement math is critical for working with game physics.
1.5 Summary of Key Takeaways
- Classes define reusable blueprints for objects.
- Inheritance allows multiple objects to share the same base behaviors .
- Parent-child relationships structure objects in both the scene tree and code.
- Movement is calculated based on speed, direction, and frame time .
get_process_delta_time()
ensures consistent movement across different FPS rates .
Next Steps
Now that we have a solid understanding of: ✅ Classes
✅ Inheritance
✅ Movement
We can move forward with creating Player and NPC classes that inherit from Entity
. Let’s do that next! 🚀
Step 2: Creating Player and NPCs as Child Classes
Now that we have a general Entity
class , we can create a Player and NPC class that inherit from it.
2.1 Creating player.gd
- Create a new file called
player.gd
. - Write the following code:
extends Entity
class_name Player
func jump():
print(name, " jumped!")
2.2 Creating npc.gd
- Create a new file called
npc.gd
. - Write the following code:
extends Entity
class_name NPC
func wander():
print(name, " is wandering...")
What’s Happening Here?
Player
andNPC
both extendEntity
.- This means they inherit
Entity
’s properties (health
,speed
) and methods (move()
). - Each class adds its own unique method:
Player
can jump() .NPC
can wander() .
Step 3: Using Our Entity System in main.gd
Now, let’s create actual instances of Player
and NPC
in main.gd
.
- Open
main.gd
and replace its contents with:
extends Node2D
func _ready():
var player = Player.new()
player.name = "Hero"
add_child(player)
var npc = NPC.new()
npc.name = "Crow"
add_child(npc)
player.move(Vector2(1, 0)) # Move the player
player.jump() # Player-specific action
npc.move(Vector2(-1, 0)) # Move the NPC
npc.wander() # NPC-specific action
Step 4: Running the Code
When we run the scene, the following output should appear:
Hero moved to (50, 0)
Hero jumped!
Crow moved to (-50, 0)
Crow is wandering...
This shows that:
- Both
Player
andNPC
usemove()
fromEntity
. - Each has its own unique behavior (
jump()
for Player,wander()
for NPC) .
Why This Approach Is Powerful
- Code Reusability:
- We don’t need to rewrite
health
,speed
, ormove()
for every entity.
- We don’t need to rewrite
- Scalability:
- If we want to add another entity type (e.g., an enemy), we just extend
Entity
.
- If we want to add another entity type (e.g., an enemy), we just extend
- Organized Structure:
- Instead of cluttering
main.gd
with logic, we keep character behaviors inside their own scripts .
- Instead of cluttering