Chapter 02
Programming for games
How game code is structured in your engine: the script lifecycle, components, exposed variables, events and the language itself.
Script lifecycle
Also called Awake, _ready, _process, BeginPlay, lifecycle method
- What
- The functions the engine calls on your code at set moments: once when the object is created or enters play, then every frame, then at a fixed physics rate, and finally when it is destroyed.
- Why
- Putting code in the right callback avoids most beginner bugs: setup in the start callback, per-frame logic in update, physics in the fixed-rate callback.
extends CharacterBody2D
func _ready() -> void:
pass # node and children are in the tree
func _process(delta: float) -> void:
pass # every frame
func _physics_process(delta: float) -> void:
pass # fixed physics tickWatch out
Reading another object in the earliest callback before it has initialised itself, which gives null references.
Components and composition
Also called ECS, composition over inheritance
- What
- Building game objects from small parts: a health component, a mover, a sprite, a collider. Unity uses components on GameObjects, Godot uses child nodes, Unreal uses Actor Components.
- Why
- Small reusable parts let you make a new enemy by combining existing pieces, instead of a deep class tree where every change breaks something.
Watch out
One giant player script of 2,000 lines that handles input, health, animation, sound and UI.
Exposed variables
Also called SerializeField, @export, UPROPERTY EditAnywhere
- What
- Variables marked so they appear in the editor, where you or a designer can tune them without touching code.
- Why
- Game feel is found by tweaking numbers such as speed, jump height and cooldowns while the game runs. Exposing them makes tuning fast.
@export var move_speed: float = 300.0
@export var max_health: int = 3
@export var bullet_scene: PackedSceneWatch out
Changing values in the editor while in play mode and losing them all when you stop.
Events, signals and dispatchers
Also called observer pattern, delegates
- What
- A way for an object to announce that something happened, such as "health changed", so that any number of listeners can react without the sender knowing about them.
- Why
- It keeps code decoupled: the health bar, the sound and the achievement system all listen to the player instead of the player calling each one.
signal health_changed(new_health: int)
func take_damage(amount: int) -> void:
health -= amount
health_changed.emit(health)
# elsewhere
player.health_changed.connect(_on_health_changed)Watch out
Forgetting to disconnect or unsubscribe when a listener is destroyed, which causes errors or leaks.
Nodes, scenes and the scene tree
- What
- In Godot everything is a node, nodes form a tree, and a saved branch of nodes is a scene. A scene can be a player, a bullet or a whole level, and scenes nest inside scenes.
- Why
- Scenes are Godot's unit of reuse: you instance a bullet scene to spawn bullets, and a level scene contains instanced player and enemy scenes.
Watch out
Hard-coding long node paths such as get_node("../../UI/Health") that break as soon as the tree changes; prefer exported references or %UniqueName.
GDScript and static typing
- What
- Godot's built-in language, with Python-like indentation, tight editor integration and optional static types written as
var speed: float. - Why
- Typed GDScript gives better autocompletion, catches mistakes in the editor and runs faster. It is the default choice for Godot; C# is available but cannot export to the web in Godot 4.
var speed: float = 200.0
var enemies: Array[Node2D] = []
func heal(amount: int) -> void:
health = mini(health + amount, max_health)Watch out
Leaving everything untyped, which hides typos until the line runs in the middle of a playtest.
State machine
Also called FSM, finite state machine
- What
- Modelling an object as one state at a time, such as idle, running, jumping or attacking, with clear rules for moving between states.
- Why
- It replaces a tangle of booleans (
isJumping,isAttacking,isHurt) with one variable, so impossible combinations cannot happen. Enemy AI and animation both use it. - Example
- An enemy patrols until it sees the player, chases while in range, attacks when close, and returns to patrol if the player escapes for three seconds.
Watch out
Adding a new boolean for every behaviour until the player can attack while dead.
Debugging games
Also called logging, breakpoints, debug draw
- What
- Printing values to the console, pausing on breakpoints in your IDE, and drawing debug lines, shapes and text in the game view to see what the code thinks is happening.
- Why
- Game bugs are often visual or timing related, so seeing a ray or a hitbox on screen finds them faster than reading code.
print("velocity: ", velocity)
push_warning("Enemy spawned outside the arena")
# Debug > Visible Collision Shapes in the editorWatch out
Leaving hundreds of log lines running every frame, which slows the game and hides the message you need.