Gra Doodle Jump na matrycy NeoPixel ze sterowaniem bezprzewodowym przy użyciu joysticka

Typ_projektu
microPython
Zdjecie główne
Krótki opis projektu

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.

Niezbędne elementy

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

Sprzęt

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

Opis projektu

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.

Zdjęcia
kod programu
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)


 

Schemat
Youtube
Tagi
#gra #doodlejump #LED #esp32 #joystick