A coding adventure for kids aged 8 and up. 11 chapters. Real GDScript. A complete, playable game by the end — and you write every line yourself.
By the end of Chapter 10 you'll have a fully playable game. Every feature, written by you.
Do one chapter at a time. Test after each one. Don't skip ahead. This is how real programmers work.
Start at Chapter 1 and work through in order. Each chapter adds one piece to the game.
Before building anything visible, create a script that remembers the score, lives, and wave number. This is called GameState.
# GameState.gd — type this exactly extends Node # var creates a variable — a box that holds a number var score: int = 0 var lives: int = 3 var wave: int = 1 var hi_score: int = 0
res:// → New Script → name it GameState.gd → type the code aboveThe player ship moves left/right and fires bullets. It's built from three nodes stacked together.
Player.tscn → attach a new script Player.gd# Player.gd extends Area2D const SPEED = 180.0 const SCREEN_W = 480.0 var can_shoot = true func _process(delta): var dir = int(Input.is_action_pressed("ui_right")) - int(Input.is_action_pressed("ui_left")) position.x = clamp(position.x + dir * SPEED * delta, 20, SCREEN_W - 20) if Input.is_action_just_pressed("ui_accept") and can_shoot: shoot()
Both the player and the aliens fire bullets. Build one bullet scene and reuse it for both — just change the direction.
# Bullet.gd extends Area2D # -1.0 = flies UP (player) +1.0 = flies DOWN (enemy) @export var direction: float = -1.0 @export var speed: float = 400.0 @export var is_enemy: bool = false func _process(delta): position.y += speed * direction * delta # Delete bullet when it leaves the screen if position.y < -20 or position.y > 650: queue_free()
Build a single alien scene. Later, the AlienGrid script will create 55 copies of it automatically.
# Alien.gd extends Area2D signal alien_killed(points, pos) # 0=squid(10pts) 1=crab(20pts) 2=ufo-row(30pts) @export var alien_type: int = 0 @export var points: int = 10 func _ready(): add_to_group("aliens") match alien_type: 0: $AnimatedSprite2D.play("a") 1: $AnimatedSprite2D.play("b") 2: $AnimatedSprite2D.play("c") func die(): alien_killed.emit(points, global_position) queue_free()
The most complex script in the game. Take your time — read each section before typing it. AlienGrid does three jobs:
# AlienGrid.gd — variables section extends Node2D signal all_dead signal reached_bottom signal enemy_bullet_fired(pos) const COLS = 11 const ROWS = 5 const SPACING_X = 36 const SPACING_Y = 32 const ROW_POINTS = [30, 20, 20, 10, 10] var _dir: float = 1.0 var _step_interval: float = 0.8 const AlienScene = preload("res://Alien.tscn")
AlienScene.instantiate() creates a new copy. Like a cookie cutter — load it once, stamp out as many as you need.Four destructible shields the player can hide behind. Both player and alien bullets chip chunks out of them.
Barrier.gd — the script tracks health and destroys the node when it hits zeroBarrier.tscnAlso create an Explosion scene — an AnimatedSprite2D that plays a short animation then deletes itself with queue_free().
# Barrier.gd — simplified extends StaticBody2D var health = 4 func take_hit(): health -= 1 # Update sprite to show damage $Sprite2D.frame = 4 - health if health <= 0: queue_free()
Every game needs pictures. This chapter covers drawing your pixel art sprites and wiring them into the scenes.
Sprites you need to draw:
res://assets/ · Assigned to correct scenesThe Main scene brings everything together — it creates the player, alien grid, and barriers, and connects all the signals.
# Main.gd — signal connections in _ready() func _ready(): $AlienGrid.all_dead.connect(_on_wave_clear) $AlienGrid.reached_bottom.connect(_on_game_over) $AlienGrid.enemy_bullet_fired.connect(_on_enemy_shoot) $Player.player_shot.connect(_on_player_shoot)
This is the moment you've been working toward. Set Main.tscn as the main scene and press F5.
Main.tscnCommon fixes:
Your game works. Now make it yours. Here are challenges to push further.
_step_intervalFileAccessReal code from the actual game. Each pattern appears in the guide with a full explanation before you type it.
func _march(): if _drop_next: _drop_next = false _dir = -_dir position.y += 12 if position.y > 420: reached_bottom.emit() return position.x += _dir * 12
func _on_alien_killed(pts, pos): _alive_count -= 1 # Speed up as fewer aliens remain _step_interval = max( 0.08, 0.8 * (_alive_count / float(COLS * ROWS)) ) if _alive_count <= 0: all_dead.emit()
func _random_shoot(): # Find bottom alien in each column var shooters = [] for c in range(COLS): for r in range(ROWS-1, -1, -1): if is_instance_valid(aliens[r][c]): shooters.append(aliens[r][c]) break
var _anim_t: float = 0.0 func _process(delta): position.y += speed * direction * delta # Enemy bullets zigzag between 2 frames if is_enemy: _anim_t += delta * 8 $Sprite2D.frame = int(_anim_t) % 2