Ten projekt to implementacja klasycznej gry Doodle Jump na matrycy LED NeoPixel (16x16) z wykorzystaniem mikrokontrolera ESP32 jako jednostki głównej oraz ESP8266 jako bezprzewodowego kontrolera. Komunikacja między urządzeniami odbywa się za pomocą szybkiego i energooszczędnego protokołu ESP-NOW i jest to komunikacja typu master slave. Całość została zaprogramowana w MicroPython.
Konsola Gry (ESP32 - Slave):
Mikrokontroler: ESP32
Wyświetlacz: Matryca NeoPixel 16x16
Zasilanie: Zasilacz 5V (do zasilania ESP32 i matrycy LED).
Kontroler (ESP8266 - Master):
Mikrokontroler: ESP8266
Sterowanie: Dwa przyciski/Joystick
Zasilanie: Opcjonalnie bateria
Oprogramowanie:
MicroPython Firmware na obu mikrokontrolerach.
Biblioteki MicroPython
Konsola Gry (ESP32 - Slave):
Mikrokontroler: ESP32
Wyświetlacz: Matryca NeoPixel 16x16
Zasilanie: Zasilacz 5V (do zasilania ESP32 i matrycy LED).
Kontroler (ESP8266 - Master):
Mikrokontroler: ESP8266
Sterowanie: Dwa przyciski/Joystick
Zasilanie: Opcjonalnie bateria
Oprogramowanie:
MicroPython Firmware na obu mikrokontrolerach.
Biblioteki MicroPython
1. Konsola Gry (ESP32)
Ekran Gry: Matryca 16x16 NeoPixel jest sercem konsoli. Funkcja set_pixel zawiera logikę mapowania współrzędnych (x, y) na liniowy indeks LED, uwzględniając serpentynowe połączenie diod.
Logika Gry: Jest to uproszczona wersja Doodle Jump. Gracz (3x2 piksele) spada zgodnie z prawami fizyki i porusza się w poziomie.
- Owinięcie Ekranu: Pozycja gracza w poziomie jest cykliczna – wyjście poza lewą krawędź powoduje pojawienie się po prawej stronie i odwrotnie.
- Platformy: Platformy są generowane losowo i przesuwane w dół, gdy gracz osiągnie próg przewijania
- Kolizje: Kluczową mechaniką jest wykrywanie kolizji tylko podczas spadania. Po kolizji z platformą prędkość pionowa jest natychmiast ustawiana na prędkość skoku, symulując odbicie.
Komunikacja: ESP32 działa w trybie Station. Używa modułu espnow do ciągłego nasłuchiwania pakietów. W głównej pętli, pakiety odebrane są dekodowane na polecenia sterujące ('L', 'R', 'S') i używane do ustawienia zmiennej direction.
2. Joystick (ESP8266)
Użycie ESP-NOW: ESP8266 również działa w trybie Station. Po zainicjowaniu modułu espnow, dodaje on ESP32 jako sparowanego partnera
Odczyt Joysticka: Przyciski są skonfigurowane jako wejścia z wewnętrznym podciąganiem, co oznacza, że wartość 0 oznacza wciśnięcie.
Tryb Ciągły: Kontroler działa w pętli ciągłej, co 50ms. W każdej iteracji określa aktualny stan (L, R lub domyślnie S - STOP) i wysyła go do ESP32. Takie ciągłe wysyłanie zapewnia płynne i responsywne sterowanie, działając w praktyce jak bezprzewodowy kabel danych.
Projekt jest solidną demonstracją, jak połączyć interaktywną grafikę LED z fizyką gry i niezawodną komunikacją bezprzewodową między mikrokontrolerami.
Kod do joysticka czyli do mikroprocesora esp8266:
import network
import espnow
import machine
import time
# --- KONFIGURACJA ---
# WPISZ TU ADRES MAC ESP32 (Slave)
target_mac = b'\x24\x6f\x28\xaf\xdb\x78'
PIN_LEFT = 13 # D1
PIN_RIGHT = 12 # D2
btn_left = machine.Pin(PIN_LEFT, machine.Pin.IN, machine.Pin.PULL_UP)
btn_right = machine.Pin(PIN_RIGHT, machine.Pin.IN, machine.Pin.PULL_UP)
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.disconnect()
wlan.config(channel=1)
e = espnow.ESPNow()
e.active(True)
try:
e.add_peer(target_mac)
except:
pass
print("Pilot gotowy (Continuous Mode).")
while True:
msg = b'S' # Domyślnie STOP
# Logika - jeśli wciśnięty, nadpisz wiadomość
if btn_left.value() == 0:
msg = b'L'
elif btn_right.value() == 0:
msg = b'R'
# Wysyłaj ciągle (działa jak kabel)
try:
e.send(target_mac, msg)
except:
pass
# Krótkie opóźnienie, żeby nie zapchać sieci, ale mieć płynność
time.sleep(0.05)
Kod do ramki NeoPixel (esp32):
import time
import random
import network
import espnow
from machine import Pin
from neopixel import NeoPixel
# --- KONFIGURACJA SIECI ---
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.disconnect()
wlan.config(channel=1)
e = espnow.ESPNow()
e.active(True)
# --- MATRYCA 16x16 ---
SCREEN_WIDTH = 16
SCREEN_HEIGHT = 16
PIN_NEO = 16
np = NeoPixel(Pin(PIN_NEO, Pin.OUT), SCREEN_WIDTH * SCREEN_HEIGHT)
# --- FIZYKA GRY ---
player_x = 7.0
player_y = 10.0
direction = "STOP"
player_height = 2
player_draw_width = 2
SCROLL_THRESHOLD = 8
PLAYER_SPEED = 0.8
ay = 0.15
jump_height = 6.0
jump_speed = -((2 * ay * jump_height) ** 0.5)
# --- KOLORY ---
C_PLAYER = (50, 0, 50)
C_PLATFORM = (0, 30, 0)
C_BG = (0, 0, 0)
vy = 0.0
# --- GENEROWANIE PLATFORM ---
platforms = []
platforms.append([6, 14, 4])
for _ in range(6):
w = random.randrange(3, 6) # ZAMIANA RANDINT NA RANDRANGE
max_x = SCREEN_WIDTH - w
if max_x <= 0: max_x = 1
x = random.randrange(0, max_x + 1)
y = random.randrange(0, SCREEN_HEIGHT - 2)
platforms.append([x, y, w])
# --- FUNKCJA RYSOWANIA ---
def set_pixel(x, y, color):
if x < 0: x = 0
if x >= SCREEN_WIDTH: x = SCREEN_WIDTH - 1
if y < 0: y = 0
if y >= SCREEN_HEIGHT: y = SCREEN_HEIGHT - 1
x = int(x)
y = int(y)
if y % 2 == 0:
idx = y * SCREEN_WIDTH + x
else:
idx = y * SCREEN_WIDTH + (SCREEN_WIDTH - 1 - x)
np[idx] = color
def draw():
np.fill(C_BG)
for p in platforms:
px, py, pw = p
if 0 <= py < SCREEN_HEIGHT:
for i in range(pw):
set_pixel(px + i, py, C_PLATFORM)
px = int(player_x)
py = int(player_y)
px_wrapped = px % SCREEN_WIDTH
set_pixel(px_wrapped, py, C_PLAYER)
set_pixel((px_wrapped+1)%SCREEN_WIDTH, py, C_PLAYER)
set_pixel(px_wrapped, py+1, C_PLAYER)
set_pixel((px_wrapped+1)%SCREEN_WIDTH, py+1, C_PLAYER)
np.write()
print("GRA START!")
# --- GŁÓWNA PĘTLA ---
while True:
# 1. ODCZYT WIFI
while True:
host, msg = e.recv()
if msg:
if msg == b'L': direction = "LEFT"
elif msg == b'R': direction = "RIGHT"
elif msg == b'S': direction = "STOP"
else:
break
# 2. RUCH
if direction == "LEFT":
player_x -= PLAYER_SPEED
elif direction == "RIGHT":
player_x += PLAYER_SPEED
if player_x < 0: player_x += SCREEN_WIDTH
if player_x >= SCREEN_WIDTH: player_x -= SCREEN_WIDTH
vy += ay
player_y += vy
# 3. KOLIZJE
if vy > 0:
player_bottom = player_y + player_height
for p in platforms:
plat_x, plat_y, plat_w = p
if plat_y - 1 <= player_bottom <= plat_y + 1.5:
p_left = player_x % SCREEN_WIDTH
p_right = (player_x + player_draw_width) % SCREEN_WIDTH
if (plat_x <= p_left < plat_x + plat_w) or (plat_x <= p_right < plat_x + plat_w):
vy = jump_speed
break
# 4. PRZEWIJANIE (POPRAWIONE RANDINT -> RANDRANGE)
if player_y < SCROLL_THRESHOLD:
offset = SCROLL_THRESHOLD - player_y
player_y = SCROLL_THRESHOLD
for p in platforms:
p[1] += offset
if p[1] > SCREEN_HEIGHT:
p[1] = 0
p[2] = random.randrange(3, 6) # POPRAWKA
max_x = SCREEN_WIDTH - p[2]
if max_x <= 0: max_x = 1
p[0] = random.randrange(0, max_x + 1) # POPRAWKA
if player_y > SCREEN_HEIGHT - player_height:
vy = jump_speed
player_y = SCREEN_HEIGHT - player_height
draw()
time.sleep(0.02)