DEV Community

Cover image for Godot 4: The Book of Code
christine
christine

Posted on • Originally published at christinec-dev.Medium on

Godot 4: The Book of Code

If you’re eager to dive into the exciting world of Godot development, it’s important to get a handle on the basics of programming! You don’t need to be a coding wizard to make a game, you just need to have a solid understanding the key concepts like variables, functions, loops, and arrays.

I previously put together the Book of Nodes (which I’m currently updating), covering the common nodes you’ll encounter on your game development journey. I thought it would be super helpful to create a companion resource — a friendly coding guide!

This guide covers the fundamentals of coding in Godot. We won’t delve into all the aspects of coding or GDScript, but we will cover the core structures necessary to begin coding.

What is GDScript?

GDScript is Godot’s own programming language, made just for games. It’s easy to read, beginner-friendly, and designed to integrate tightly with the Godot Editor.

Why use GDScript?

  • It’s simple, fast, and built for Godot.
  • It’s similar to Python (clean syntax, no extra symbols).
  • You can write code directly on any node to control how it behaves .

A lot of developers switch over from Unity and therefore codes in Godot using C#. For that reason, we will be covering both GDScript and C# in this resource.


Navigation

This resource is broken down into the following sections:

1. Fundamentals

  • Variables
  • Data Types
  • Constants
  • Functions
  • Comments
  • Scope

2. Logic & Control

  • If / Else
  • Match (Switch)
  • Loops (For / While)
  • Break, Continue, & Return

3. Collections

  • Arrays
  • Dictionaries
  • Resources

4. Gameplay Mechanics

  • Signals (Events)
  • Input
  • Timers
  • Physics Process vs. Process
  • Random Numbers
  • The Ready Function

5. Object & Scene Control

  • Instancing Scenes
  • Accessing Nodes
  • Inheritance
  • Export Variables
  • Groups

6. Bonus: Polish & Organization

  • Code Style Tips
  • Debugging
  • Autoloads (Singletons)

To make it easier to read, you can download the (free) Offline PDF version of this post here.

Godot 4 Book of Code


Part I: Fundamentals

Before embarking on a complex project, it’s crucial to grasp the foundational elements of coding. The Fundamentals section covers the core concepts that you will frequently use in nearly every Godot script. These principles form the basis of all programming, not just within Godot.

In this section, we will cover the following topics:

  • In this section, we will cover the following topics:
  • Variables— storing and reusing data
  • Data Types — understanding different kinds of information
  • Constants— setting fixed, unchanging values
  • Functions— organizing your logic into reusable actions
  • Comments — explaining your code for yourself and others
  • Scope — understanding where and how your data exists

1. Variables

Variables are containers used to store reusable pieces of data such as numbers, text, or even entire nodes. You can think of them as your code’s “memory slots,” holding information like the player’s name, health, or the sound that plays when they take damage.

Variables can be defined globally, making them accessible in every piece of the script (and sometimes other scripts), or locally, within functions, where they exist only while that function runs.

Godot 4 Book of Code

Why Variables are Needed

Games constantly track information:

  • Player health, score, or ammo
  • Enemy positions
  • Whether a door is open or not
  • The name of the NPC you’re talking to

Without variables, a game wouldn’t know what happened before or what should happen next. It couldn’t reference or change values , making it impossible to keep track of states. Variables are the backbone of all programming.

Syntax

GDScript:

var variable_name = value
var player_health = 100
Enter fullscreen mode Exit fullscreen mode

C#:

type variableName = value;
int playerHealth = 100
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use clear, descriptive names  — for example, player_health or enemy_speed, so it’s easy to understand what each variable represents.
  • Follow snake_case naming in GDScript  — write variable names in lowercase with underscores, like player_score or door_open.
  • Follow camelCase naming in C#  — write variable names in camel case, like playerScore or doorOpen.
  • Avoid reserved keywords  — don’t use words that Godot or GDScript already reserve for internal use (e.g. name, class, print), as this can cause errors or unexpected behavior.

Example

Here’s how you might define variables throughout your game.

The below code creates a player with the name “Bob”, who has 100 health and a sword.

GDScript:

var player_health = 100
var player_name = "Bob"
var has_sword = true
Enter fullscreen mode Exit fullscreen mode

C#:

int playerHealth = 100;
string playerName = "Bob";
bool hasSword = true;
Enter fullscreen mode Exit fullscreen mode

2. Data Types

Data types define the kind of information that a variable can store, such as numbers, text, boolean values (true/false), or even complex objects like nodes or images. They are essential for ensuring that your game can correctly utilize and process data while maintaining a standardized approach to handling and processing that data.

For example, by specifying the data type, we can prevent the addition of a string to a numeric data type. This means that you cannot assign “Bob” as a value for your health; it must always be a number or a float!

Why Data Types Are Needed

Every game needs to handle different kinds of information:

  • Numbers: Used for health, speed, or score. These can be either integers (whole numbers like 1 or 99) or floats (decimal values, such as 1.42 or 9.87).
  • Text: Refers to player names, dialogues, or menus. Text is defined as strings.
  • Boolean Values: Represent true/false states, such as “Is the door open?” or “Is the player jumping?”
  • Complex Types: Include structures like Vector2, Color, or Node, which are used for movement, visual representation, and object references.

Without data types, your game wouldn’t know how to compare, process, calculate, or display information properly. This could potentially lead to to confusing bugs and broken logic.

Types of Data

Godot 4 Book of Code

Syntax

GDScript:

var variable_name: Type = value
var player_speed: float = 4.5
Enter fullscreen mode Exit fullscreen mode

C#:

