Přeskočit na obsah

Boot a herní smyčka

Tyto tři moduly jsou páteří každé hry na PicoPadu: picogame_game převezme displej a předá ti Scene, picogame_clock hlídá timing snímků a dává ti dt, a picogame_input převede fyzická tlačítka na bitmasku s detekcí hran. Typický code.py jednou zavolá picogame_game.setup(), vytvoří Buttons() a Clock(), a pak se točí ve smyčce: poll, update, refresh, tick. Čisté signatury najdeš na /cs/reference/.

Inicializační kostra. Zavolej to jednou na začátku hry - zakáže to displayio auto-refresh, vyčistí root group, alokuje dva strip buffery a sestaví ti Scene, takže přeskočíš veškerý renderovací boilerplate. Sáhni po tom, kdykoli chceš herní převzetí obrazovky místo normálního displayio UI.

  • setup(display=None, strip_h=24, background=0, fast=True, top=0, bottom=0, left=0, right=0, rgb444=False) - převezme displej a vrátí tuple (scene, buffer_a, buffer_b). Nastaví display.auto_refresh = False, vyčistí root_group, alokuje dva full-width x strip_h strip buffery (každý width * strip_h * 2 bajtů) a sestaví Scene. Drž všechny tři vrácené hodnoty živé po celou dobu hry - buffery nesmí být garbage-collected.
    • display - displayio displej; výchozí je board.DISPLAY.
    • strip_h - výška každého render stripu v pixelech. Menší hodnota šetří RAM, větší může znamenat méně SPI transakcí. Kompromis popisuje /cs/memory/.
    • background - barva pozadí za scénou jako RGB565 int. Sestav ji pomocí pg.rgb565(r, g, b).
    • top / bottom / left / right - rezervuje okraj (px), do kterého scéna nebude kreslit, takže maluje jen vnitřní herní obdélník. Ten okraj nakreslíš sám (HUD bar, boční panely) jednou a pak se už nepřepočítává na každý snímek.
    • fast - True použije platformový rychlý pg.Display (async DMA kde je k dispozici); False řídí obyčejný busdisplay přes přenosný renderer (funguje všude, pomalejší). Na portu bez DMA backendu setup automaticky přepne.
    • rgb444 - True posílá 12-bit RGB444 místo 16-bit RGB565 (~25% méně SPI provozu, 4096 barev), jen pro rychlý Display a vyžaduje 12-bit kontroler (ST7789/ST7735, ne ILI9341). rgb444="auto" to zapne jen tam, kde board hlásí pg.RGB444_SUPPORTED, takže jeden kód běží optimálně na ST7789 a bezpečně na ILI9341.
import picogame as pg
import picogame_game
BG = pg.rgb565(20, 24, 30)
scene, bufA, bufB = picogame_game.setup(background=BG, strip_h=16, top=12)
# scene is a pg.Scene; add sprites and refresh it each frame
scene.add(sprite)
scene.refresh()

Pozor na: drž scene, bufA a bufB po celou dobu hry - pokud se buffery uvolní, rendering se rozbije. Okraj, který rezervuješ přes top/bottom/left/right, je tvůj na malování; setup jen zastaví scénu, aby tam kreslila. Co Scene očekává popisuje /cs/scene-format/ a poznámky k displejům pro konkrétní boardy najdeš na /cs/hardware/.

Timing snímků. Clock omezí tvoji smyčku na cílové FPS a vrátí reálné dt (uběhlé sekundy), takže pohyb může být nezávislý na snímkové frekvenci a rychlé poklepání na D-pad nevymrští sprite přes celou obrazovku při vysokém FPS. FixedStep je alternativa, když chceš deterministickou logiku: spouští update v stejně velkých krocích bez ohledu na dobu renderování. Používej Clock pro většinu her; sáhni po FixedStep, když fyzika nebo kolize musí být reprodukovatelné.

Clock:

  • Clock(fps=30, max_dt=0.1) - omezí smyčku na fps (použij 0 pro bez omezení) a ořízne vrácené dt na maximálně max_dt sekund, takže pauza nebo zaseknutí nemůže vyrobit obří dt, které teleportuje vše.
  • tick() - čeká do hranice snímku a pak vrátí reálné dt v sekundách od posledního tick(). Kotví se na ideální rozvrh, takže malé překročení se nesmí akumulovat do driftu; pokud jsi přetáhl rozpočet, ukotvuje se na reálný čas a dt zůstane přesné. Volej jednou za snímek.
  • tick_async() - awaitable varianta, která při čekání nečinnosti předá řízení jiným asyncio taskům místo blokování. Vyžaduje dostupnou knihovnu asyncio (jinak vyhodí RuntimeError). Pozor - samotné renderování blokuje, takže async pomůže jen v mezeře cap-sleep.
  • set_fps(fps) - změn cílové FPS za běhu (např. menu na 30, akce na 60). 0 odstraní omezení.

FixedStep:

  • FixedStep(step_fps=60, max_steps=5) - pevný timestep 1/step_fps sekund, spustí nejvýše max_steps logických kroků za snímek (limit zabrání “spirále smrti”, když rendering nestíhá - backlog se zahodí).
  • step_count() - vrátí počet pevných kroků pro tento snímek (0..max_steps). Iteruj for _ in range(step_count()) a používej konstantní self.dt; tato forma nic nealokuje, vhodné pro horké smyčky.
  • dt - konstantní délka kroku v sekundách. Předej ji svému updatu.
  • steps() - generátorová forma, která pro každý krok yielduje self.dt. Pohodlné, ale při každém volání alokuje generátor; v hlavní smyčce raději použij step_count().
