A vertical scrolling space shooter game for the ESP32-C3 microcontroller with OLED display, accelerometer-based tap controls, and rotary encoder navigation.
This game features a player-controlled spaceship that must dodge or destroy incoming obstacles. The game includes multiple difficulty levels, progressive speed increases, and a high score system with persistent storage. Players navigate menus using a rotary encoder and control the ship using physical buttons.
- ESP32-C3 microcontroller
- 128x64 OLED display (I2C, address 0x3C)
- MPU-6050 accelerometer (I2C, address 0x68)
- Rotary encoder with push button
- 3 momentary push buttons (for gameplay)
- PWM-capable buzzer
- Single RGB NeoPixel LED
- OLED Display: I2C bus (SCL, SDA pins)
- MPU-6050 Accelerometer: I2C bus (address 0x68)
- Rotary Encoder: D0 (DT), D1 (CLK), D2 (Button)
- Left Button (move left): D8
- Middle Button (shoot): D10
- Right Button (move right): D7
- Buzzer: D6 (PWM output)
- NeoPixel LED: D3
The hardware components are housed in a custom enclosure designed to resemble a miniature desktop computer setup. This design choice provides both aesthetic appeal and practical organization of the electronic components.
The physical design is inspired by a traditional desktop workstation, creating a recognizable and intuitive form factor that reflects the gaming nature of the project. Fusion file: https://a360.co/3KH3QhY
Display as Monitor The 128x64 OLED display is positioned to serve as the monitor in this miniature setup. It is mounted at an appropriate viewing angle, mimicking how a computer monitor would sit on a desk.
Keyboard-Style Input Layout The three gameplay buttons (left, shoot, right) are arranged in a keyboard-inspired configuration for ergonomic access during gameplay. This layout provides intuitive left-right movement controls with the shooting action centrally positioned for easy thumb access.
PC Tower Enclosure The main electronic components are housed within a structure designed to resemble a desktop PC tower. This enclosure contains:
- ESP32-C3 microcontroller
- Power switch for system control
- RGB NeoPixel LED (visible through the case for status indication)
- MPU-6050 accelerometer (mounted internally)
- Buzzer for audio feedback
- Wiring and power distribution
Desk Base The entire assembly is mounted on a base platform that represents a desk surface. This provides structural stability and completes the miniature workstation aesthetic. The rotary encoder is positioned on this base for menu navigation.
Thematic Coherence The desktop computer form factor is immediately recognizable and appropriate for a video game project, creating a cohesive design narrative.
Component Organization Housing components in distinct sections (tower, keyboard area, monitor) provides clear physical separation that mirrors the logical separation in the code architecture.
User Experience The familiar desktop layout makes the control scheme intuitive. Users immediately understand that the display is for viewing, the keyboard area is for controls, and the tower contains the processing hardware.
Accessibility All interactive elements (buttons, encoder, display) are easily accessible while maintaining a clean, organized appearance. The design minimizes cable clutter by routing connections through the tower enclosure.
code.py Main game loop and entry point. Handles game state management, player input processing, collision detection, and scoring. This file coordinates all game systems and is the first file executed when the device powers on.
hardware.py Centralizes all hardware initialization. Sets up I2C communication, display, accelerometer, buzzer, NeoPixel LED, buttons, and rotary encoder. Provides utility functions for LED control and color conversion.
rotary_encoder.py Implements a state machine-based rotary encoder driver. Uses quadrature decoding to reliably detect rotation direction and track position with automatic wrapping at boundaries.
game_objects.py Defines the Obstacle and Bullet classes. Obstacles move down the screen and can be dodged or destroyed. Bullets move upward and damage obstacles on contact. Each class manages its own position, rendering, and collision detection.
graphics.py Contains all drawing functions for game visuals. Includes the 3D perspective grid background, HUD elements, targeting reticle, player ship rendering, and collision checking logic.
accelerometer_filter.py Implements tap detection with noise filtering using an Exponential Moving Average (EMA) algorithm. Handles accelerometer calibration at startup and provides reliable tap gesture recognition for the level-up mechanic.
animations.py Contains startup animations displayed before the main menu. Includes the Windows logo splash screen and an animated cockpit HUD with a rotating radar display.
menu.py Manages the main menu system, difficulty selection, high score display, and initials entry. Handles persistent storage of high scores to the filesystem and provides rotary encoder-based navigation.
sounds.py Centralizes all audio feedback. Includes sound effects for menu navigation, weapon fire, player movement, game over, and victory. Uses PWM frequency modulation to generate tones.
Menu Navigation
- Rotate encoder: Navigate through menu options
- Press encoder button: Select highlighted option
Gameplay
- Left button: Move spaceship to the left lane
- Middle button: Fire weapon
- Right button: Move spaceship to the right lane
- Tap device: Continue to next level (during level-up sequence)
The game offers three difficulty settings that affect starting conditions and score multipliers:
Easy
- Starting Speed: 2
- Starting Level: 1
- Score Multiplier: 1.0x
Medium
- Starting Speed: 4
- Starting Level: 2
- Score Multiplier: 1.5x
Hard
- Starting Speed: 6
- Starting Level: 3
- Score Multiplier: 2.0x
- Dodging an obstacle (letting it pass): 10 points x multiplier
- Destroying an obstacle with weapon: 20 points x multiplier
- Level progression: Every 100 points advances to the next level
- Victory condition: Reach 300 points
Every 100 points triggers a level-up sequence:
- Screen inverts colors (black and white swap)
- LED flashes white and black alternately
- Player must tap the device within 5 seconds to continue
- Game resumes with increased difficulty
Upon reaching 300 points:
- "YOU WIN!" message displays on screen
- NeoPixel LED cycles through rainbow colors for 3 seconds
- Victory fanfare plays
- Gameplay continues normally after the celebration
- White LED: Normal gameplay
- Blue LED flash: Lane change
- Green LED flash: Weapon fired
- Red LED: Game over
- Rainbow LED: Victory mode
The game maintains a persistent list of the top 5 scores. When a player achieves a qualifying score, they can enter their three-letter initials using the rotary encoder. High scores are saved to the device's filesystem and persist across power cycles.
-
Ensure CircuitPython is installed on your ESP32-C3
-
Copy all Python files to the root directory of the device:
- code.py
- hardware.py
- rotary_encoder.py
- game_objects.py
- graphics.py
- accelerometer_filter.py
- animations.py
- menu.py
- sounds.py
-
Connect all hardware components according to the pin configuration
-
Reset or power cycle the device
The game will automatically start, display animations, and present the main menu.
The accelerometer calibrates automatically during startup. Keep the device still on a flat surface during the "Calibrating..." message. This establishes a baseline for tap detection. The system uses an Exponential Moving Average filter with a 0.3 alpha value to reduce noise and prevent false tap detections.
Display shows nothing
- Verify I2C connections (SCL, SDA)
- Check display address (should be 0x3C)
- Ensure displayio.release_displays() runs before display initialization
Rotary encoder not responding
- Confirm D0 and D1 pin connections
- Verify pull-up resistors are enabled
- Check that encoder button (D2) has proper pull-up
Buttons not working
- Verify button pins: D8 (left), D10 (middle), D7 (right)
- Confirm buttons are connected to ground when pressed
- Check pull-down configuration
Tap detection too sensitive or not working
- Recalibrate by restarting device on a flat surface
- Adjust TAP_THRESHOLD in accelerometer_filter.py (default: 2.5)
- Modify FILTER_ALPHA for more/less smoothing (default: 0.3)
High scores not saving
- Check filesystem permissions
- Verify device has write access to root directory
- Ensure proper storage.remount() calls in menu.py
Difficulty Adjustment Edit starting_speed, starting_level, and score_multiplier values in code.py (around line 42)
Victory Condition
Change the score threshold in code.py (around line 214): if score >= 300
Tap Sensitivity Modify TAP_THRESHOLD in accelerometer_filter.py (line 20)
Sound Effects Adjust frequencies and durations in sounds.py
Visual Elements Modify drawing functions in graphics.py
Filter Responsiveness Change FILTER_ALPHA in accelerometer_filter.py (line 26): lower values = more smoothing
The game uses a state machine architecture with a main loop running at approximately 50 FPS. The rotary encoder employs quadrature decoding for reliable position tracking. Tap detection uses an Exponential Moving Average filter to reduce accelerometer noise. All graphics are rendered to a 1-bit bitmap before display to optimize performance. The menu system uses edge detection for button presses to prevent repeat triggers.
Game developed for ESP32-C3 using CircuitPython and guided by Anthropic Claude. Rotary encoder driver based on state machine principles for quadrature decoding. Sound and visual design inspired by classic arcade games and Stars Wars IV.