Type variableName = value;
float playerSpeed = 4.5f;
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Always use the correct type for what you’re storing —  use integers (int) for whole numbers, floats (float) for decimals, and strings (String) for text.
  • Be consistent —  don’t change a variable’s type mid-script (e.g., storing a number and later replacing it with text).
  • Use type hints (GDScript 2.0+) or explicit types (C#) to make your code safer and easier to read.
  • Learn Godot’s built-in types  — like Vector2, Vector3, Color, Node, and Array, as they’re core to building any game.

Example

Here’s how you might define different data types for a player’s stats and position.

The below code creates a player with a name of “Bob” (value of string), a health of 100 and speed of 4.5 (numeric values), a sword (boolean), and position (Vector2).

GDScript:

var player_name: String = "Bob" #string
var player_health: int = 100 #integer
var player_speed: float = 4.5 #float
var has_sword: bool = true #boolean
var player_position: Vector2 = Vector2(200, 100) # complex type
Enter fullscreen mode Exit fullscreen mode

C#:

string playerName = "Bob"; //string
int playerHealth = 100; //integer
float playerSpeed = 4.5f; //float
bool hasSword = true; //boolean
Vector2 playerPosition = new Vector2(200, 100); //complex type
Enter fullscreen mode Exit fullscreen mode

3. Constants

Constants are values that never change while your game runs. They’re like permanent markers in your code — once defined, they stay the same no matter what happens.

You might use constants for things like maximum health, gravity, default speed, or level names. These are all values that should stay fixed and not be altered during gameplay.

Godot 4 Book of Code

Why Constants Are Needed

Constants make your code more organized, predictable, and easier to maintain.

They’re especially useful for:

  • Fixed values that should never change, like MAX_HEALTH = 100 (we want our health to change, but never go over 100%)
  • Game settings like gravity, jump force, or map size
  • Avoiding “magic numbers” — instead of random hard-coded numbers, you give them clear names
  • Preventing accidental changes to important values during gameplay

Without constants, you might end up reusing the same number in multiple places, and if you ever need to change it, you’ll have to hunt it down everywhere in your code.

Syntax

GDScript:

const CONSTANT_NAME = value
const MAX_HEALTH = 100
Enter fullscreen mode Exit fullscreen mode

C#:

const Type CONSTANT_NAME = value;
const int MAX_HEALTH = 100;
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use constants for fixed or shared values  — especially ones that appear multiple times in your code.
  • Name constants in ALL_CAPS to make them stand out (e.g., MAX_SPEED, GRAVITY).
  • Group related constants together at the top of your script for better organization.
  • Never modify a constant after it’s defined in your code. That defeats its purpose, so if you define it at the beginning of your code, avoid modifying its value later in a function.

Example

Here’s how you might define constants for player attributes and physics.

The code below creates constants that ensure the maximum health never surpasses 100%, sets a gravity force of 9.8, assigns the final player name as Bob, and establishes a spawn position that will never change.

GDScript:

const MAX_HEALTH = 100
const GRAVITY = 9.8
const PLAYER_NAME = "Bob"
const START_POSITION = Vector2(100, 200)
Enter fullscreen mode Exit fullscreen mode

C#:

const int MAX_HEALTH = 100;
const float GRAVITY = 9.8f;
const string PLAYER_NAME = "Bob";
readonly Vector2 START_POSITION = new Vector2(100, 200);
Enter fullscreen mode Exit fullscreen mode

Godot 4 Book of Code


4. Functions

Functions are reusable blocks of code that perform specific tasks. They let you organize your logic into smaller, manageable pieces, almost like instructions you can call at anytime.

Think of a function as a mini-program inside your script: instead of writing the same code over and over, you define it once and call it whenever needed.

Godot 4 Book of Code

Why Functions Are Needed

Games rely on repeated actions and logic. For example, an NPC should know when to walk and when to stop. Functions make that process clean and efficient.

For example:

  • Moving a character
  • Playing a sound when the player takes damage
  • Spawning an enemy or item
  • Saving or loading game data

Without functions, you’d need to duplicate the same lines of code in multiple places — making your game harder to read, debug, and maintain. Functions promote clean, modular, and reusable code.

Syntax

GDScript:

func function_name(parameters):
    # code to run
    return value

func add(a, b):
   return a + b
Enter fullscreen mode Exit fullscreen mode

C#:

ReturnType FunctionName(parameters)
{
    // code to run
    return value;
}

int Add(int a, int b) { 
  return a + b; 
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use clear, action-based names (e.g., move_player(), take_damage(), play_music()), so it’s obvious what the function does.
  • Keep functions focused  — each one should do one thing well.
  • Use parameters to pass data into a function, and returns to get data back.
  • Avoid long functions  — break them into smaller pieces for readability and reuse.
  • In GDScript, function names use snake_case; in C#, use PascalCase.

Example

The code defines a variable which stores the player’s health, and in the function, whenever the player takes damage, the health value changes. Thus, the function controls the logic for when the player takes damage.

GDScript:

# variable
var player_health = 100

# function to change variable
func take_damage(amount):
    player_health -= amount
    print("Player health:", player_health)
Enter fullscreen mode Exit fullscreen mode

C#:

// variable
int playerHealth = 100;

// function to change variable
void TakeDamage(int amount)
{
    playerHealth -= amount;
    GD.Print("Player health: " + playerHealth);
}
Enter fullscreen mode Exit fullscreen mode

5. Comments

Comments are notes that you leave inside your code that the computer completely ignores. They’re meant for you and other developers to explain what your code does, why you wrote it that way, or to temporarily disable certain lines during testing.

Think of comments as sticky notes for your future self. They help make your code readable, teachable, and easier to maintain.

Godot 4 Book of Code

Why Comments Are Needed

As you grow your game, your code may become confusing after a few weeks (or to someone new reading it). Comments help you remember the reasoning behind your decisions, and take notes on what you need to change or refer back to.

You might use comments to:

  • Explain what a function or variable does
  • Clarify complex logic or math
  • Leave notes for future improvements
  • Temporarily disable code without deleting it

Without comments, your codebase can quickly become a maze of unexplained logic — especially in large projects with multiple scripts and systems.

Syntax

GDScript:

# GDScript
# Single-line comment
# This explains a line of code
Enter fullscreen mode Exit fullscreen mode

C#:

// CSharp 
// Single-line comment
/* Multi-line
   comment */
// This explains a line of code
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Write comments for clarity, not decoration. Use them to explain why something exists, not just what it does.
  • Keep comments up to date. Outdated comments can be more confusing than no comments at all.
  • Use comments to break your code into sections. It helps you navigate long scripts easily.
  • In GDScript , comments start with #.
  • In C# , comments start with //.

Example

Here’s how you might use comments to explain your code in both languages.

GDScript:

# Player starts with 100 health
var player_health = 100  

# Function to apply damage to the player
func take_damage(amount):
    player_health -= amount
    print("Player health:", player_health) # Debug print to console
Enter fullscreen mode Exit fullscreen mode

C#:

// Player starts with 100 health
int playerHealth = 100;

/* Function to apply damage
   and display remaining health */
void TakeDamage(int amount)
{
    playerHealth -= amount;
    GD.Print("Player health: " + playerHealth); // Debug print
}
Enter fullscreen mode Exit fullscreen mode

6. Scope

Scope determines where a variable or function can be accessed in your code. It’s like defining whether a certain piece of logic is available everywhere or only inside a specific block of code.

Godot 4 Book of Code

Why Scope Is Needed

Scope keeps your code clean, predictable, and safe from unwanted changes.

In games, you might want some data to be accessible everywhere (like the player’s score), while other data should only exist temporarily inside a function (like a loop counter or a temporary position).

Without scope rules, variables could overwrite each other, cause bugs, or leak data across scripts unintentionally.

Here’s the most common scopes:

  • Global scope: Variables and functions that can be accessed from anywhere in the script, and sometimes by other scripts.
  • Local scope: Variables that only exist inside a specific function or block. They’re created when the function runs and destroyed when it ends.
  • Access modifiers (C# only): Define who can access a variable or function from other scripts.

Godot 4 Book of Code

Syntax

GDScript:

# Global variable
var global_var = value

func some_function():
    var local_var = value # Local to this function - you cannot call it outside of here
Enter fullscreen mode Exit fullscreen mode

C#:

// Global variable
int globalVar = 100;

void SomeFunction()
{
    int localVar = 50; // Only accessible inside this function
}
// Access modifiers (C# only)
public int localVar = 100; // Accessible by all scripts
private int localVar = 30; // Accessible only in this class
protected int localVar = 10; // Accessible in this class and subclasses
internal int localVar = 0; // Accessible within the same assembly
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use local scope whenever possible  — it keeps variables self-contained and prevents accidental changes elsewhere.
  • Use global scope only for shared data like settings, player stats, or managers that must be accessed by multiple scripts.
  • Avoid naming conflicts  — two variables with the same name in different scopes can cause confusion.
  • In GDScript , define globals outside of functions (like at the top of your script), and locals inside.
  • In C# , use access modifiers like public, private, and protected to control visibility.

Example

Here’s a simple example showing the difference between global and local scope.

GDScript:

# Global variable - any function can access this, even other scripts
var player_health = 100

func take_damage(amount):
    # Local variable (only exists while this function runs)
    var new_health = player_health - amount
    print("After damage:", new_health)
Enter fullscreen mode Exit fullscreen mode

C#:

// Global variable - any function can access this, even other scripts
public int playerHealth = 100; // Accessible by all scripts
private int ammoCount = 30; // Accessible only in this class
protected int armor = 10; // Accessible in this class and subclasses
internal int score = 0; // Accessible within the same assembly

void TakeDamage(int amount)
{
    // Local variable (only exists within this function)
    int newHealth = playerHealth - amount;
    GD.Print("After damage: " + newHealth);
}
Enter fullscreen mode Exit fullscreen mode

Part II: Logic & Control

Games and programs are filled with decisions — should the player take damage? Should the door open? Should the enemy chase or retreat?

Logic and control structures enable your game to think, react, and adapt to various situations. These tools instruct your code on what actions to take and when to take them. They serve as the brain behind your game’s behavior, controlling the flow of the game, responding to player input, and determining outcomes.

In this section, we will explore the following concepts:


1. If / Else

If / Else statements are the foundation of decision-making in programming. They let your game choose different paths depending on whether certain conditions are true or false.

You can think of them like branching roads — your game checks a condition, and depending on the result, it takes one route or another.

Godot 4 Book of Code

Why If / Else Is Needed

Every game relies on conditions, for example:

  • If the player’s health reaches zero → trigger game over
  • If the score is high enough → unlock a new level
  • If a key is collected → open a door
  • If the player presses jump → play jump animation

Without conditional logic, your game would run in a straight line with no reaction to what happens — no decisions, no consequences, and no interactivity.

Syntax

GDScript:

if condition:
    # code
elif other_condition:
    # code
else:
    # code
Enter fullscreen mode Exit fullscreen mode

C#:

if (condition)
{
    // code
}
else if (otherCondition)
{
    // code
}
else
{
    // code
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Keep conditions clear and readable  — use descriptive variable names so your logic reads like a sentence (if player_health <= 0:).
  • Avoid deep nesting  — too many nested if statements make code hard to follow. Consider using match or early returns instead.
  • Use elif for multiple related conditions instead of chaining separate ifs.
  • Test edge cases  — ensure your logic works at boundaries (e.g., 0 health, max score).

Example

Here’s a simple block of code that uses if/else statements to determine whether the player is alive, low on health, or defeated.

GDScript:

var player_health = 50

# If health is more than zero, the player is alive.
if player_health > 50:
    print("Player is alive!")
# If health is between 1 and 50, warn that it's low.
elif player_health > 0 and player_health <= 50:
    print("Player health low")
# Otherwise, the player is defeated.
else:
    print("Player is defeated.")
Enter fullscreen mode Exit fullscreen mode

C#:

int playerHealth = 50;

// If health is more than zero, the player is alive.
if (playerHealth > 50)
{
    GD.Print("Player is alive!");
}
// If health is between 1 and 50, warn that it's low.
else if (playerHealth > 0 && playerHealth <= 50)
{
    GD.Print("Player health low");
}
// Otherwise, the player is defeated.
else
{
    GD.Print("Player is defeated.");
}
Enter fullscreen mode Exit fullscreen mode

Godot 4 Book of Code


2. Match (Switch)

The Match statement (called Switch in many other languages) is a cleaner, more organized way to handle multiple possible outcomes for a single value.

Instead of writing a long chain of if and elif statements, match lets your code check one variable against several conditions, thus keeping your logic simple and easy to read.

Think of it like a menu of possibilities: the program picks the one that matches and runs the code for that case.

Godot 4 Book of Code

Why Match Is Needed

When your game needs to react differently to multiple options — such as player input, item types, or enemy states — match keeps your logic tidy.

For example:

  • Checking which key was pressed
  • Determining what item the player picked up
  • Reacting to an enemy’s state (idle, chasing, attacking)
  • Handling dialog choices or menu selections

Without match, you’d end up with repetitive if/elif blocks that are harder to maintain.

Syntax

GDScript:

match value:
    pattern1:
        # code
    pattern2:
        # code
    _:
        # default case
Enter fullscreen mode Exit fullscreen mode

C#:

switch (value)
{
    case pattern1:
        // code
        break;
    case pattern2:
        // code
        break;
    default:
        // default case
        break;
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use match for clear, single-variable comparisons  — it’s cleaner than stacking if/elif.
  • Include a _ (default) case to catch unexpected values or errors.
  • Keep cases short and direct  — avoid nesting too much logic inside each one.
  • In C# , use the switch statement (and consider switch expressions in newer C# versions for concise code).

Example

Here’s how you might use match to handle player actions. The logic of the game will change depending on the state of the player.

GDScript:

# Player state
var action = "jump"

# Change state based on action
match action:
    "attack":
        print("Player attacks!")
    "jump":
        print("Player jumps!")
    "block":
        print("Player blocks!")
    _:
        print("Unknown action!")
Enter fullscreen mode Exit fullscreen mode

C#:

// Player state
string action = "jump";

// Change state based on action
switch (action)
{
    case "attack":
        GD.Print("Player attacks!");
        break;
    case "jump":
        GD.Print("Player jumps!");
        break;
    case "block":
        GD.Print("Player blocks!");
        break;
    default:
        GD.Print("Unknown action!");
        break;
}
Enter fullscreen mode Exit fullscreen mode

Godot 4 Book of Code


3. Loops (For / While)

Loops allow your code to repeat actions automatically without writing the same line over and over. They’re essential for anything that needs to run multiple times — like moving enemies, checking objects, or counting through a list.

Think of loops as automated cycles: you define what should happen and how long it should continue

Godot 4 Book of Code

Why Loops Are Needed

Games rely on repetition — from drawing frames to checking collisions. Loops make that process simple and efficient.

You might use them to:

  • Move every enemy in a list
  • Spawn several coins or projectiles
  • Update player stats each frame
  • Repeat something until a condition is met

Without loops, you’d have to manually duplicate code for every instance — wasting time and creating room for errors.

Godot 4 Book of Code

Syntax

GDScript:

for element in collection:
    # repeated code

while condition:
    # repeated code
Enter fullscreen mode Exit fullscreen mode

C#:

foreach (var element in collection)
{
    // repeated code
}
while (condition)
{
    // repeated code
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use for loops when you know how many times something should run (e.g., counting or iterating through arrays).
  • Use while loops when you don’t know how long something will continue (e.g., waiting for an event or condition).
  • Avoid infinite loops  — always include a clear condition or break.
  • Use descriptive loop variables (for enemy in enemies: instead of for i in range(…)).
  • Keep loop bodies small  — too much logic inside can hurt performance.

Example

Here’s how you might use both types of loops to manage enemies in a scene.

The for loop will iterate over a list of enemies (Goblin, Orc, and Troll), and for each enemy, it will print a message indicating that they are ready to attack.

The while loop will decrease the player’s health as long as their current health is more than 0. Once the player’s health reaches 0, the loop will stop.

GDScript:

# Array of enemies
var enemies = ["Goblin", "Orc", "Troll"]

# FOR LOOP: Go through a list of enemies
for enemy in enemies:
    print(enemy, "is ready to attack!")

# WHILE LOOP: Reduce player health over time
var player_health = 100
while player_health > 0:
    player_health -= 10
    print("Player health:", player_health)
Enter fullscreen mode Exit fullscreen mode

C#:

// Array of enemies
string[] enemies = { "Goblin", "Orc", "Troll" };

// FOR LOOP: Go through a list of enemies
foreach (string enemy in enemies)
{
    GD.Print(enemy + " is ready to attack!");
}

// WHILE LOOP: Reduce player health over time
int playerHealth = 100;
while (playerHealth > 0)
{
    playerHealth -= 10;
    GD.Print("Player health: " + playerHealth);
}
Enter fullscreen mode Exit fullscreen mode

Godot 4 Book of Code


4. Break, Continue, & Return

Inside loops and functions, break, continue , and return are special keywords that give you precise control over how and when your code stops or skips certain actions.

  • break  — immediately stops a loop , even if it hasn’t finished all its cycles.
  • continue  — skips the rest of the current loop cycle and jumps to the next one.
  • return  — exits a function immediately and optionally sends a value back.

Think of them as traffic signs for your logic flow: “Stop here,” “Skip ahead,” or “Exit and report back.”

Why They’re Needed

Games constantly run logic loops and functions that depend on changing conditions. These keywords give you fine-grained control over when that logic should stop, skip, or exit.

You might use them to:

  • break when you’ve found the first matching item in a list
  • continue to skip inactive enemies or irrelevant data
  • return to exit a function early (like when the player is already dead)
  • Save performance by stopping unnecessary calculations

Without these control statements, loops and functions would run to completion every time — wasting cycles and cluttering logic.

Syntax

GDScript:

break # Exit loop immediately
continue # Skip to next loop iteration
return value # Exit function and return value
Enter fullscreen mode Exit fullscreen mode

C#:

break; // Exit loop immediately
continue; // Skip to next loop iteration
return value; // Exit function and return value
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use break only when needed to exit loops early.
  • Use continue for filtering logic , such as skipping invalid data.
  • Use return for early exits in functions , especially for edge-case checks.
  • Avoid multiple returns in long functions — too many can make code flow harder to follow.
  • Keep conditions clear and simple to prevent confusion when jumping out of loops or functions.

Example

Here’s how you might combine all three in a single gameplay function.

In the code below, we loop through our list of enemies. If we encounter an invalid enemy, we use the continue statement to skip to the next iteration. If we find an Orc in the list, we print a message and continue with the remaining logic (checking for dragon). If we come across a dragon, we print a message and exit the loop using the break statement. Once the loop is complete, we return, which stops the function from executing further.

GDScript:

func find_enemy(enemies):
    for enemy in enemies:
        if enemy == null:
            continue # Skip missing entries
        if enemy == "Orc":
            print("Ignoring Orc.")
            continue # Skip missing entries
        if enemy == "Dragon":
            print("Boss found! Stopping search.")
            break # Stop loop
        print("Spotted:", enemy)
    return "Search complete." # Exit function and return a message
Enter fullscreen mode Exit fullscreen mode

C#:

string FindEnemy(string[] enemies)
{
    foreach (string enemy in enemies)
    {
        if (enemy == null)
            continue; // Skip missing entries
        if (enemy == "Orc")
        {
            GD.Print("Ignoring Orc.");
            continue; // Skip missing entries
        }
        if (enemy == "Dragon")
        {
            GD.Print("Boss found! Stopping search.");
            break; // Stop loop
        }
        GD.Print("Spotted: " + enemy);
    }
    return "Search complete."; // Exit function and return a message
}
Enter fullscreen mode Exit fullscreen mode

Part III: Collections

As your games develop, you’ll soon realize that single variables, like player_name and enemy, aren’t sufficient. You will need ways to store, organize, and manage groups of data, such as multiple enemies, items, or lines of dialogue.

Collections are special data structures that enable you to handle many values simultaneously, making your scripts more dynamic, efficient, and powerful.

In this section, we’ll cover:


1. Arrays

Arrays are ordered lists that can store multiple values in a single variable.

You can think of them as containers or shelves, where each slot holds an item — like a list of enemies, levels, or collected items.

Each item in an array is stored at a numbered position called an index , starting at 0.

Godot 4 Book of Code

Why Arrays Are Needed

Arrays are essential whenever you need to handle more than one piece of related data.

For example:

  • A list of enemies in a level
  • All items in the player’s inventory
  • A sequence of checkpoints or spawn points
  • A queue of dialog lines or sound effects

Without arrays, you’d need a separate variable for every element (enemy1, enemy2, enemy3…), which quickly becomes unmanageable and performance heavy.

Syntax

GDScript:

var array_name = [value1, value2, value3]
array_name.append(value)
array_name[index]
Enter fullscreen mode Exit fullscreen mode

C#:

Type[] arrayName = { value1, value2, value3 };
arrayName[index];
var listName = new List<Type>() { value1, value2, value3 };
listName.Add(value);
Enter fullscreen mode Exit fullscreen mode

GDScript Methods

Godot 4 Book of Code

C# Methods

Godot 4 Book of Code

Best Practices

  • Use arrays for ordered data where position matters.
  • Access elements by index  — array[0] gets the first item.
  • Use loops to efficiently process all items.
  • Be careful with indices  — trying to access an index that doesn’t exist causes an error.
  • Use methods like append(), erase(), or size() to manage array contents.

Example

Here’s how you might use arrays to handle multiple enemies.

We start by defining an array named enemies, which holds a list of enemy names. Next, we print the first element in the array (at index 0), which is “Goblin”. After that, we add a new enemy “Dragon” to the list. When we print the array again, it will then display the updated list: [“Goblin”, “Orc”, “Troll”, “Dragon”]

GDScript:

var enemies = ["Goblin", "Orc", "Troll"]

# Access the first element
print(enemies[0]) # Output: Goblin
# Add a new enemy
enemies.append("Dragon")
# Loop through the list
for enemy in enemies:
    print(enemy)
Enter fullscreen mode Exit fullscreen mode

C#:

// Create an array of enemies
string[] enemies = { "Goblin", "Orc", "Troll" };

// Access the first element
GD.Print(enemies[0]); // Output: Goblin
// Convert to a dynamic list to add new items
var enemyList = new System.Collections.Generic.List<string>(enemies);
enemyList.Add("Dragon");
// Loop through the list
foreach (string enemy in enemyList)
{
    GD.Print(enemy);
}
Enter fullscreen mode Exit fullscreen mode

Godot 4 Book of Code


2. Dictionaries

Dictionaries store data as key–value pairs, allowing you to label each piece of information instead of relying on numerical positions.

Think of a dictionary as a filing cabinet — the key is the label on the drawer, and the value is what’s inside. This makes your data easier to understand and access, especially when order isn’t important but meaning is.

Godot 4 Book of Code

Why Dictionaries Are Needed

Dictionaries are perfect for data that needs names instead of numbers.

For example:

  • Storing player stats like {“health”: 100, “mana”: 50}
  • Tracking inventory items and their quantities
  • Keeping NPC dialog by character name
  • Mapping key bindings or configuration settings

Without dictionaries, you’d need separate variables or parallel arrays for every related value — which quickly gets messy.

Syntax

GDScript:

var dict_name = {
    "key1": value1,
    "key2": value2
}
dict_name["key3"] = value3
Enter fullscreen mode Exit fullscreen mode

C#:

var dictName = new Dictionary<string, Type>()
{
    { "key1", value1 },
    { "key2", value2 }
};
dictName["key3"] = value3;
Enter fullscreen mode Exit fullscreen mode

GDScript Methods

Godot 4 Book of Code

C# Methods

Godot 4 Book of Code

Best Practices

  • Use dictionaries when labels make data clearer  — e.g., player["health"] is more readable than player[0].
  • Access values by key , not index — use dict["key_name"].
  • Check for existence before accessing a key to avoid errors (if “health” in player:).
  • Use nested dictionaries for structured data (like characters, stats, or items).
  • In C# , use Dictionary for type-safe collections.

Example

Here’s how you might use dictionaries to store player stats and inventory.

In the code below, we create a new dictionary for our players values — storing their name, health, and mana. We then add a new value to the dictionary, which will store the amount of gold that they have. Finally, we iterate over the dictionary to return the updated dictionary, which will be: {“name”: “Bob”, “health”: 100, “mana”: 50, “gold”: 250}

GDScript:

# Create a dictionary of player stats
var player = {
    "name": "Bob",
    "health": 100,
    "mana": 50
}

# Access a value by key
print(player["health"]) # Output: 100

# Add a new key-value pair
player["gold"] = 250

# Loop through keys and values
for key in player:
    print(key, ":", player[key])
Enter fullscreen mode Exit fullscreen mode

C#:

using System.Collections.Generic;

// Create a dictionary of player stats
Dictionary<string, object> player = new Dictionary<string, object>()
{
    { "name", "Bob" },
    { "health", 100 },
    { "mana", 50 }
};

// Access a value by key
GD.Print(player["health"]); // Output: 100

// Add a new key-value pair
player["gold"] = 250;

// Loop through all keys and values
foreach (var stat in player)
{
    GD.Print(stat.Key + ": " + stat.Value);
}
Enter fullscreen mode Exit fullscreen mode

Godot 4 Book of Code


3. Resources

Resources in Godot are data objects that can be saved, loaded, and reused across your project. They’re stored as .tres (text-based) or .res (binary) files, and can contain variables, configurations, or even scripts — anything from item stats and materials to player data or configuration settings.

Think of them as data containers that live outside your scripts, allowing you to manage information in a clean, modular way.

Why Resources Are Needed

Resources make your game more modular, reusable, and data-driven. Instead of hardcoding every item and managing a list of arrays or dictionaries, you can store that information in Resource files and reuse them across multiple scenes or objects.

You’ll typically use Resources to:

  • Store item stats (like damage, rarity, or price).
  • Define enemy attributes or character abilities.
  • Create configuration files for tuning and game balance.
  • Create NPC, enemy, or character data which can be loaded into scenes.
  • Save and load player progress or inventory data.
  • Define materials, audio effects, shaders, and particle settings.

Without resources, you’d have to duplicate data across scripts or scenes, making your project harder to update, maintain, and balance.

Custom Resource Example

You can create your own resource classes to store structured data, such as an RPG item database or character stats.

GDScript

# ItemData.gd
extends Resource
@export var name: String
@export var damage: int
@export var price: int
Enter fullscreen mode Exit fullscreen mode

Once you’ve created your custom Resource script (for example, ItemData.gd), you can easily create Resource files directly inside the Godot Editor — no code needed.

To create a Resource file:

  1. In the FileSystem panel, right-click anywhere in your data/ folder (or wherever you want to store it).
  2. Select New ResourceCreate Resource.
  3. In the window that appears, scroll down and select your custom class (e.g., ItemData).
  4. Click Create , then assign your script (ItemData.gd) if it’s not already linked.
  5. You’ll now see your custom exported variables (like name, damage, and price) in the Inspector.
  6. Fill in their values — e.g. name = "Sword", damage = 10, price = 100.
  7. Save it as sword.tres.

Godot 4 Book of Code

Godot 4 Book of Code

Godot 4 Book of Code

Godot 4 Book of Code

For example, you could create multiple item resources:

data/
  sword.tres
  axe.tres
  potion.tres
Enter fullscreen mode Exit fullscreen mode

Then load them in-game:

var sword = load("res://data/sword.tres")
print(sword.name, "does", sword.damage, "damage")
Enter fullscreen mode Exit fullscreen mode

C

// ItemData.cs
using Godot;

[GlobalClass]
public partial class ItemData : Resource
{
    [Export] public string Name { get; set; }
    [Export] public int Damage { get; set; }
    [Export] public int Price { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

You can now create .tres resources from this script directly in the editor — each one representing an item.

Best Practices

  • Use .tres for text-based resources (readable and version control friendly).
  • Keep reusable data in res://data/ or a similar dedicated folder.
  • Use custom Resource scripts for structured data (like items, quests, or NPCs).
  • Don’t hardcode data — load and reference Resource files instead.
  • Reuse Resources between multiple nodes or scenes whenever possible.
  • Avoid circular references (when Resources reference each other in a loop).

Example

Here’s how you can create, load, and use Resources dynamically to power your game’s item system.

GDScript

# Game.gd
extends Node

func _ready():
    var sword = load("res://data/sword.tres")
    print("Picked up:", sword.name)
    print("Damage:", sword.damage)
Enter fullscreen mode Exit fullscreen mode

C

using Godot;

public partial class Game : Node
{
    public override void _Ready()
    {
        var sword = (ItemData)GD.Load("res://data/sword.tres");
        GD.Print($"Picked up: {sword.Name}");
        GD.Print($"Damage: {sword.Damage}");
    }
}
Enter fullscreen mode Exit fullscreen mode

Part IV: Gameplay Mechanics

Now that you understand how to store data and control logic, it’s time to explore the coding features that can help make your game interactive. Gameplay mechanics are what bring your project to life by connecting player actions, objects, and events in real time. They’re what make doors open, characters jump, and music change when you take damage.

In this section, we’ll explore:


1. Signals (Events)

Signals are Godot’s way of letting nodes communicate without being directly connected. Think of them as events, where one node or part of the code “emits” a signal, and another “listens” and reacts to it. This makes your game more modular and organized, since nodes don’t need to directly reference each other to interact.

Godot 4 Book of Code

You can use signals for all sorts of gameplay interactions:

  • Notify the game that the Start button was pressed → start the game
  • Have the UI flash red when the player takes damage
  • When an action is pressed, tell the door to open
  • When an animation is complete, switch to the next one

Signals turn isolated nodes into a living system that reacts to events in real time, which is a key concept for dynamic, responsive gameplay.

Why Signals Are Needed

Signals allow for clean, modular communication between game elements.

You’ll use them to:

  • Detect button presses in UI
  • React when a player’s health changes
  • Trigger events when a collision shape is entered or exited
  • Notify scripts when timers finish or animations end

Without signals, nodes would need to constantly “poll” each other, ultimately creating messy code.

Syntax

I like to think that signals are comprised of three main parts: Event → Listener → Response

GDScript:

# Emitting a signal (Event)
signal health_changed(new_health)
health_changed.emit_signal(80)

# Connecting a signal (Listener)
button.pressed.connect(_on_button_pressed)

# Defining the action (Response)
func _on_button_pressed():
    print("Button was pressed!")
Enter fullscreen mode Exit fullscreen mode

C#:

// Emitting a signal (Event)
[Signal]
public delegate void HealthChangedEventHandler(int newHealth);
EmitSignal(nameof(HealthChanged), 80);

// Connecting a signal (Listener)
button.Pressed += OnButtonPressed;

// Response method (Response)
private void OnButtonPressed()
{
    GD.Print("Button was pressed!");
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use signals to decouple logic  — nodes shouldn’t depend directly on each other
  • Connect signals in _ready() or in the editor for clarity.
  • Name custom signals descriptively , like player_died or door_opened.
  • Disconnect unused signals to prevent unexpected behavior.

Example

Here’s a simple setup where pressing a button updates the player’s health bar.

In the code below, we define a signal called health_changed in the Player script. When our player takes damage, we emit this signal to notify the other parts in our game that our health has changed.

When the signal is emitted, the other parts of our code which are connected to it, should execute their logic. In this case, we have another script called UI, which connects to this signal in the ready() function. We are connecting this signal to a new function in this script called on_health_changed. This tells the game our UI script is listening for events (emits) from the Player script, and whenever that event occurs, the logic within the function will execute - which in this case is a simple print statement notifying us of our new health value.

GDScript:

# Player.gd

# Signal creation
signal health_changed(new_health)
var health = 100

# Signal emission (Event)
func take_damage(amount):
    health -= amount
    health_changed.emit(health)

# UI.gd

# Signal connection (Listener)
func _ready():
    var player = get_node("../Player")
    player.health_changed.connect(_on_health_changed)

# Signal action (Response)
func _on_health_changed(new_health):
    print("Player health:", new_health)
Enter fullscreen mode Exit fullscreen mode

C#:

// Player.cs

// Signal creation
[Signal]
public delegate void HealthChangedEventHandler(int newHealth);
int health = 100;

// Signal emission (Event)
public void TakeDamage(int amount)
{
    health -= amount;
    EmitSignal(nameof(HealthChanged), health);
}

// UI.cs

// Signal connection (Listener)
public override void _Ready()
{
    var player = GetNode("../Player");
    player.Connect("HealthChanged", this, nameof(OnHealthChanged));
}

// Signal action (Response)
private void OnHealthChanged(int newHealth)
{
    GD.Print("Player health: " + newHealth);
}
Enter fullscreen mode Exit fullscreen mode

2. Input

Input is how players interact with your game. This can be via pressing keys, clicking, or touching the screen to control characters and trigger actions. Godot’s input system makes it easy to detect and respond to player actions through code or the Input Map (found in Project → Project Settings → Input Map).

Godot 4 Book of Code

You can think of Input as the player’s choices, and your game listens and reacts accordingly.

Why Input Is Needed

Every interactive game depends on Input:

  • Moving the player character
  • Jumping, attacking, or using abilities
  • Navigating menus or UI
  • Detecting gamepad or touchscreen actions

Without Input handling, your game wouldn’t know what the player wants to do, and the player won’t have any way of interacting with the game, they would just sit idle!

Syntax

GDScript:

# Detecting input actions continuously (we'll move right as long as we hold key)
func _process(delta):
    if Input.is_action_pressed("move_right"):
        # logic

# Detecting input actions once-off (we'll jump once and have to press key again)
func _input(event):
    if event.is_action_pressed("jump"):
        # logic
Enter fullscreen mode Exit fullscreen mode

C#:

// Detecting input actions continuously (we'll move right as long as we hold key)
public override void _Process(double delta)
{
    if (Input.IsActionPressed("move_right"))
       // logic
}

// Detecting input actions once-off (we'll jump once and have to press key again)
public override void _Input(InputEvent @event)
{
    if (@event.IsActionPressed("jump"))
        // logic
}
Enter fullscreen mode Exit fullscreen mode

Input Methods

Godot 4 Book of Code

Best Practices

  • Use the Input Map instead of hardcoding keys — this lets players rebind controls easily.
  • Check for actions (like "ui_accept" or "move_left") rather than specific keys.
  • Use _process() for continuous input (movement), and _input() for one-time actions (button presses).
  • Support multiple devices (keyboard, controller, touchscreen) whenever possible.
  • Keep input logic separate from gameplay logic — use signals or dedicated functions to handle actions cleanly.

Example

Here’s a simple movement script that lets a player move left and right, and jump when the spacebar is pressed.

In the code below, the move_right and move_left keys have been assigned to the ← → keys in the Project Settings , and the jump key to the spacebar.

GDScript:

extpends CharacterBody2D

const SPEED = 200
const JUMP_FORCE = -400

func _physics_process(delta):
    var velocity = Vector2.ZERO

    # Move player left and right
    if Input.is_action_pressed("move_right"):
        velocity.x += SPEED
    elif Input.is_action_pressed("move_left"):
        velocity.x -= SPEED

    # Move player up (jump)
    if is_on_floor() and Input.is_action_just_pressed("jump"):
        velocity.y = JUMP_FORCE
    velocity = move_and_slide(velocity, Vector2.UP)
Enter fullscreen mode Exit fullscreen mode

C#:

using Godot;

public partial class Player : CharacterBody2D
{
    const float Speed = 200f;
    const float JumpForce = -400f;
    public override void _PhysicsProcess(double delta)
    {
        Vector2 velocity = Velocity;

        // Move player left and right
        if (Input.IsActionPressed("move_right"))
            velocity.X = Speed;
        else if (Input.IsActionPressed("move_left"))
            velocity.X = -Speed;
        else
            velocity.X = 0;

        // Move player up (jump) 
        if (IsOnFloor() && Input.IsActionJustPressed("jump"))
            velocity.Y = JumpForce;
        Velocity = velocity;
        MoveAndSlide();
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Timers

Timers are nodes that let you run code after a delay or repeatedly at fixed intervals. They’re essential for creating time-based events such as cooldowns, countdowns, enemy spawns, or temporary effects.

Think of a timer as an alarm clock inside your game: you set it, wait, and when it rings, something happens.

Godot 4 Book of Code

Why Timers Are Needed

Games constantly rely on timing to feel dynamic and balanced:

  • Countdown before starting a match
  • Enemy attack intervals
  • Power-up durations or cooldowns
  • Auto-saving or periodic events

Without timers, you’d need to manually track time using variables, which quickly becomes messy and unreliable.

Syntax

GDScript:

# Creating and starting a timer
var timer = Timer.new()
add_child(timer)
timer.wait_time = 2.0 # how long it will execute for (2 seconds)
timer.one_shot = true # will execute once, set to false for continuous execution
timer.start() # start the timer

# Connecting timeout signal
timer.timeout.connect(_on_timer_timeout)

# When timer times out, do this logic
func _on_timer_timeout():
    print("Time's up!")
Enter fullscreen mode Exit fullscreen mode

C#:

// Creating and starting a timer
var timer = new Timer();
AddChild(timer);
timer.WaitTime = 2.0f; // how long it will execute for (2 seconds)
timer.OneShot = true; // will execute once, set to false for continuous execution
timer.Start(); // start the timer
timer.Timeout += OnTimerTimeout; 

// When timer times out, do this logic
private void OnTimerTimeout()
{
    GD.Print("Time's up!");
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use Timer nodes for clarity instead of manually counting seconds in _process().
  • Connect the timeout signal to trigger actions cleanly when the timer finishes.
  • Set one_shot to true for single-use timers, or false to make them loop.
  • Use start() and stop() to control timers in code.
  • Reuse timers for repeating events rather than creating new ones each time.

Example

Here’s how you might use a Timer to respawn an enemy 3 seconds after it’s defeated.

In the code below, we create a timer as soon as our game starts. When it’s created, we also connect its timeout signal to our script (this can be done in the editor itself, instead of via code). When the enemy is defeated, say their health reaches below zero, we start the timer. The timer will run for however long we’ve set its wait_time, which is 3 seconds. When 3 seconds have passed, the timer will timeout , and the enemy will be respawned.

If the enemy dies again, the logic will start over again, unless we’ve set our timer to be one_shot enabled.

GDScript:

extends Node2D

# Create timer
func _ready():
    var respawn_timer = $RespawnTimer
    respawn_timer.wait_time = 3.0 
    respawn_timer.one_shot = false
    respawn_timer.timeout.connect(_on_respawn_timer_timeout)

# Start timer
func on_enemy_defeated():
    print("Enemy defeated! Respawning in 3 seconds...")
    $RespawnTimer.start()

# Stop timer
func _on_respawn_timer_timeout():
    print("Enemy has respawned!")
Enter fullscreen mode Exit fullscreen mode

C#:

using Godot;

public partial class EnemySpawner : Node2D
{
    // Create timer
    private Timer RespawnTimer;
    public override void _Ready()
    {
        RespawnTimer = GetNode<Timer>("RespawnTimer");
        RespawnTimer.WaitTime = 3b.0f; 
        RespawnTimer.OneShot = false;    
        RespawnTimer.Timeout += OnRespawnTimerTimeout;
    }
    // Start timer
    public void OnEnemyDefeated()
    {
        GD.Print("Enemy defeated! Respawning in 3 seconds...");
        RespawnTimer.Start();
    }
    // Stop timer
    private void OnRespawnTimerTimeout()
    {
        GD.Print("Enemy has respawned!");
    }
}
Enter fullscreen mode Exit fullscreen mode

4. Physics Process vs. Process

In Godot, both _process() and _physics_process() are special built-in functions that run every frame — but they serve different purposes.

  • _process(delta) runs every rendered frame — perfect for animations, UI, and non-physics logic.
  • _physics_process(delta) runs at a fixed time step — perfect for movement, collisions, and anything that interacts with the physics engine.

Godot 4 Book of Code

Why It Matters

Games depend on predictable, consistent updates.

You’ll typically use:

  • _process() → for animations, timers, and UI transitions.
  • _physics_process() → for movement, gravity, and collisions.

Syntax

GDScript:

# Called every frame (variable time step)
func _process(delta):
    # do framerate related logic

# Called at a fixed time step (default: 60 times per second)
func _physics_process(delta):
    # do physics related logic
    # usually ends in move_and_slide() or move_and_collide()
Enter fullscreen mode Exit fullscreen mode

C#:

// Called every frame
public override void _Process(double delta)
{
   // do framerate related logic
}

// Called at a fixed physics tick rate
public override void _PhysicsProcess(double delta)
{
    // do physics related logic
    // usually ends in MoveAndSlide() or MoveAndCollide()
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use _physics_process() for movement and collisions.
  • Use _process() for visual effects and non-physics updates.
  • Always multiply by delta to keep movement frame-rate independent.
  • Don’t mix physics logic in _process() — it can cause jitter or inconsistencies.
  • Pause logic carefully  — timers and physics might still run if not managed properly.

Example

Here’s a basic player script that plays a characters run animations continuously whilst the input keys are being held down in the process() function. It also then moves the character around whilst the input keys are being held down in the physics_process() function.

GDScript:

# This will play the run animations on inputs
func _process(delta):
    if Input.is_action_pressed("move_right") or Input.is_action_pressed("move_left"):
        $AnimatedSprite2D.play("run")
    else:
        $AnimatedSprite2D.play("idle")

# This will move the player on inputs.
func _physics_process(delta):
    var velocity = Vector2.ZERO
    if Input.is_action_pressed("move_right"):
        velocity.x += 200
    elif Input.is_action_pressed("move_left"):
        velocity.x -= 200
    velocity = move_and_slide(velocity)
Enter fullscreen mode Exit fullscreen mode

C#:

using Godot;

public partial class Player : CharacterBody2D
{
    const float Speed = 200f;
    private AnimatedSprite2D sprite;
    public override void _Ready()
    {
        sprite = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
    }

    // This will play the run animations on inputs
    public override void _Process(double delta)
    {
        if (Input.IsActionPressed("move_right") || Input.IsActionPressed("move_left"))
            sprite.Play("run");
        else
            sprite.Play("idle");
    }

    // This will move the player on inputs.
    public override void _PhysicsProcess(double delta)
    {
        Vector2 velocity = Vector2.Zero;
        if (Input.IsActionPressed("move_right"))
            velocity.X += Speed;
        else if (Input.IsActionPressed("move_left"))
            velocity.X -= Speed;
        Velocity = velocity;
        MoveAndSlide();
    }
}
Enter fullscreen mode Exit fullscreen mode

5. Random Numbers

Random numbers add variety and unpredictability to your game, thus ensuring no two playthroughs feel exactly the same. Godot’s random system lets you generate numbers, select random items from arrays, or even produce random directions for projectiles or enemies.

Randomness makes things feel dynamic, alive, and surprising.

Why Random Numbers Are Needed

Games rely on randomness for endless replayability:

  • Loot drops or item rewards
  • Random enemy spawns
  • Critical hits or damage variation
  • Procedural generation (maps, terrain, puzzles)
  • Particle spread or visual effects

Without randomness, your game would feel repetitive and predictable.

Syntax

GDScript:

# Initialize random seed (usually in _ready)
randomize()

# Generate a random float between 0 and 1
var r = randf()

# Generate an integer between 0 and 10
var i = randi_range(0, 10)

# Pick a random element from an array
var enemies = ["Goblin", "Orc", "Troll"]
var random_enemy = enemies.pick_random()

# Random vector direction
var dir = Vector2(randf_range(-1, 1), randf_range(-1, 1)).normalized()
Enter fullscreen mode Exit fullscreen mode

C#:

using Godot;
using System;

public partial class RandomExample : Node
{
    private Random rand = new Random();
    public override void _Ready()
    {
        // Random float between 0 and 1
        float r = (float)rand.NextDouble();

        // Random integer between 0 and 10
        int i = rand.Next(0, 11);

        // Random element from an array
        string[] enemies = { "Goblin", "Orc", "Troll" };
        string randomEnemy = enemies[rand.Next(enemies.Length)];

        // Random direction
        Vector2 dir = new Vector2(
            (float)rand.NextDouble() * 2 - 1,
            (float)rand.NextDouble() * 2 - 1
        ).Normalized();
        GD.Print($"Enemy: {randomEnemy}, Direction: {dir}");
    }
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use randomize() once at startup (in _ready()) to avoid repeating patterns.
  • Choose the right random function  — Godot offers several for different data types.
  • Use seeded randomness for predictable results (great for replays or testing).
  • Clamp or round values if you only need whole numbers or limits.
  • Keep randomness balanced  — too much can frustrate players.

Example

Here’s how you might use randomness to spawn enemies at random positions on the map.

GDScript:

extends Node2D

func _ready():
    randomize()
    for i in range(5):
        var enemy_scene = preload("res://Enemy.tscn").instantiate()
        enemy_scene.position = Vector2(
            randi_range(100, 800),
            randi_range(100, 400)
        )
        add_child(enemy_scene)
Enter fullscreen mode Exit fullscreen mode

C#:

using Godot;
using System;

public partial class Spawner : Node2D
{
    private Random rand = new Random();
    public override void _Ready()
    {
        for (int i = 0; i < 5; i++)
        {
            var enemyScene = GD.Load<PackedScene>("res://Enemy.tscn").Instantiate<Node2D>();
            enemyScene.Position = new Vector2(
                rand.Next(100, 801),
                rand.Next(100, 401)
            );
            AddChild(enemyScene);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Godot 4 Book of Code


6. The Ready Function

In Godot, the _ready() function is called once when a node enters the scene tree and is fully initialized. It’s the go-to place for setup logic such as loading resources, connecting signals, setting initial values, or caching references to other nodes.

Why Its Needed

When a node is created, not all of its children or dependencies exist yet. If you try to reference them too early, your code can break.

_ready() ensures the entire node hierarchy is loaded before running your setup code.

You’ll typically use it to:

  • Get references to child nodes
  • Connect signals between nodes
  • Load textures, sounds, or scenes
  • Initialize variables or game states

Without _ready(), you’d risk calling nodes or data that don’t exist yet.

Syntax

GDScript:

# Called once when the node enters the scene tree
func _ready():
    # Set values to be initialized
    var sprite = $Sprite2D
    var player_health = 100
Enter fullscreen mode Exit fullscreen mode

C#:

using Godot;

public partial class Player : Node2D
{
    public override void _Ready()
    {
        // Set values to be initialized
        Sprite2D sprite = GetNode<Sprite2D>("Sprite2D");
        int playerHealth = 100
    }
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use _ready() for one-time setup only — not for logic that runs continuously.
  • Keep it clean and short  — just initialization, no heavy loops or updates.
  • Access child nodes here safely  — all children are guaranteed to exist.
  • Connect signals or timers in _ready(), not in _process().
  • Avoid creating dependencies between unrelated nodes  — keep setups modular.

Example

Here’s a simple player setup that uses _ready() to load assets, connect signals, and initialize variables before gameplay starts.

GDScript:

extends CharacterBody2D

var health = 100

func _ready():
    $AnimatedSprite2D.play("idle")
    $HealthBar.max_value = health
    $Area2D.body_entered.connect(_on_body_entered)

func _on_body_entered(body):
    print("Collided with:", body.name)
Enter fullscreen mode Exit fullscreen mode

C#:

using Godot;

public partial class Player : CharacterBody2D
{
    private int health = 100;
    private AnimatedSprite2D sprite;
    private ProgressBar healthBar;
    private Area2D hitArea;

    public override void _Ready()
    {
        sprite = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
        sprite.Play("idle");
        healthBar.MaxValue = health;
        hitArea.BodyEntered += OnBodyEntered;
    }

    private void OnBodyEntered(Node body)
    {
        GD.Print($"Collided with: {body.Name}");
    }
}
Enter fullscreen mode Exit fullscreen mode

Part V: Scenes & Nodes

In Godot, everything is a node. From your player and camera to UI buttons, lights, and audio — every piece of your game inherits from the Node class.

Nodes are the building blocks of Godot. When you combine them, you create scenes, which are self-contained groups of nodes that can represent anything: a level, a character, or even a menu. Together, these form the Scene Tree, a hierarchy that defines how everything in your game is organized, updated, and connected.

In this section, we’ll cover:

  • Node Basics — understanding what nodes are and how they work
  • Scenes— reusable groups of nodes that form your game’s structure
  • Inheritance— allows one scene or script to reuse another’s logic and structure.
  • Export variables — makes a variable visible and editable in the Inspector panel.
  • Groups — tagging nodes into similar categories

1. Accessing Nodes

A Node is the most fundamental building block in Godot. Each node has a name, a type, and optional children. Nodes within a scene creates a tree-like structure that defines how your game world is built.

Think of it like this:

  • Nodes are the building blocks.
  • Scenes are the containers which hold and organize nodes.

For a more visual reference on all the nodes and their use-cases, refer to my Book of Nodes .

Godot 4 Book of Code

View the online version of the above depiction of scenes and nodes here.

Why Nodes Are Needed

Without nodes, there would be no game. They’re the foundation of everything you create in Godot.

Nodes can represent almost anything:

  • A collider that detects hits
  • A sprite that displays a texture
  • A camera that follows the player
  • A light that illuminates the scene
  • A script that controls behavior

Without nodes, every part of your game would have to be hard-coded, messy, unorganized, and nearly impossible to maintain.

Syntax

Nodes are usually added within the editor itself, but we can create and reference nodes directly within our code.

GDScript:

# Referencing an existing node and changing its property 
var sprite = $Sprite2D
sprite.modulate = Color.RED

# Getting a node by path
var camera = get_node("Camera2D")

# Creating a new node
var new_label = Label.new()
new_label.text = "Hello World"
add_child(new_label)
Enter fullscreen mode Exit fullscreen mode

C#:

// Referencing an existing node and changing its property 
Sprite2D sprite = GetNode<Sprite2D>("Sprite2D");
sprite.Modulate = new Color(1, 0, 0);

// Getting a node by path
Camera2D camera = GetNode<Camera2D>("Camera2D");

// Creating a new node
Label newLabel = new Label();
newLabel.Text = "Hello World";
AddChild(newLabel);
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Name nodes clearly (e.g., PlayerSprite, Hitbox, MainCamera).
  • Use composition  — build functionality by combining nodes, not writing giant scripts.
  • Leverage node types  — use the right node for the job (Area2D for detection, Sprite2D for visuals, etc.).
  • Keep the tree organized  — indentation and naming go a long way.
  • Don’t overload one scene  — break your game into multiple smaller, reusable ones.

Example

Here’s how you can reference a node that already exists and create a new one dynamically in the same script.

In the code below, we assume that within the editor we’ve added a Sprite2D node called “Sprite2D”. We reference this node in our script, and then we change its texture (sprite). We also create a new Label node with the text “Player ready”, and position it to our screen. The add_child() method adds the newly created node to our scene via the script.

GDScript:

extends Node2D

func _ready():
    # Reference an existing Sprite node
    var sprite = $Sprite2D
    sprite.texture = preload("res://sprites/player.png")

    # Create and add a new Label node
    var label = Label.new()
    label.text = "Player ready!"
    label.position = Vector2(10, 10)
    add_child(label)
Enter fullscreen mode Exit fullscreen mode

C#:

using Godot;

public partial class Player : Node2D
{
    public override void _Ready()
    {
        // Reference an existing node
        Sprite2D sprite = GetNode<Sprite2D>("Sprite2D");
        sprite.Texture = (Texture2D)GD.Load("res://sprites/player.png");

        // Create and add a new node dynamically
        Label label = new Label();
        label.Text = "Player ready!";
        label.Position = new Vector2(10, 10);
        AddChild(label);
    }
}
Enter fullscreen mode Exit fullscreen mode

Godot 4 Book of Code


2. Accessing Scenes

A Scene in Godot is a collection of nodes. It can represent this such as a character, a level, a user interface, or even a single reusable object.

Every scene has a root node , and that root defines the scene’s type. For example, a CharacterBody2D might be the root for a player scene, or a Control node might be the root for a UI screen.

Think of scenes as blueprints: you build them once in the editor, then reuse, instance, or switch between them dynamically during gameplay.

Godot 4 Book of Code

View the online version of the above depiction of scenes and nodes here.

Why Scenes Are Needed

Scenes make your game modular and reusable. They allow you to:

  • Build individual pieces of your game separately.
  • Reuse the same object (like enemies or items) multiple times.
  • Load and unload levels dynamically.
  • Transition between menus, gameplay, and cutscenes.

Without scenes, your entire game would live in one massive, unmanageable hierarchy, which makes updates, debugging, and testing a nightmare.

Syntax

GDScript:

# Load a scene from file
var enemy_scene = preload("res://scenes/Enemy.tscn")

# Create an instance of that scene
var enemy_instance = enemy_scene.instantiate()

# Add it to the current scene tree
add_child(enemy_instance)

# Change to another scene file
get_tree().change_scene_to_file("res://scenes/Level2.tscn")

# Reload the current scene
get_tree().reload_current_scene()

# Get a node from another scene (if it's loaded)
var player = get_tree().get_root().get_node("World/Player")
player.health = 50
Enter fullscreen mode Exit fullscreen mode

C#:

// Load a scene from file
PackedScene enemyScene = (PackedScene)GD.Load("res://scenes/Enemy.tscn");

// Create an instance of that scene
Node enemyInstance = enemyScene.Instantiate();

// Add it to the current scene tree
AddChild(enemyInstance);

// Change to another scene file
GetTree().ChangeSceneToFile("res://scenes/Level2.tscn");

// Reload the current scene
GetTree().ReloadCurrentScene();

// Get a node from another scene (if it's loaded)
Node player = GetTree().Root.GetNode("World/Player");
player.Set("health", 50);
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Keep scenes focused  — each should represent one clear purpose (e.g., Player, MainMenu, Enemy, Level1).
  • Use the root node type wisely  — it defines what the scene is and how it behaves.
  • Instance scenes instead of duplicating  — this makes updates propagate automatically.
  • Use get_tree().change_scene_to_file() for transitions, and preload() or load() for instancing.
  • Keep references clean  — don’t rely on global paths unless necessary.

Example

The below code loads and spawns an enemy scene when the player presses a key, and transitions to a new scene when health reaches zero.

GDScript:

extends Node2D

# Load scene
var enemy_scene = preload("res://scenes/Enemy.tscn")
var health = 100

func _process(delta):
    # Spawn enemy when pressing "E"
    if Input.is_action_just_pressed("spawn_enemy"):
        var enemy = enemy_scene.instantiate() # Spawn scene
        enemy.position = Vector2(400, 200)
        add_child(enemy)

    # Change to game over scene when health reaches zero
    if health <= 0:
        get_tree().change_scene_to_file("res://scenes/GameOver.tscn") # Transition scene
Enter fullscreen mode Exit fullscreen mode

C#:

using Godot;

public partial class Level : Node2D
{
    // Load scene
    private PackedScene enemyScene = (PackedScene)GD.Load("res://scenes/Enemy.tscn");
    private int health = 100;

    public override void _Process(double delta)
    {
        if (Input.IsActionJustPressed("spawn_enemy"))
        {
            Node2D enemy = enemyScene.Instantiate<Node2D>(); // Spawn scene
            enemy.Position = new Vector2(400, 200);
            AddChild(enemy);
        }
        if (health <= 0)
            GetTree().ChangeSceneToFile("res://scenes/GameOver.tscn"); // Transition scene
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Inheritance

Inheritance allows you to create new scenes or scripts that reuse and extend the functionality of existing ones. It’s one of the most powerful features of coding, as it lets you define a base behavior (like a generic “Enemy”) and then build specialized versions (like “Goblin” or “Orc”) without rewriting everything from scratch.

There are two main types of inheritance in Godot:

  1. Script inheritance  — extending another script’s code using the extends keyword.
  2. Scene inheritance  — creating new scenes that build on existing ones in the editor.

Why Inheritance Is Needed

Inheritance helps you write cleaner, reusable, and modular code. It keeps your project organized and avoids duplication when many objects share similar traits.

For example:

  • A base Enemy scene might define health and damage logic for all enemies.
  • Derived scenes like Orc, Goblin, or Troll can inherit that and add their own unique animations or stats.
  • A CharacterBody2D script might define general movement for all characters in your game, and the Player script can extend it to handle input.

Without inheritance, you’d have to duplicate large chunks of code or rebuild scenes from scratch every time.

Godot 4 Book of Code

Best Practices

  • Use inheritance for shared functionality , not for one-off behaviors.
  • Keep base classes simple and focused  — they should define core rules, not everything.
  • Override functions responsibly  — always call super() or ._process(delta) if you still want the parent’s logic to run.
  • For scenes , use “Inherit Scene” in the editor instead of duplicating.
  • Use composition (adding child nodes) alongside inheritance for flexibility.

Syntax

Script Inheritance

GDScript:

# Base script: Base.gd
# The base class defines the basic values of your enemies
class_name Base
extends Node

var health = 100

func take_damage(amount):
    health -= amount
    print("Enemy took", amount, "damage. Health:", health)

# Derived script: Derived.gd
# The derived class gets everythign from the Base, such as health and take_damage() function
extends Base

func _ready():
    print("Goblin ready!")
    take_damage()
Enter fullscreen mode Exit fullscreen mode

C#:

// Base script: Base.cs
// The base class defines the basic values of your enemies
using Godot;

public partial class Base : Node
{
    public int Health = 100;
    public virtual void TakeDamage(int amount)
    {
        Health -= amount;
        GD.Print($"Enemy took {amount} damage. Health: {Health}");
    }
}

// Derived script: Derived.cs
// The derived class gets everythign from the Base, such as health and take_damage() function
using Godot;

public partial class Derived : Base
{
    public override void _Ready()
    {
        GD.Print("Goblin ready!");
        base.TakeDamage(amount);
    }
}
Enter fullscreen mode Exit fullscreen mode

Scene Inheritance

You can also inherit scenes directly in the editor:

  1. Right-click an existing scene (e.g., Enemy.tscn).
  2. Choose “New Inherited Scene”.
  3. Godot creates a new version with all the parent nodes — ready for customization.

Godot 4 Book of Code

You can then:

  • Add new child nodes (e.g., a weapon or animation).
  • Override properties (like textures, speeds, or collision shapes).
  • Change script references while keeping base functionality intact.

Example

The code below creates a base Enemy class , which defines the shared behavior for all of our enemies. We then create two derived classes (Orc and Troll) which extends from it. These classes will get all the base functionality, such as health and take_damage(), but will still be able to get their own unique features.

This means our damage logic is the same for all enemies, but different enemies can have different animations, sound, and behavior.

GDScript:

# Base Enemy.gd
class_name Enemy
extends CharacterBody2D

var health = 100

func _ready():
    print("Enemy spawned with", health, "HP")

func take_damage(amount):
    health -= amount
    print("Enemy took damage:", amount)

# Orc.gd (inherits Enemy.gd)
# Orc plays a dance anim on taking damage, whilst still losing health
extends Enemy

func _ready():
    print("Orc enters the battlefield!")

func take_damage(amount):
    print("Orc roars in anger!")
    $animated_sprite.play("dance_anim")

# Troll.gd (inherits Enemy.gd)
extends "res://scripts/Enemy.gd"

func take_damage(amount):
    print("Troll regenerates some health!")
    health += 5
Enter fullscreen mode Exit fullscreen mode

4. Export Variables

Export variables let you expose script properties directly to the Godot Editor. They make your scripts customizable, flexible, and designer-friendly, allowing you to change values without modifying the code.

Think of them as editable parameters which appear in the Inspector just like any built-in node property.

Godot 4 Book of Code

Why Export Variables Are Needed

Export variables bridge the gap between code and design. Instead of opening scripts to adjust things like player speed or enemy health for testing, you can edit them visually in the editor.

You’ll typically use them for:

  • Character attributes (health, speed, jump power)
  • Enemy AI settings (vision range, attack delay)
  • Visual tuning (colors, scale, offsets)
  • Audio or effect configuration
  • Linking external resources (textures, scenes, sounds)

Without exports, tuning gameplay would mean constant code edits and reloads, which slows development dramatically.

Syntax

GDScript:

# A variable visible and editable in the editor
@export var speed = 200
@export var player_name = "Hero"
# Choose a texture in the editor
@export var icon: Texture2D
Enter fullscreen mode Exit fullscreen mode

C#:

[Export]
public float Speed = 200f;
[Export]
public string PlayerName = "Hero";
[Export]
public Texture2D Icon;
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use clear names  — make sure exports describe what they control.
  • Set sensible defaults  — keep values playable right away.
  • Add type hints (int, float, Color, PackedScene) for editor validation.
  • Use ranges and enums to restrict inputs and avoid mistakes.
  • Keep export usage simple  — avoid exporting everything just for convenience.

Example

The below script exports variables which will be exposed to the Inspector panel for editing. This means whatever value we assign in the inspector panel will be the value assigned to that script — meaning it will overwrite hard-coded values in our script!

GDScript:

extends CharacterBody2D

@export var speed: float = 200.0
@export var jump_force: float = 400.0
@export var sprite_texture: Texture2D
Enter fullscreen mode Exit fullscreen mode

C#:

using Godot;

public partial class Player : CharacterBody2D
{
    [Export] public float Speed = 200f;
    [Export] public float JumpForce = 400f;
    [Export] public Texture2D SpriteTexture;
}
Enter fullscreen mode Exit fullscreen mode

4. Groups

Groups are a powerful way to organize and manage multiple nodes in Godot. They let you categorize nodes under a shared label (like “Enemies” or “Interactables”) so you can call functions, apply effects, or send signals to all of them at once.

Think of groups as tags for nodes. You can assign one or more groups to a node and then access them from anywhere in your game.

Godot 4 Book of Code

Why Groups Are Needed

Groups are essential when you have multiple instances of similar objects, like enemies, NPCs, or projectiles.

You’ll typically use them to:

  • Apply damage or effects to every enemy.
  • Check interactions dynamically — for example, showing an “Interact” prompt when focusing on an interactable group item, or triggering dialog when focusing on an NPC.
  • Enable or disable all interactables at once (e.g., pausing player input or freezing AI).
  • Trigger multiple lights, doors, or sounds with a single command.
  • Broadcast global events, like GameOver, LevelCleared, or Pause

Without groups, you’d need to manually loop through every node or keep separate arrays, which is messy, slow, and error-prone.

Syntax

You can assign nodes directly to Groups within the editor, but below is how you can add objects to groups using code.

GDScript:

# Add the node to a group
add_to_group("Enemies")

# Check if the node belongs to a group
if is_in_group("Enemies"):
    print("This node is an enemy!")

# Remove the node from a group
remove_from_group("Enemies")
Enter fullscreen mode Exit fullscreen mode

C#:

// Add the node to a group
AddToGroup("Enemies");

// Check if the node belongs to a group
if (IsInGroup("Enemies"))
    GD.Print("This node is an enemy!");

// Remove the node from a group
RemoveFromGroup("Enemies");
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use groups to organize related nodes (e.g., “Enemies”, “Collectibles”, “UI”).
  • Add groups via code or the editor (under the “Node → Groups” tab).
  • Avoid too many overlapping groups  — keep naming meaningful.
  • Use is_in_group() to check membership before applying logic.
  • Leverage signals and call_group() for clean global communication.

Example

Here’s how you can use groups to detect whether the player is focusing on an interactable object (like an item, door, or NPC). If the object is in the Interactable group, the player receives a message prompt.

GDScript:

# Player.gd
extends CharacterBody2D

func _process(delta):
    # Cast a ray forward to detect what the player is looking at
    var space_state = get_world_2d().direct_space_state
    var result = space_state.intersect_ray(global_position, global_position + Vector2(50, 0))

    # Get the collider of the object
    if result and result.has("collider"):
        var target = result.collider

        # If its in interactable group, show prompt
        if target.is_in_group("Interactable"):
            print("You're focusing on an interactable. Press [E] to pick up.")
        else:
            print("You're looking at:", target.name)
Enter fullscreen mode Exit fullscreen mode

C#:

// Player.cs
using Godot;

public partial class Player : CharacterBody2D
{
    public override void _Process(double delta)
    {
        // Cast a ray forward to detect what the player is looking at
        var spaceState = GetWorld2D().DirectSpaceState;
        var result = spaceState.IntersectRay(GlobalPosition, GlobalPosition + new Vector2(50, 0));

        // Get the collider of the object
        if (result.Count > 0 && result.ContainsKey("collider"))
        {
            var target = result["collider"] as Node;

            // If its in interactable group, show prompt
            if (target.IsInGroup("Interactable"))
                GD.Print("You're focusing on an interactable. Press [E] to pick up.");
            else
                GD.Print($"You're looking at: {target.Name}");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Part VI: Bonus

You’ve learned how to build the core gameplay systems of a Godot project. Now it’s time to focus on the finishing touches that make your project clean, stable, and scalable.

This section covers the tools and habits that keep your code readable, debuggable, and organized — especially as your project grows.

We’ll go over:


1. Code Style Tips

Clean code isn’t just about getting things to work, it’s about making your future self thank you later.

Readable scripts are easier to debug, share, and extend.

Why It Matters

As projects grow, consistency becomes critical. Having clear naming, structure, and commenting habits keeps your code easy to understand for everyone (including you, months later).

Best Practices

  • Use consistent naming:
  • GDScript → snake_case for variables and functions.
  • C# → camelCase for variables and PascalCase for methods and classes.
  • Comment with purpose: Explain why, not just what.
  • Group related code: Keep setup, logic, and cleanup organized in sections OR code regions.
  • Avoid magic numbers: Use constants or exported variables instead.
  • Keep functions short: Each function should do one clear thing.
  • Be descriptive: player_health is better than hp1.

Example (GDScript)

# Bad
var s = 10
func x():
    s -= 1
    if s == 0: print("Dead")

func phello():
    print("Hello")

# Good
#region [Player Health Management]
var player_health = 10

func take_damage(amount):
    player_health -= amount
    if player_health <= 0:
        print("Player defeated")
        # Trigger game over sequence
#endregion

#region [Debugging]
func print_hello():
    print("Hello")
#endregion
Enter fullscreen mode Exit fullscreen mode

2. Debugging

Every developer runs into bugs, it’s just inevitable. The key is finding and understanding them quickly. Godot provides a range of built-in tools to help you diagnose problems effectively.

Godot 4 Book of Code

Best Practices

  • Use print() or GD.Print() to log key values and track execution flow.
  • Use assert(condition) to catch unexpected states early.
  • Use the Debugger panel (bottom dock) to pause on errors and inspect variables.
  • Leverage breakpoints to step through your code line by line.
  • Enable “Visible Collision Shapes” in the Debug menu to check collisions.
  • Don’t ignore warnings  — they often point to subtle bugs before they crash your game.

Example

GDScript:

func _physics_process(delta):
    assert(player != null, "Player reference missing!")
    if player.health <= 0:
        print("Player is dead")
Enter fullscreen mode Exit fullscreen mode

C#:

public override void _PhysicsProcess(double delta)
{
    GD.Assert(Player != null, "Player reference missing!");
    if (Player.Health <= 0)
        GD.Print("Player is dead");
}
Enter fullscreen mode Exit fullscreen mode

Godot 4 Book of Code


3. Using Autoloads (Singletons)

Autoloads (also called Singletons) are special scripts that stay loaded across all scenes. They’re perfect for storing global data, such as player stats, game state, settings, or audio managers.

Once registered, they’re available from anywhere in your game using their name.

Why Autoloads Are Needed

Autoloads make it easy to share data between scenes without constantly passing references around.

They’re great for things like:

  • Saving and loading game progress.
  • Keeping player stats between levels.
  • Handling global input or settings.
  • Managing background music, pause menus, or transitions.

They’re not great for:

  • Shared or per-instance values, like enemy health or temporary item states. If you store shared data such as enemy health in an autoload, then when one enemy dies, all others will die too.
  • Objects that exist multiple times in the world, such as bullets, items, or NPCs.
  • Autoloads are global, meaning every scene accesses the same data. Using them for per-object logic can cause synchronization issues or unexpected behavior between instances.

Setup

  1. Create a new script, e.g. Global.gd.
  2. Go to Project → Project Settings → Autoload.
  3. Add your script and give it a name (e.g., Global).
  4. Enable “Singleton.”

Now you can access it anywhere using that name.

Godot 4 Book of Code

Syntax

GDScript:

# GameManager.gd (autoload)
extends Node

var player_health = 100
var coins = 0

func add_coin():
    coins += 1

# Access the autoload directly from any other script
GameManager.add_coin()
print(GameManager.coins)
Enter fullscreen mode Exit fullscreen mode

C#:

// GameManager.cs (autoload)
using Godot;

public partial class GameManager : Node
{
    public int PlayerHealth = 100;
    public int Coins = 0;
    public void AddCoin() => Coins++;
}

// Access the autoload directly from any other script
GameManager game = (GameManager)GD.Load("res://GameManager.cs");
game.AddCoin();
GD.Print(game.Coins);
Enter fullscreen mode Exit fullscreen mode

Godot 4 Book of Code

Top comments (0)