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 an NPC might both move, but the player might jump while the NPC might wander around .

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 named Car.
  • var speed → This defines a property called speed 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

  1. Inside Visual Studio Code , create a new script file called entity.gd.
  2. 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() returns 0.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

  1. Classes define reusable blueprints for objects.
  2. Inheritance allows multiple objects to share the same base behaviors .
  3. Parent-child relationships structure objects in both the scene tree and code.
  4. Movement is calculated based on speed, direction, and frame time .
  5. 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

  1. Create a new file called player.gd.
  2. Write the following code:
extends Entity
class_name Player

func jump():
    print(name, " jumped!")

2.2 Creating npc.gd

  1. Create a new file called npc.gd.
  2. Write the following code:
extends Entity
class_name NPC

func wander():
    print(name, " is wandering...")

What’s Happening Here?

  • Player and NPC both extend Entity .
  • 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.

  1. 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 and NPC use move() from Entity .
  • Each has its own unique behavior (jump() for Player, wander() for NPC) .

Why This Approach Is Powerful

  1. Code Reusability:
    • We don’t need to rewrite health, speed, or move() for every entity.
  2. Scalability:
    • If we want to add another entity type (e.g., an enemy), we just extend Entity .
  3. Organized Structure:
    • Instead of cluttering main.gd with logic, we keep character behaviors inside their own scripts .