import picogame_clock
clock = picogame_clock.Clock(30) # cap to 30 FPS
while True:
dt = clock.tick() # sleeps to the frame boundary, returns real dt
player.x += player.vx * dt # frame-rate independent movement
scene.refresh()

Pozor na: volej tick() přesně jednou za snímek, na konci smyčky. Pokud vrácenou hodnotu ignoruješ, FPS cap stále funguje, ale přijdeš o nezávislost na snímkové frekvenci - to je v pořádku pro mřížkovou hru s pevným pohybem, ne pro plynulý motion. tick_async() ti něco přinese jen tehdy, když rendering může překrývat jiné tasky; samotné volání renderu stále blokuje.

Tlačítka. Buttons čte fyzická tlačítka boardu do logické bitmasky s detekcí hran (právě stisknuto / právě uvolněno) a auto-repeatem, přičemž backend vybírá automaticky podle boardu: modul keypad z CircuitPythonu (hardwarový debounce, skenování na pozadí, fronta událostí, takže rychlé stisky se nikdy neztratí) kde je k dispozici, jinak digitalio polling - stejné API v obou případech. Timer je malý ubývající čítač snímků pro triky se vstupní benevolencí (coyote time, jump buffering). Používej Buttons v každé hře; přidej Timer, když akční hra musí být odpouštějící.

Logická tlačítka jsou přístupná jak jako konstanty modulu, tak jako atributy instance: UP, DOWN, LEFT, RIGHT, A, B, X, Y, L1, L2, R1, R2, START, SELECT, plus ALL. PicoPad mapuje osm face buttonů; chybějící tlačítka (žádná ramena) prostě nikdy nespustí. Jsou to bitové příznaky, takže je můžeš ORovat: btn.A | btn.B.

Buttons:

  • Buttons(profile=None, pull=None, prefer_keypad=True, debounce_s=0.02) - sestaví čtečku. S profile=None se pin mapa určuje podle priority od nejvyšší: explicitní profile, pak settings.toml PICOGAME_BUTTONS = "UP=GP2 A=GP12 ..." (přemapuj vlastní Pico bez reflashování), pak vestavěný profil podle board.board_id, pak fallback PICOPAD. pull výchozí je Pull.UP (nebo PICOGAME_PULL v settings.toml). debounce_s je okno skenování keypad; prefer_keypad=False vynutí polling.
  • poll() - jednou vzorkuje všechna tlačítka a vrátí aktuální bitmasku stisknutých. Volej jednou za snímek, před jakýmkoliv dotazem. Vyprázdní frontu událostí keypad (zachytí sub-frame stisky) nebo přečte piny přímo a aktualizuje počty held-frame pro repeat().
  • is_pressed(mask=ALL) - True pokud je aktuálně stisknuto jakékoli tlačítko v mask (úroveň).
  • just_pressed(mask=ALL) - True na vzestupné hraně (snímek, kdy tlačítko šlo dolů). Na keypad backendu pochází z fronty událostí, takže stisk kratší než jeden snímek se stále zaregistruje.
  • just_released(mask=ALL) - True na sestupné hraně (snímek, kdy tlačítko šlo nahoru).
  • has(mask=ALL) - True pokud tento board fyzicky zapojuje daná tlačítka. Použij to k přizpůsobení ovládání/UI pro boardy bez ramen nebo bez START/SELECT.
  • repeat(button, delay=15, interval=4) - auto-repeat pro JEDNO tlačítko: True ve snímku, kdy je stisknuto, pak každých interval snímků po přidržení delay snímků. Ideální pro pohyb v menu a mřížce.
  • clear() - resetuje stav a vyprázdní čekající vstup. Volej při přechodech mezi scénami nebo menu, aby přidržené tlačítko neprosáklo dál.

Timer:

  • Timer(frames) - čítač, který ubývá jeden snímek po druhém po dobu frames snímků.
  • feed(condition) - dobije na plnou hodnotu pokud je condition pravdivá, jinak odečte jeden snímek; vrátí, zda je stále aktivní. Použij pro coyote time (feed(on_ground)).
  • charge() - vynutí čítač na plnou hodnotu.
  • is_active (property) - True dokud je čítač nad nulou.
  • consume() - jednou vrátí True pokud je aktivní, pak se vynuluje, takže bufferovaný stisk se spustí přesně jednou (jump buffering).
import picogame_input
btn = picogame_input.Buttons() # auto profile by board
while True:
btn.poll()
dx = btn.is_pressed(btn.RIGHT) - btn.is_pressed(btn.LEFT) # -1, 0 or +1
if btn.just_pressed(btn.A): # rising edge: fire once per tap
jump()
scene.refresh()

Coyote time a jump buffering, přímo z příkladu platformovky:

coyote = picogame_input.Timer(5) # still jump a few frames after a ledge
jbuf = picogame_input.Timer(6) # honour a jump pressed just before landing
# each frame:
coyote.feed(on_ground)
jbuf.feed(btn.just_pressed(btn.A))
if coyote.is_active and jbuf.consume():
jump()

Pozor na: vždy volej poll() jednou za snímek před dotazováním, jinak is_pressed / just_pressed čtou zastaralý stav. repeat() bere jedno tlačítko, ne OR-masku. Backend digitalio polling nemá debounce (vzorkování po snímcích už filtruje sub-frame zákmity); pro hlučný přepínač na sestavě bez keypad debounce řeš upstream. Profily boardů a klíče pro přemapování v settings.toml popisuje /cs/hardware/.