Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. GDScript
  3. GDScript Foundations Practice
GDScript32 exercises

GDScript Foundations Practice

Learn the first lines of every Godot 4 script: extends, typed vars, @export, @onready, signals, and lifecycle callbacks (_init, _ready). Includes copy-paste templates + drills.

Common ErrorsQuick ReferencePractice
Warm-up1 / 2

Can you write this from memory?

Write the first line of a GDScript that extends Node2D for your Arena Survivor player.

On this page
  1. 1Quick Definitions
  2. 2The Mental Model
  3. The Script Skeleton
  4. Lifecycle Timeline (the part people mess up)
  5. Why extends Matters
  6. @onready: The Safe Way to Reference Children
  7. Type Everything
  8. Constants
  9. Node References
  10. Common Errors and Fixes
  11. Next Steps
Quick DefinitionsThe Mental Model

Every GDScript file starts the same way: extends a node type, declare variables, reference children with @onready, define _ready. Once you nail the foundations, you can move on to movement, signals, or exports tuning.

These exercises lock in the script skeleton so you can write game logic instead of fighting boilerplate.

Related GDScript Topics
GDScript MovementGDScript ExportsGDScript Timers & SignalsGDScript Scene InstancingGDScript Arrays & Loops

_init is for data, _ready is for nodes. If you need $Child, use @onready or _ready()—the scene tree isn't built during _init.

What does extends do? It declares which Godot class your script inherits from. Without it, your script extends RefCounted (not Node!), meaning no _ready(), no scene tree, no $Child.

When does @onready run? Just before _ready(). It waits until the node and all children are in the scene tree, so $Child references work.

_init vs _ready? _init runs when the object is created in memory (no scene tree). _ready runs once the node and its children have entered the tree. Rule of thumb: _init is for data, _ready is for nodes.


Ready to practice?

Start practicing GDScript Foundations with spaced repetition

A script is a class, a node is an instance, a scene is a packed tree. Always write extends explicitly—without it, your script defaults to RefCounted and loses all lifecycle methods.

Think of it this way: A script is a class. A node is an instance. A scene is a packed tree of nodes.

When you write extends CharacterBody2D, you're creating a class that inherits from Godot's CharacterBody2D. When you drag that script onto a node in the editor, that node becomes an instance of your class.

PackedScene (.tscn file)
    │
    ▼ instantiate()
Node Tree (runtime)
    │
    ▼ add_child()
Live in the game

The Script Skeleton

Every gameplay script follows this pattern:

extends CharacterBody2D  # What class we inherit from

signal health_changed(new_value: int)  # Custom events

@export var speed: float = 200.0  # Inspector-editable
@onready var sprite: Sprite2D = $Sprite2D  # Child reference

func _ready() -> void:
    # Runs once when node enters tree
    pass

func _physics_process(delta: float) -> void:
    # Runs every physics frame (60 Hz)
    pass

Lifecycle Timeline (the part people mess up)

Rule of thumb: _init is for data. _ready is for nodes.

  1. _init() — Runs when the object is created in memory. Scene tree not available yet.
  2. _enter_tree() — Node enters the tree. Still don't assume children are ready.
  3. @onready variables resolve just before _ready().
  4. _ready() — Runs once the node and all its children are in the tree.

If you need $ChildNode, it must be @onready or inside _ready().

func _init() -> void:
    # Object exists, but NOT in tree yet
    # $Child will be null here!
    max_hp = 100  # Data-only initialization is OK

func _ready() -> void:
    # Node + children are in the tree
    sprite = $Sprite2D  # This works now
    hp_bar.max_value = max_hp

Why extends Matters

If you omit extends, your script defaults to extending RefCounted—not Node. This means:

  • No _ready(), _process(), or other lifecycle methods
  • No $Child references
  • No scene tree access

Always write extends explicitly, even for simple scripts.

@onready: The Safe Way to Reference Children

# WRONG: Child doesn't exist yet at _init time
var sprite = $Sprite2D  # Returns null!

# RIGHT: Defers until just before _ready
@onready var sprite: Sprite2D = $Sprite2D

The @onready annotation waits until the node and all its children are in the scene tree before evaluating the expression. This is when $Child paths actually work.

Type Everything

Godot 4 GDScript supports gradual typing. Use it:

# Weak: No type info, no autocomplete
var speed = 200

# Strong: Type hints, editor support, error checking
var speed: float = 200.0
var player: CharacterBody2D
var items: Array[String] = []

# Inferred typing with := (type from right-hand side)
var direction := Vector2.UP  # Inferred as Vector2
var count := 0               # Inferred as int

Types help the editor catch mistakes before you run the game.

Constants

Use const for values that never change:

const MAX_SPEED: float = 400.0
const GRAVITY: float = 980.0
const DIRECTIONS := [Vector2.UP, Vector2.DOWN, Vector2.LEFT, Vector2.RIGHT]

Constants must be assigned at declaration and cannot be modified. Use them for configuration values, magic numbers, and preloaded resources.

Node References

$Child is shorthand for get_node("Child"). It breaks when you rename or move nodes.

@onready var sprite: Sprite2D = $Sprite2D        # Relative path
@onready var player: Node2D = $"../Player"       # Parent's sibling

For refactor-safe references, Godot 4 introduced Unique Names (%NodeName). See scene instancing for the full pattern.

Common Errors and Fixes

