Build
Space Invaders
From Scratch
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.
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
🛠️ Free In-House Dev Tools
Use these free browser tools alongside this workshop to create custom sprites, sounds, levels and colour schemes for your game. No installs. Free forever.