Space Invaders Example¶
A complete implementation of the classic Space Invaders game showcasing advanced PixelRoot32 features including sprite animations, collision detection with sweep tests, dynamic music tempo, tilemap backgrounds, and efficient entity management.
Overview¶
This example demonstrates a fully playable Space Invaders game with: - Player ship with horizontal movement and shooting - Formation of 32 aliens (4 rows × 8 columns) with different types - Defensive bunkers that degrade when hit - Dynamic background music that speeds up as aliens approach - Visual explosion effects - Score and lives system - Win/lose conditions
Architecture¶
Scene Structure¶
SpaceInvadersScene (SpaceInvadersScene.h/cpp) - Main game scene managing all entities and game state - Handles game loop, collisions, spawning, and state transitions - Implements custom entity management to work within MAX_ENTITIES limit
Entity Classes¶
PlayerActor¶
- Type:
PhysicsActor - Features:
- Horizontal movement controlled by LEFT/RIGHT buttons
- Shooting with FIRE button
- Physics-based movement with world boundaries
- Respawn system after being hit
AlienActor¶
- Type:
Actor - Features:
- Three types: SQUID (top row, 30 points), CRAB (middle rows, 20 points), OCTOPUS (bottom rows, 10 points)
- Step-based sprite animations (alternates frames on movement)
- Formation movement (moves as a group, drops down when hitting edges)
- Uses MultiSprite for CRAB type (multi-layer sprite)
ProjectileActor¶
- Type:
PhysicsActor - Features:
- Two types: PLAYER_BULLET (white, moves up) and ENEMY_BULLET (red, moves down)
- Object pooling (reuses projectiles instead of creating/destroying)
- Sweep test collision detection for fast-moving projectiles
- Tracks previous position for accurate collision detection
BunkerActor¶
- Type:
Actor - Features:
- Health system (starts at 4, decreases when hit)
- Visual degradation (color changes: Green → Yellow → Red)
- Collision detection with dynamic hitbox based on remaining health
Background¶
TilemapBackground - Starfield created using tilemap system - Procedurally generated pattern - Rendered on layer 0 (background)
Key Systems¶
Collision Detection¶
The game uses sweep tests for accurate collision detection with fast-moving projectiles:
// Example from SpaceInvadersScene::handleCollisions()
Circle startCircle;
startCircle.x = proj->getPreviousX() + radius;
startCircle.y = proj->getPreviousY() + PROJECTILE_HEIGHT * 0.5f;
startCircle.radius = radius;
Circle endCircle;
endCircle.x = proj->x + radius;
endCircle.y = proj->y + PROJECTILE_HEIGHT * 0.5f;
endCircle.radius = radius;
float tHit = 0.0f;
if (sweepCircleVsRect(startCircle, endCircle, targetBox, tHit)) {
// Collision detected
}
Why sweep tests? - Projectiles move fast (120 px/s) - Standard AABB can miss collisions between frames - Sweep tests check the path between previous and current position
Entity Management¶
Due to the MAX_ENTITIES = 32 limit, the scene uses custom entity management:
// Custom vectors instead of Scene's entity system
std::vector<AlienActor*> aliens;
std::vector<ProjectileActor*> projectiles;
std::vector<BunkerActor*> bunkers;
Object Pooling: - Projectiles are pre-allocated (MaxProjectiles = 12) - Projectiles are reused instead of created/destroyed - reset() method reactivates projectiles
Scene Arena (Optional): - Uses PIXELROOT32_ENABLE_SCENE_ARENA if available - Pre-allocates memory for all entities - Avoids heap fragmentation
Dynamic Music Tempo¶
The background music tempo increases as aliens get closer to the player:
void SpaceInvadersScene::updateMusicTempo() {
// Find lowest active alien
float lowestY = /* ... */;
// Calculate threat factor (0.0 to 1.0)
float threatFactor = (lowestY - ALIEN_START_Y) * INV_Y_RANGE;
// Target tempo: 1.0 (slow) to 1.9 (fast)
float targetTempo = 1.0f + (threatFactor * 0.9f);
// Smooth interpolation
currentMusicTempoFactor += (targetTempo - currentMusicTempoFactor) * 0.05f;
engine.getMusicPlayer().setTempoFactor(currentMusicTempoFactor);
}
Music Tracks: - BGM_SLOW_TRACK: Initial tempo (slow) - BGM_MEDIUM_TRACK: Medium tempo (unused, can be used for transitions) - BGM_FAST_TRACK: Fast tempo (unused, can be used for transitions) - WIN_TRACK: Victory music (plays once) - GAME_OVER_TRACK: Defeat music (plays once)
Visual Effects¶
Enemy Explosions¶
- Simple cross pattern (horizontal + vertical lines)
- Pool of 8 explosion effects
- 200ms duration each
- Reused slots to avoid allocations
Player Explosion¶
- 3-frame sprite animation
- Plays when player is hit
- Pauses gameplay during animation
- Respawns player after animation completes
Alien Formation Movement¶
Aliens move in lockstep formation:
void SpaceInvadersScene::updateAliens(unsigned long deltaTime) {
stepTimer += scaledDelta;
if (stepTimer >= stepDelay) {
// Check if formation hit edge
bool edgeHit = /* ... */;
if (edgeHit) {
moveDirection *= -1; // Reverse direction
// Drop down
for (auto* alien : aliens) {
alien->move(0, ALIEN_DROP_AMOUNT);
}
} else {
// Move horizontally
float dx = moveDirection * ALIEN_STEP_AMOUNT_X;
for (auto* alien : aliens) {
alien->move(dx, 0);
}
}
}
}
Movement Characteristics: - Step-based (not continuous) - Base step delay: 417ms (72 BPM) - Step amount: 2.5 pixels horizontally - Drop amount: 7 pixels when hitting edge - Tempo scales with music tempo factor
Enemy Shooting System¶
Enemies shoot from bottom-most aliens:
void SpaceInvadersScene::enemyShoot() {
// Find bottom-most aliens (no alien below them)
std::vector<AlienActor*> bottomAliens;
// ... collect bottom aliens ...
// Difficulty-based chance (increases as aliens die)
float t = 1.0f - (alive / total);
int chance = minChance + (t * (maxChance - minChance));
// Random roll
if (roll >= chance) {
// Fire from random bottom alien
}
}
Shooting Rules: - Only bottom-most aliens can shoot - Maximum 4 enemy bullets active at once - Chance increases as more aliens are destroyed - Difficulty scales from 8% to 30% chance
Code Structure¶
File Organization¶
SpaceInvaders/
├── SpaceInvadersScene.h/cpp # Main scene
├── PlayerActor.h/cpp # Player ship
├── AlienActor.h/cpp # Enemy aliens
├── ProjectileActor.h/cpp # Bullets
├── BunkerActor.h/cpp # Defensive bunkers
├── GameConstants.h # Game configuration
└── AlienSprites.h # Sprite definitions
Game Constants¶
Key constants defined in GameConstants.h:
// Formation
constexpr int ALIEN_ROWS = 4;
constexpr int ALIEN_COLS = 8;
// Movement
constexpr unsigned long BASE_STEP_DELAY = 417; // 72 BPM
constexpr float ALIEN_STEP_AMOUNT_X = 2.5f;
constexpr float ALIEN_DROP_AMOUNT = 7.0f;
// Projectiles
constexpr int MaxProjectiles = 12;
constexpr float PROJECTILE_SPEED = 120.0f;
// Bunkers
constexpr int BUNKER_COUNT = 4;
constexpr int BUNKER_WIDTH = 24;
constexpr int BUNKER_HEIGHT = 16;
Design Patterns Used¶
1. Object Pooling¶
Projectiles are pooled to avoid allocations:
// Pre-allocate in resetGame()
for (int i = 0; i < MaxProjectiles; ++i) {
ProjectileActor* projectile = new ProjectileActor(0, -PROJECTILE_HEIGHT, ProjectileType::PLAYER_BULLET);
projectile->deactivate();
projectiles.push_back(projectile);
}
// Reuse when shooting
for (auto* proj : projectiles) {
if (!proj->isActive()) {
proj->reset(px, py, ProjectileType::PLAYER_BULLET);
break;
}
}
2. State Machine¶
Game states: Playing → Paused (on hit) → Game Over
if (gameOver) {
// Game over state
} else if (isPaused) {
// Paused during player explosion
if (!playerExplosion.isActive()) {
respawnPlayerUnderBunker();
isPaused = false;
}
} else {
// Normal gameplay
}
3. Formation Controller¶
Aliens are moved as a group by the scene, not individually:
// Scene controls movement
for (auto* alien : aliens) {
if (alien->isActive()) {
alien->move(dx, 0); // Scene tells alien to move
}
}
4. Explosion Pool¶
Enemy explosions use a fixed pool:
static constexpr int MaxEnemyExplosions = 8;
EnemyExplosion enemyExplosions[MaxEnemyExplosions];
void spawnEnemyExplosion(float x, float y) {
// Find first available slot
for (int i = 0; i < MaxEnemyExplosions; ++i) {
if (!enemyExplosions[i].active) {
enemyExplosions[i].active = true;
enemyExplosions[i].x = x;
enemyExplosions[i].y = y;
enemyExplosions[i].remainingMs = 200;
return;
}
}
}
Audio Integration¶
Sound Effects¶
Player Shoot:
AudioEvent event{};
event.type = WaveType::PULSE;
event.frequency = 880.0f;
event.duration = 0.08f;
event.volume = 0.4f;
event.duty = 0.5f;
engine.getAudioEngine().playEvent(event);
Enemy Hit:
AudioEvent event{};
event.type = WaveType::NOISE;
event.frequency = 600.0f;
event.duration = 0.12f;
event.volume = 0.6f;
engine.getAudioEngine().playEvent(event);
Player Hit:
AudioEvent event{};
event.type = WaveType::NOISE;
event.frequency = 400.0f;
event.duration = 0.18f;
event.volume = 0.7f;
engine.getAudioEngine().playEvent(event);
Background Music¶
- Uses
MusicPlayerwith tempo control - Tempo dynamically adjusts based on threat level
- Different tracks for win/lose conditions
Sprite System¶
Sprite Formats¶
- 1bpp sprites: Player, most aliens, explosions
- MultiSprite: CRAB alien type (multi-layer sprite)
- Animations: Step-based (advances on movement, not time)
Sprite Scaling¶
All sprites use SPRITE_SCALE = 1.25f for larger appearance:
renderer.drawSprite(PLAYER_SHIP_SPRITE,
static_cast<int>(x),
static_cast<int>(y),
SPRITE_SCALE, // scaleX
SPRITE_SCALE, // scaleY
Color::White);
Performance Optimizations¶
1. Entity Pooling¶
- Projectiles are pooled (12 max)
- Explosions are pooled (8 max)
- Avoids allocations during gameplay
2. Scene Arena (Optional)¶
- Pre-allocates 8KB buffer for entities
- All entities allocated from arena
- Zero heap fragmentation
3. Efficient Collision Detection¶
- Sweep tests only for fast projectiles
- Simple AABB for static/slow objects
- Early exits in collision loops
4. Custom Entity Management¶
- Bypasses Scene's entity limit by using vectors
- Direct control over entity lifecycle
- More efficient for this specific use case
Game Flow¶
Initialization¶
- Create tilemap background
- Spawn player at bottom center
- Spawn 32 aliens in formation (4×8 grid)
- Spawn 4 bunkers evenly spaced
- Pre-allocate projectile pool
- Start background music
Gameplay Loop¶
- Update Input: Read player movement and shooting
- Update Aliens: Move formation, check edges, enemy shooting
- Update Projectiles: Move projectiles, check boundaries
- Handle Collisions: Sweep tests for projectiles vs aliens/bunkers/player
- Update Effects: Explosion animations
- Update Music: Adjust tempo based on alien position
- Draw: Background, entities, explosions, HUD
Game Over Conditions¶
- Win: All aliens destroyed
- Lose: Player lives reach 0 OR aliens reach player Y position
Respawn System¶
- Player hit → Start explosion animation
- Pause gameplay
- Wait for explosion to complete
- Respawn player under first intact bunker
- Resume gameplay
Key Learnings¶
What This Example Demonstrates¶
- Advanced Collision Detection
- Sweep tests for fast-moving objects
-
Accurate hit detection even at high speeds
-
Efficient Entity Management
- Object pooling for frequently created/destroyed entities
- Custom management to work around MAX_ENTITIES limit
-
Scene Arena for zero-fragmentation allocation
-
Dynamic Audio
- Music tempo synchronization with gameplay
- Multiple music tracks for different states
-
Sound effects for game events
-
Sprite Animations
- Step-based animations (tied to game logic, not time)
- MultiSprite for complex sprites
-
Sprite scaling for visual variety
-
State Management
- Game states (playing, paused, game over)
- Smooth transitions between states
-
Respawn system with visual feedback
-
Formation Movement
- Coordinated group movement
- Edge detection and direction reversal
-
Progressive difficulty (tempo increases)
-
Visual Effects
- Explosion animations
- Health-based rendering (bunkers)
- HUD with score and lives
Code Examples¶
Shooting System¶
// Player shooting
if (fireInputReady && player->wantsToShoot()) {
// Check if player bullet already active
bool hasPlayerBullet = false;
for (auto* proj : projectiles) {
if (proj->isActive() && proj->getType() == ProjectileType::PLAYER_BULLET) {
hasPlayerBullet = true;
break;
}
}
if (!hasPlayerBullet) {
// Find inactive projectile and reuse it
for (auto* proj : projectiles) {
if (!proj->isActive()) {
float px = player->x + (PLAYER_WIDTH - PROJECTILE_WIDTH) / 2.0f;
float py = player->y - PROJECTILE_HEIGHT;
proj->reset(px, py, ProjectileType::PLAYER_BULLET);
// Play shoot sound
AudioEvent event{};
event.type = WaveType::PULSE;
event.frequency = 880.0f;
event.duration = 0.08f;
event.volume = 0.4f;
engine.getAudioEngine().playEvent(event);
break;
}
}
}
}
Collision Detection with Sweep Test¶
// Check projectile vs alien collision
Circle startCircle;
startCircle.x = proj->getPreviousX() + radius;
startCircle.y = proj->getPreviousY() + PROJECTILE_HEIGHT * 0.5f;
startCircle.radius = radius;
Circle endCircle;
endCircle.x = proj->x + radius;
endCircle.y = proj->y + PROJECTILE_HEIGHT * 0.5f;
endCircle.radius = radius;
float tHit = 0.0f;
pixelroot32::core::Rect targetBox = alien->getHitBox();
if (sweepCircleVsRect(startCircle, endCircle, targetBox, tHit) ||
proj->getHitBox().intersects(targetBox)) {
// Hit! Deactivate projectile and kill alien
proj->deactivate();
alien->kill();
score += alien->getScoreValue();
spawnEnemyExplosion(alien->x + alien->width * 0.5f,
alien->y + alien->height * 0.5f);
}
Running the Example¶
Prerequisites¶
- PixelRoot32 Game Engine installed
- ESP32 or Native build configured
- Display and input configured
Build and Run¶
-
Include the example in your project:
-
In your main.cpp:
-
Build and upload (ESP32) or run (Native)
Controls¶
- LEFT/RIGHT: Move player ship
- FIRE: Shoot projectile
- FIRE (on game over): Restart game
See Also¶
- Game Examples Guide - Analysis of all game examples
- Manual - Scenes and Entities - Scene system
- Manual - Physics and Collisions - Collision detection
- Manual - Audio - Audio system
- Manual - Sprites and Animation - Sprite system
- Manual - Memory Management - Object pooling