A genuinely new category for this store: a small handheld with a real color screen and real controls, running simple original games you write yourself, the same fundamentals behind every classic handheld.
4 real parts included
Total for this kit: 1,271.00 ฿
Doesn't include any order discount you may get at checkout.
This is the most involved build in the catalog: a real handheld game device with a color screen, a physical control stick, and sound, running games you write entirely yourself. It pulls together display graphics, analog input, and audio into one project, the same three ingredients behind every classic handheld console.
What the Controller Does
It runs a continuous game loop: read the joystick, update game state based on that input, redraw the screen, check for a win or loss condition, and repeat, dozens of times per second. Everything a game actually is comes down to that one loop running fast and consistently.
What the Display Does
The color TFT redraws the game's visuals every single frame: player position, score, obstacles, whatever the game needs shown. Full color opens up genuinely game-like visuals that a small monochrome OLED can't match.
What the Joystick Does
It reports its physical position as two simple analog values plus a digital button click, the exact same input style behind a real game controller, giving the controller everything it needs to move a player character with real, gradual control rather than just on/off directions.
What the Buzzer Does
It supplies simple sound effects and beeps on key game events, scoring, collisions, game over, the small audio cues that make a game feel genuinely responsive.
Software and Library Concepts
Start far simpler than a full game: draw one static shape on screen, then make it move in response to the joystick, then add a second object and simple collision detection. A complete game is just that same loop with more rules layered on top. Start with something as simple as a single moving dot before attempting anything with real graphics.
Testing and Troubleshooting
Redraws look flickery or slow, avoid clearing and redrawing the entire screen every frame, only redraw the specific pixels that actually changed.
The joystick reads jumpy or never quite returns to center, read its resting values once at startup and treat that as zero, rather than assuming it is a perfect midpoint.
This is a lower-volume component listing than most of the catalog, if you notice anything off about the joystick out of the box, it is worth a quick message to the supplier rather than assuming it is normal.
Optional Upgrade
Add the SD card module from the Data Logger build to save high scores that survive a power cycle, a real save file instead of a score that resets every time.
Wiring
ST7789 VCC → ESP32-S3 3.3V
ST7789 GND → ESP32-S3 GND
ST7789 SCK → ESP32-S3 GPIO12
ST7789 SDA (MOSI) → ESP32-S3 GPIO11
ST7789 RES (RST) → ESP32-S3 GPIO8
ST7789 DC → ESP32-S3 GPIO9
ST7789 CS → ESP32-S3 GPIO10
ST7789 BLK → ESP32-S3 GPIO13, or tie directly to 3.3V for an always-on backlight
Joystick VRx → ESP32-S3 GPIO1 (ADC)
Joystick VRy → ESP32-S3 GPIO2 (ADC)
Joystick SW → ESP32-S3 GPIO3 (INPUT_PULLUP, active LOW)
Joystick VCC/GND → ESP32-S3 3.3V/GND
Buzzer signal → ESP32-S3 GPIO4
Buzzer GND → ESP32-S3 GND
2.4" ST7789 panels are commonly 240x320 but vary by supplier — check yours and adjust `SCREEN_WIDTH`/`SCREEN_HEIGHT` and `setRotation()` if the image is mirrored or cropped. This is an active buzzer per the parts list, so it can only click on/off — fine for boundary-hit beeps, but it cannot play a tune or vary pitch (see the Theremin project for tone generation, which needs a passive buzzer instead).
Wiring Diagram
Starter Code
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
// --- Pin definitions: adjust to match your ESP32-S3 board's silkscreen if different ---
#define TFT_CS 10
#define TFT_DC 9
#define TFT_RST 8
#define TFT_SCK 12
#define TFT_MOSI 11
#define TFT_BLK 13 // backlight - tie to 3.3V directly, or drive as PWM for dimming
#define VRX_PIN 1 // joystick X (ADC-capable pin)
#define VRY_PIN 2 // joystick Y (ADC-capable pin)
#define SW_PIN 3 // joystick push button, active LOW
#define BUZZER_PIN 4 // active buzzer signal
// 2.4" ST7789 panels are commonly 240x320 - adjust if yours differs
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320
SPIClass tftSPI(FSPI);
Adafruit_ST7789 tft = Adafruit_ST7789(&tftSPI, TFT_CS, TFT_DC, TFT_RST);
int paddleX, paddleY;
const int PADDLE_SIZE = 20;
const int MOVE_SPEED = 4;
void beep(int durationMs) {
digitalWrite(BUZZER_PIN, HIGH);
delay(durationMs);
digitalWrite(BUZZER_PIN, LOW);
}
void setup() {
Serial.begin(115200);
pinMode(SW_PIN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
pinMode(TFT_BLK, OUTPUT);
digitalWrite(TFT_BLK, HIGH); // backlight on
tftSPI.begin(TFT_SCK, -1, TFT_MOSI, TFT_CS);
tft.init(SCREEN_WIDTH, SCREEN_HEIGHT);
tft.setRotation(2);
tft.fillScreen(ST77XX_BLACK);
paddleX = SCREEN_WIDTH / 2;
paddleY = SCREEN_HEIGHT / 2;
}
void loop() {
int rawX = analogRead(VRX_PIN); // 0-4095 on ESP32 ADC
int rawY = analogRead(VRY_PIN);
int dx = map(rawX, 0, 4095, -MOVE_SPEED, MOVE_SPEED);
int dy = map(rawY, 0, 4095, -MOVE_SPEED, MOVE_SPEED);
// erase old paddle
tft.fillRect(paddleX, paddleY, PADDLE_SIZE, PADDLE_SIZE, ST77XX_BLACK);
paddleX += dx;
paddleY += dy;
bool hitEdge = false;
if (paddleX < 0) { paddleX = 0; hitEdge = true; }
if (paddleX > SCREEN_WIDTH - PADDLE_SIZE) { paddleX = SCREEN_WIDTH - PADDLE_SIZE; hitEdge = true; }
if (paddleY < 0) { paddleY = 0; hitEdge = true; }
if (paddleY > SCREEN_HEIGHT - PADDLE_SIZE) { paddleY = SCREEN_HEIGHT - PADDLE_SIZE; hitEdge = true; }
if (hitEdge) beep(40);
// draw new paddle
tft.fillRect(paddleX, paddleY, PADDLE_SIZE, PADDLE_SIZE, ST77XX_GREEN);
if (digitalRead(SW_PIN) == LOW) {
beep(100); // button press feedback
}
delay(20);
}