Error MessageWhat It MeansFix
Script inherits from native type 'RefCounted'...You forgot extends Node2D (or similar)Add extends at the top of your script
Invalid get index 'Sprite2D' (on base: 'Nil')$Sprite2D is null—accessed too earlyUse @onready or move to _ready()
Node not found: "NodeName"The node path is wrong or doesn't existCheck spelling, path structure, and unique name setup
Identifier "x" not declared in current scopeVariable referenced before declarationDeclare with var x or check for typos
Cannot call method 'X' on null instanceThe object was freed or never initializedAdd null checks or verify node exists

Next Steps

Once you have the foundations locked in:

  • Movement — Input handling, velocity, move_and_slide
  • Exports & Tuning — @export_range, enums, Inspector organization
  • Timers & Signals — Event-driven architecture, await, cooldowns
  • Arrays & Loops — Collections, iteration, dictionary patterns

When to Use GDScript Foundations

  • Starting any new GDScript file—extends, var, and _ready are the skeleton of every script
  • Defining node behavior with exported properties and signal connections in _ready
  • Setting up @onready references to child nodes to avoid repeated get_node calls

Check Your Understanding: GDScript Foundations

Prompt

Check Your Understanding: What is the difference between _ready and _init in GDScript, and when does @onready resolve?

What a strong answer looks like

_init is called when the object is created in memory, before it enters the scene tree. _ready is called once the node and all its children have entered the tree. @onready variables resolve just before _ready runs, making them safe for referencing child nodes. Use _init for data-only initialization and _ready for anything that depends on the scene tree.

What You'll Practice: GDScript Foundations

extends declarationvar and const declarationsType inference with :=@onready node references_init() vs _ready() lifecycle

Common GDScript Foundations Pitfalls

  • Accessing child nodes in _init instead of _ready—the scene tree is not built yet, so $Child returns null
  • Forgetting that @export vars set in the Inspector override values assigned in code—this causes confusion when defaults seem to be ignored
  • Omitting extends and wondering why _ready never runs (script defaults to RefCounted, not Node)
  • Using var when you meant const—if a value should never change, declare it const so the editor catches accidental reassignment

GDScript Foundations FAQ

What does extends do in GDScript?

extends declares which Godot class or script your node inherits from. For example, extends CharacterBody2D gives your script access to all CharacterBody2D methods like move_and_slide(). Every GDScript file must start with extends or it defaults to extending RefCounted (not Node!).

What is the difference between _init and _ready in Godot 4?

_init() runs when the object is created in memory—the scene tree is not available yet, so $Child returns null. _ready() runs after the node and all its children have entered the tree. Use _init for data-only initialization (setting default values). Use _ready for anything that touches the scene tree (child references, signal connections).

How does @onready work in Godot 4 GDScript?

@onready is an annotation that defers variable assignment until just before _ready() runs. This guarantees child nodes exist in the scene tree. For example: @onready var sprite = $Sprite2D. Without @onready, the assignment happens at _init time when child nodes are not yet available.

What is the signal keyword in GDScript?

The signal keyword declares a custom signal on a node. In Godot 4 the syntax is: signal health_changed(new_value: int). Other nodes connect to it via health_changed.connect(callable). Signals decouple nodes so they communicate without direct references. See timers & signals for full patterns.

What does class_name actually do?

class_name registers your script as a global type. Other scripts can reference it by name without preload(), and it appears in the "Add Node" dialog. Use it for scripts you'll reference frequently or want to instantiate by type.

What's the difference between @export and setting a variable in code?

@export exposes the variable to the Inspector, letting you set different values per scene instance. The Inspector value overrides the code default. Use @export for values you want to tweak without editing code. See exports tuning for advanced patterns.

When should I use Node vs Node2D?

Use Node for non-visual logic (game managers, state machines). Use Node2D when you need position, rotation, and scale in 2D space. Node2D is the base for all 2D game objects.

What is the difference between $NodeName and %NodeName?

$NodeName is shorthand for get_node() with a relative path—it breaks if you move nodes. %NodeName uses Godot 4 Unique Names for refactor-safe references. See the scene instancing guide for the full pattern and gotchas.

GDScript Foundations Syntax Quick Reference

Basic script structure
extends CharacterBody2D

signal health_changed(new_value: int)

@export var speed: float = 200.0
@onready var sprite: Sprite2D = $Sprite2D

func _ready() -> void:
	print("Node ready: ", name)
Typed variables and constants
extends Node

const MAX_HP: int = 100
var current_hp: int = MAX_HP
var player_name: String = "Hero"
var items: Array[String] = []

func take_damage(amount: int) -> void:
	current_hp = max(current_hp - amount, 0)
Script as data object (RefCounted)
# No extends = extends RefCounted
# This is a data class, not a Node
class_name PlayerStats

var max_hp: int = 100
var attack: int = 10
var defense: int = 5

GDScript Foundations Sample Exercises

Example 1Difficulty: 1/5

Write the first line of a GDScript that extends CharacterBody2D for a player that uses physics movement.

extends CharacterBody2D
Example 2Difficulty: 1/5

Declare a variable max_enemies with type int and value 10 for your arena's spawn cap.

var max_enemies: int = 10
Example 3Difficulty: 1/5

Declare a variable move_speed with type float and value 150.0 for your player's base speed.

var move_speed: float = 150.0

+ 29 more exercises

Practice in Build a Game

Arena Survivor: Part 1: Your First 2D GameBuild a GameRoguelike: Part 1: Into the CaveBuild a Game

Further Reading

  • Facade Pattern in Godot 4 GDScript: Taming "End Turn" Spaghetti12 min read
  • GDScript Dictionary map() and map_in_place12 min read

Also in Other Languages

Rust Foundations Practice: Variables, Types & Basic Syntax

Start practicing GDScript Foundations

Free daily exercises with spaced repetition. No credit card required.

← Back to GDScript Syntax Practice
Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.