Přeskočit na obsah

picogame — 2D herní engine pro PicoPad (CircuitPython)

picogame je experimentální C modul pro CircuitPython: rychlý 2D herní engine v retained-mode pro Pajenicko PicoPad (RP2040, 320×240 ST7789). Přebírá techniky vykreslování z PicoLibSDK a koncepčně představuje „úplnější _stage“ — sprite libovolné velikosti, retained scénu s vykreslováním pomocí dirty-rectangle, tilemapy a asynchronní DMA backend displeje.

  • Stav: experimentální. Jádro (sprite, async-DMA Display, retained Scene + dirty-rect, tilemapy, kolize) je hotové a ověřené na hardwaru.
  • Výkon: nejhorší případ (48 pohyblivých sprite, celá obrazovka) ~33 FPS; typicky (statické pozadí + lokalizovaný pohyb přes dirty-rect) 140+ FPS.

  1. Koncepty
  2. Rychlý start
  3. API reference
  4. Text a fonty
  5. Asset pipeline
  6. Výkon a omezení
  7. Sestavení firmwaru
  8. Příklady

Retained mode. Scénu (Scene) postavíte jednou (přidáte sprite a tilemapy), pak v každém frame jen měníte objekty (sprite.x = ...) a voláte scene.refresh(). Engine porovná stav s předchozím frame a překreslí jen změněnou oblast (dirty rectangle), přičemž přes SPI přenese pouze tyto pixely. Nic se nezměnilo → nic se neposílá.

Barvy ve wire-order. Všechny barvy (pozadí, položky palette, RGB565 pixely) se ukládají v pořadí bajtů na sběrnici displeje (wire byte order). Barvy vždy vytvářejte pomocí picogame.rgb565(r, g, b) — nepředávejte syrové 0xRRGGBB ani naivní RGB565.

Dva backendy displeje:

  • picogame.Displayrychlý: obaluje existující busdisplay, posílá pixely přes asynchronní DMA s dvojitým bufferem (překrývá CPU blit s SPI přenosem). Funguje s libovolným MIPI SPI TFT. Použijte tento se Scene.
  • picogame.render(...)univerzální: kreslení v immediate-mode přes libovolný busdisplay (jakýkoli řadič podporovaný displayio), blokující/pomalejší. Záloha / pro rychlé vykreslení HUD.

Strip buffery. Vykreslování pracuje v horizontálních proužcích (strips). Poskytujete znovupoužitelné buffery (bytearray). Rychlá cesta potřebuje dva buffery (dvojité bufferování); každý musí mít alespoň region_width * 2 bajtů. Běžná velikost je celá šířka × 24 řádků = 320 * 24 * 2 = 15360 bajtů na každý.

Souřadnice. Počátek vlevo nahoře. Obdélníky předávané do vykreslování jsou x0,y0 (včetně) až x1,y1 (vyjma).


import time, array, board
import picogame as pg
display = board.DISPLAY
display.auto_refresh = False # the engine drives the display itself
display.root_group = None
W, H = 320, 240
bufA = bytearray(W * 24 * 2)
bufB = bytearray(W * 24 * 2)
BG = pg.rgb565(20, 24, 40)
# A 16x16 paletted sprite (index 0 transparent).
pal = array.array("H", [pg.rgb565(0, 0, 0), pg.rgb565(230, 80, 80)])
data = bytearray(16 * 16)
for y in range(16):
for x in range(16):
if 3 <= x < 13 and 3 <= y < 13:
data[y * 16 + x] = 1
hero_bmp = pg.Bitmap(data, 16, 16, format=pg.PAL8, palette=pal, transparent=0)
disp = pg.Display(display)
scene = pg.Scene(disp, bufA, bufB, background=BG)
hero = pg.Sprite(hero_bmp, 150, 110)
scene.add(hero)
while True:
hero.x = (hero.x + 1) % (W - 16) # mutate
scene.refresh() # only the hero's area is repainted
time.sleep(1 / 60)

NázevPopis
RGB565konstanta formátu (16bitová barva, wire order)
PAL8konstanta formátu (8bitový index do palette)
rgb565(r, g, b) -> intvytvoří wire-order RGB565 barvu z 8bitových složek
collide(x1, y1, x2, y2, ax1, ay1, ax2, ay2) -> boolpřekryv AABB box↔box; inkluzivní hranice — boxy kolidují při doteku (boxy spritu předávej jako (x, y, x+w, y+h); spustí se při kontaktu). collide je inkluzivní, na rozdíl od půlotevřených pixelových rozsahů u render — jiné domény (hitboxy vs pixely)
collide(x1, y1, x2, y2, px, py) -> boolbox↔bod (6 argumentů)
render(display, sprites, buffer, x0, y0, x1, y1, *, background=0)univerzální immediate vykreslení seznamu sprite do busdisplay
value2d(x, y, *, seed=0) -> floathladký 2-D value noise, 0..1 (rychlé C)
value1d(x, *, seed=0) -> floathladký 1-D value noise, 0..1
fbm2d(x, y, *, octaves=4, seed=0, lacunarity=2.0, gain=0.5) -> floatfraktální (fBm) 2-D noise, 0..1 — terén/mraky/jeskyně
fbm1d(x, *, octaves=4, seed=0, lacunarity=2.0, gain=0.5) -> floatfraktální (fBm) 1-D noise, 0..1

Noise je interně fixed-point (Q16.16) — rychlý na RP2040 (bez FPU) a určený pro jednorázové generování terénu/mraků, ne pro každý frame. Žádné samostatné exporty _fx neexistují; value2d/value1d/fbm2d/fbm1d jsou kanonické funkce, volané přímo na modulu picogame (pg.value2d, pg.fbm2d, …); simulátor poskytuje odpovídající implementaci v Pythonu.

Bitmap(data, width, height, *, format=RGB565, palette=None, frames=1, stride=0, transparent=None)

Sekce “Bitmap(data, width, height, *, format=RGB565, palette=None, frames=1, stride=0, transparent=None)”

Obrazový atlas jednoho či více stejně velkých frame, libovolné šířky/výšky.

  • data — čitelný buffer: PAL8 = 1 bajt/pixel index; RGB565 = 2 bajty/pixel (LE wire).
  • palette — pro PAL8 buffer položek wire-order RGB565 (např. array("H", [...])).
  • frames — frame animace uspořádané vodorovně; frame f je ve sloupci f*width.
  • stride — šířka atlasu v pixelech (výchozí width*frames).
  • transparent — index palette (PAL8) nebo wire barva (RGB565), která se přeskočí; None = neprůhledné.
  • Read-only vlastnosti: width, height, frames, format, stride, palette (PAL8 paleta buffer nebo None), transparent (transparentní hodnota nebo None).

Sprite(bitmap, x=0, y=0, *, frame=0, visible=True, flip_x=False, flip_y=False)

Sekce “Sprite(bitmap, x=0, y=0, *, frame=0, visible=True, flip_x=False, flip_y=False)”

Umístěná, animovatelná instance Bitmap.

  • Vlastnosti: x, y (celočíselný pixel; setter přijímá i float), fx, fy (sub-pixelová float pozice), frame, visible, flip_x, flip_y, data, bitmap, scale, angle, shadow.
  • move(x, y) — nastaví pozici (přijímá int nebo float).
  • scale — rovnoměrné měřítko vykreslení (float, nearest-neighbour). 1.0 = nativní (rychlá cesta 1:1); 2.0 = dvojnásobná velikost; zlomky povoleny (např. mince pulzující 1.0..1.3, rostoucí powerup). Škáluje kolem anchor.
  • angle — rotace ve stupních kolem anchoru (float). 0 = žádná (rychlá cesta); jakákoli jiná hodnota použije afinní (inverzně mapovaný) blit. Celočíselná měřítka zůstávají ostrá; rotace lehce „šumí“ (kompromis pixel-art grafiky). scale + angle se skládají.
  • shadow — když je True, neprůhledné pixely sprite cíl ztmaví místo vykreslení své barvy (vržené stíny: posunutá silueta pod sprite; nebo ztmavující/vignette overlay). Lze libovolně kombinovat se scale/angle.
  • bitmap — čtení/zápis zdrojového Bitmap. Přiřazení nového vymění grafiku za běhu a může změnit velikost (powerupy, měnitelné HUD pruhy, textové popisky); scéna při dalším refresh překreslí jak staré, tak nové hranice.
  • anchor — pivot jako (fx, fy) zlomky velikosti bitmapy: (0, 0) vlevo nahoře (výchozí), (0.5, 0.5) střed, (0.5, 1.0) dole uprostřed. x/y pak odkazují na tento bod, takže růst/zmenšování přes výměnu bitmap zůstává zarovnáno kolem pivotu. Ukládá se v krocích 1/256; dirty-rect sleduje výsledný levý horní roh.
  • Pozice se ukládá jako 24.8 fixed-point. Pro hladkou fyziku použijte fx/fy (ball.fx += 2.4) místo paralelního Python floatu + int(round()); x/y vracejí zaokrouhlený pixel dolů pro výpočty s tile/kolizemi. Dirty-rect se spustí jen tehdy, když se změní pixel (sub-pixelové chvění pod 1 px je zdarma).
  • data — libovolný uživatelský payload (jakýkoli objekt) pro herní stav každého sprite, takže nepotřebujete paralelní wrapper třídu: hero.data = {"vy": 0, "dead": False}.

Rychlý async-DMA backend obalující existující busdisplay.BusDisplay (např. board.DISPLAY). Znovu využívá jeho SPI sběrnici, piny, příkazy okna a rozměry.

  • render(sprites, buffer_a, buffer_b, x0, y0, x1, y1, *, background=0) — vykreslí seznam sprite do oblasti pomocí DMA s dvojitým bufferem.

Scene(display, buffer_a, buffer_b, *, background=0, top=0, bottom=0, left=0, right=0)

Sekce “Scene(display, buffer_a, buffer_b, *, background=0, top=0, bottom=0, left=0, right=0)”

Scéna v retained-mode s vykreslováním pomocí dirty-rectangle. display je picogame.Display.

  • add(item, *, fixed=False) -> item — přidá Sprite/Tilemap/Particles/Canvas/StripDraw a vrátí přidanou položku, takže spr = scene.add(Sprite(...)) funguje. Pořadí vkládání je zdola nahoru. fixed=True (jen keyword) připne položku k obrazovce (ignoruje view offset) — použijte pro HUD / skóre / dialog, které musí zůstat na místě, zatímco se svět posouvá přes set_view. (nejprve přidejte pozadí tilemap, pak sprite, popředí tilemap nakonec).
  • add_all(items) — přidá několik položek najednou (stejné pořadí zdola nahoru).
  • refresh() -> (x1, y1, x2, y2) | None — porovná s předchozím frame a překreslí jen změněnou oblast; vrací dirty rect, nebo None, pokud se nic nezměnilo. První refresh překreslí celou obrazovku (pokryje zbylé pixely z konzole).
  • invalidate() — vynutí překreslení celé obrazovky při dalším refresh (např. při změně levelu).
  • set_view(ox, oy) — view offset = pozice počátku scény na obrazovce. Nastavte konstantní offset pro vycentrování malé hry (např. hra 128×128 na 320×240); aktualizujte ho každý frame pro posouvání většího světa (posouvání překresluje celou obrazovku). Sprite/tilemapy pak žijí v běžných souřadnicích scény bez ohledu na umístění.
  • view — read-only (ox, oy) aktuální offset kamery.

Tilemap(tileset, cols, rows)

Sekce “Tilemap(tileset, cols, rows)”

Mřížka tile indexů do tileset (Bitmap, jehož frame jsou jednotlivé tily).

  • tile(tx, ty) -> int / tile(tx, ty, value, *, flip_x=False, flip_y=False, transpose=False) — získá / nastaví tile (nastavení označí dirty; příznaky orientace jsou jen keyword).
  • move(x, y) — posune celou mapu (pixelová pozice tile 0,0).
  • fill(value) — nastaví každý tile.
  • Čtení tile() mimo rozsah vrací 0 a zápisy ignoruje (bez výjimky).
  • Read-only properties: x, y, cols, rows.

Canvas(width, height, transparent=None)

Sekce “Canvas(width, height, transparent=None)”

RAM kreslicí plocha skládaná jako vrstva Scene — obecný domov pro tvary. Přidejte ji do Scene; kreslete do ní; překreslí se jen překreslené oblasti. Barvy jsou wire-order.

  • Primitiva (všechna berou wire barvy): clear(color), pixel(x, y, color), fill_rect(x,y,w,h,color), rect(x,y,w,h,color), line(x0,y0,x1,y1,color), circle(cx,cy,r,color), fill_circle(cx,cy,r,color), ring(cx,cy,r,thickness,color), triangle(x0,y0,x1,y1,x2,y2,color), fill_triangle(...), ellipse(cx,cy,rx,ry,color), fill_ellipse(...), fill_round_rect(x,y,w,h,r,color), frame3d(x,y,w,h,light,dark) (zkosený box — světlo nahoře/vlevo, tma dole/vpravo), move(x, y).
  • Read-only properties: x, y, width, height.
  • transparent (wire barva) umožňuje, aby plocha byla tvarovaný overlay (HUD pruh, ukazatel, vektorová grafika) nad ostatními vrstvami. Stojí width*height*2 bajtů RAM, takže ji nadimenzujte na to, co potřebujete (např. stavový pruh 320×16 = ~10 KB).
  • Upozornění na RAM: Canvas(320, 240) přes celou obrazovku má 150 KB — příliš velké pro RP2040 (~190 KB heap). Udržujte Canvasy malé, nebo použijte Tilemap pro velká posouvaná pole. Viz HARDWARE.md. Pro animovanou plochu přes celý frame dejte přednost StripDraw níže — stojí 0 bajtů.

StripDraw(callback, x=0, y=0, width=0, height=0)

Sekce “StripDraw(callback, x=0, y=0, width=0, height=0)”

Kreslicí vrstva v immediate-mode zcela bez pixelového bufferu. Přidává se do Scene jako jakákoli vrstva, ale místo uchovávání pixelů volá váš callback jednou pro každý render strip, který se překrývá s jejím obdélníkem:

def draw(view, vx, vy, vw, vh):
# `view` is a Canvas pointing straight at the live strip, clipped to the part that
# overlaps the layer's rect; its local (0,0) is screen pixel (vx, vy); (vw, vh) is
# that view's size. Draw with normal Canvas primitives -- a full-view fill stays
# inside the layer's rect (the callback only fires for strips the rect touches).
for ly in range(vh):
Y = vy + ly # screen row
view.fill_rect(0, ly, vw, 1, sky_or_road(Y))
scene.add(pg.StripDraw(draw, 0, 0, 320, 240))
  • RAM: nulová — oproti width*height*2 bajtům u Canvas. Pseudo-3D silnice přes celou obrazovku má 0 B jako StripDraw vs 150 KB jako Canvas.
  • Jeho obdélník se překresluje každý frame (žádné přeskočení přes dirty-rect), takže ho použijte pro animovaný obsah: pseudo-3D silnice, gradientní oblohy, raycastery, plazmu, procedurální pozadí, nebo tvary, které se mění každý frame. Pro statickou grafiku, která většinou stojí, je Canvas levnější na CPU (překresluje se jen při změně) — vybírejte podle pohybu, ne podle velikosti.
  • Udržujte vnitřní smyčku lehkou: callback vydává C primitiva, takže hrstka volání typu fill_rect/hline na strip je levná; vyhněte se náročnému Pythonu per-pixel.
  • Callback, který vyhodí výjimku, vypíše jeden traceback (pak ta vrstva nekreslí nic) — nemůže zaseknout displej, ale opravte to.
  • Read/write vlastnosti x, y, width, height přesunou nebo změní velikost vrstvy za běhu; po zmenšení zavolejte scene.invalidate(), aby se uvolněná oblast překreslila.
  • Kreslí se v prostoru obrazovky (ignoruje offset kamery/view). Ve posouvané scéně (která volá set_view) ho přidejte jako fixedscene.add(sd, fixed=True) — aby jeho dirty rect odpovídal tomu, kam kreslí; ve scéně se statickou kamerou na tom nezáleží. Uvnitř callbacku namapujte bod obrazovky na strip pomocí (screen_x - vx, screen_y - vy) (správné i tehdy, když je dirty rect sloučen do širšího než vrstva). Skládá se nad nižší vrstvy a pod vyšší, jako každá vrstva. Viz examples/picogame_stripdraw_demo.py a examples/journey_hw/journey_mono.py (silnice závodu, intro tvary, dialogový box RPG).

Particles(capacity, size=1, gravity=0.0, fade=False)

Sekce “Particles(capacity, size=1, gravity=0.0, fade=False)”

Sdružená částicová vrstva (mnoho malých pohyblivých teček) vykreslená jako jedna vrstva Scene — mnohem levnější než jeden Sprite na částici. Přidejte ji do Scene. S fade=True každá částice během svého života stmívá k černé (vzhled jisker/uhlíků/kouře).

  • emit(x, y, count, speed=1, life=30, color=0xFFFF) — vytvoří count částic na (x, y) s náhodnou rychlostí až speed px/tick, žijících life ticků, ve wire-order barvě (použijte picogame.rgb565).
  • tick() — posune o jeden krok (pohyb, gravitace, stárnutí); volejte jednou za frame.
  • clear() — odstraní všechny částice.
  • Pozice jsou sub-pixelové (fixed-point); vrstva překresluje jen tam, kde částice jsou (a byly), takže nezanechávají stopy. v1 kreslí plné tečky size×size.

picogame_font.py (helper v čistém Pythonu, zkopírujte na zařízení) vykresluje text do Bitmap pomocí libovolného fontio fontu, včetně přibaleného firmwarového terminalio.FONT — nejsou potřeba žádné font assety.

import terminalio, picogame as pg, picogame_font
label = picogame_font.Label(pg, terminalio.FONT, x=4, y=2,
fg=pg.rgb565(255, 210, 60), bg=pg.rgb565(0, 0, 0))
# each frame / on change:
if label.set("SCORE %06d" % score): # re-renders only when the text changes
label.draw(display, bufA) # repaints just its rectangle (opaque bg)

Dále picogame_font.render_text(pg, font, text, fg, bg=None) -> (bitmap, w, h) pro jednorázové vykreslení (bg=None → průhledné). Text se skládá v Pythonu, takže ho aktualizujte jen když se mění; C typ Font je plánované zrychlení.


picogame_audio.py (helper v čistém Pythonu, zkopírujte na zařízení) obaluje audio stack CircuitPythonu (audiopwmio + audiocore + audiomixer) pro PWM audio PicoPadu. Mixer umožňuje překrývání zvukových efektů a jejich přehrávání pod hudbou. Není potřeba žádná změna firmwaru — tyto moduly jsou na RP2040 ve výchozím stavu povolené.

import picogame_audio
audio = picogame_audio.Audio() # PWM on board.AUDIO, 4 voices, 22050 Hz mono 16-bit
pew = audio.load("pew.wav") # reusable WaveFile (keep the reference)
audio.sfx(pew) # fire-and-forget on a round-robin sfx voice
audio.music(audio.load("song.wav")) # looping music on the reserved voice 0
beep = picogame_audio.tone(880, 90) # generate a tone (no .wav needed)
audio.sfx(beep)

Každý sample musí odpovídat formátu Mixeru (sample_rate, channel_count, bits_per_sample, samples_signed). Výchozí jsou 22050 Hz / mono / 16bit signed (typický ugame .wav). Voice 0 je vyhrazen pro hudbu; sfx se cyklicky střídají přes ostatní, takže se mohou překrývat.

Časování a herní smyčka

Sekce “Časování a herní smyčka”

picogame_clock.py (helper v čistém Pythonu) poskytuje pacing frame, aby byl pohyb nezávislý na snímkové frekvenci (jinak by rychlé ťuknutí na D-pad vrhlo sprite přes celou obrazovku při 140 FPS). Ve firmwaru nepotřebuje nic — používá time.monotonic_ns().

import picogame_clock
clock = picogame_clock.Clock(30) # cap to 30 FPS
while True:
dt = clock.tick() # sleep to the frame boundary; real dt (s)
update(dt) # ignore dt for fixed-feel, or scale by it
scene.refresh()
  • Clock(fps, max_dt=0.1)tick() zastropuje a vrátí dt (omezené po zaseknutí); tick_async() během nečinného čekání předá řízení dalším asyncio úlohám.
  • FixedStep(step_fps, max_steps=5)for dt in steps(): update(dt) provádí stejné logické kroky bez ohledu na čas vykreslení (deterministická fyzika), vykreslí jednou za frame.

tick_async() potřebuje knihovnu asyncio, která není přibalena ve firmwaru — zkopírujte složku asyncio do CIRCUITPY/lib (z Adafruit CircuitPython library bundle), když ji chcete. Synchronní Clock/FixedStep nepotřebují nic navíc. (Vykreslování je blokující, takže async pomáhá jen během nečinné prodlevy stropu.)

Vstup a pomocné scaffold helpery

Sekce “Vstup a pomocné scaffold helpery”
  • picogame_input.Buttons — čte tlačítka PicoPadu; volejte poll() jednou za frame, pak is_pressed(mask), just_pressed(mask), just_released(mask) s konstantami UP DOWN LEFT RIGHT A B X Y. Žádné per-game digitalio propojování.
  • picogame_game.setup(background=0, strip_h=24, fast=True) — jediné volání převezme displej a vrátí (scene, buffer_a, buffer_b) (auto-refresh vypnutý, dva strip buffery, Display+Scene). fast=False řídí holý busdisplay přes přenositelný bus.send renderer místo DMA Display — stejná cesta jako na portech bez DMA backendu (všude správné, pomalejší). Scene přijímá buď picogame.Display, nebo prostý busdisplay.
  • picogame_math — matematické helpery length/distance/normalize/angle_rad/from_angle_rad/clamp.
  • picogame_animFrameAnim / AnimatedSprite: řídí frame sprite ze sekvence frame na časovém základě (tick(dt)), místo ručního frame//4 % n.
  • picogame_pool.Pool(scene, bitmap, capacity, anchor=None, fixed=False) — znovupoužitelný pool sprite s pevnou velikostí: předalokuje capacity sprite (všechny skryté) přidaných do scény. spawn() (bez argumentů) odkryje volný sprite a vrátí ho (nebo None, pokud je plno), free(s) jeden skryje, free_all() skryje všechny, count() vrátí počet živých. Živé procházejte pomocí for s in pool.items: (přeskočte if not s.visible). Jako příznak „živý“ používá sprite.visible a per-entity stav drží v sprite.data — nahrazuje ručně psané alive-flagy.
  • picogame_save.Save(key, schema) — drobné perzistentní úložiště pro highscore/nastavení, podložené microcontroller.nvm (4 KB vyhrazeného flash na RP2040). load() vrací slovník hodnot (výchozí, pokud je prázdné/poškozené), save(dict) ho uloží; přežije restart i smazání souborového systému, bez nutnosti remountu v boot.py. schema je name -> (struct char, default), např. {"hiscore": ("I", 0), "level": ("B", 1)}. NVM je jediná sdílená oblast, takže předejte per-game key (např. "arkanoid") — ukládá se v hlavičce a ověřuje při načítání, takže data jiné hry neprojdou kontrolou klíče a dostanete vlastní výchozí hodnoty místo špatného čtení cizích bajtů. Souběžně existující hry mohou také použít odlišný offset=. Každý save() zapíše flash sektor — volejte ho při game-over / novém rekordu, ne každý frame.
  • picogame_shapesrect/circle/ring/from_mask/atlas/poly_frames generují jednobarevné PAL8 bitmapy (a předpečené rotační frame), takže hry přestanou ručně psát pixelové buffery a balení frame-atlasu.
  • picogame_uiSceneLabel (text nezávislý na kameře přes fixed vrstvu scény), TextBox (víceřádkový box v prostoru obrazovky), Menu (kurzorové menu). Zaplňuje mezeru v UI pro hry řízené dialogem/menu/HUD (RPG, strategie).
import picogame as pg, picogame_game, picogame_input, picogame_clock
scene, bufA, bufB = picogame_game.setup(background=pg.rgb565(16, 18, 32))
btn = picogame_input.Buttons()
clock = picogame_clock.Clock(60)
while True:
btn.poll()
if btn.is_pressed(btn.LEFT):
hero.move(hero.x - 2, hero.y)
if btn.just_pressed(btn.A):
fire()
scene.refresh()
clock.tick()

tools/png2picogame.py (na straně hostitele, potřebuje Pillow) převádí PNG/BMP na importovatelné asset moduly, jejichž barvy jsou již ve wire order.

Terminál
# A sprite / horizontal animation atlas (auto picks PAL8 or RGB565):
python3 tools/png2picogame.py hero.png -o hero.py --frames 6
# A vertical / grid tile sheet -> horizontal atlas Bitmap (16x16 tiles):
python3 tools/png2picogame.py tiles.bmp -o tiles.py --tile 16x16 --transparent-index 15
# A tilemap (image palette indices ARE tile indices) -> Tilemap data module:
python3 tools/png2picogame.py level.bmp -o level.py --map

Na zařízení:

import hero, tiles, level
spr = pg.Sprite(hero.bitmap(pg), 40, 120)
tileset = tiles.bitmap(pg)
tm = pg.Tilemap(tileset, level.WIDTH, level.HEIGHT)
level.fill(tm) # load the map data

Volby: --format auto|pal8|rgb565, --frames N, --tile WxH, --map, --transparent-index N (považuje index palette v režimu P za průhledný), --rle (RLE-komprese jednoframe PAL8 pozadí).

Volby šetřící velikost (PAL8):

  • --dither (+ --colors N, výchozí 255) — Floyd–Steinberg dither při redukci na PAL8; skrývá pruhování gradientů (oblohy, osvětlení). Nízké --colors (např. 16–32) + --dither = retro vzhled.
  • --dedup (s --tile WxH) — sloučí tily, které jsou identické až na orientaci (všech 8: 4 rotace × zrcadlení) do menšího tilesetu → méně RAM tilesetu. Vydá tabulku REMAP; přestavte svou mapu pomocí v, fx, fy, tp = REMAP[old_index]; tm.tile(x, y, v, flip_x=fx, flip_y=fy, transpose=tp) (nese per-tile flip/transpose; příznaky orientace jsou jen keyword). Typické ručně kreslené levely mají 40–70 % duplicit. Funguje spolu s per-cell orientací Tilemap.

Běží to na reálném hardwaru? Nejdřív si přečtěte HARDWARE.md. Pokrývá rozpočet RAM (~150–190 KB heap), rozdíly v API mezi simem a zařízením (žádné scene.display), dodávání .mpy (ne velkých .py), PAL8 bytes tilesety (ne obří array literály → pystack exhausted) a deploy vzor split-one-program-per-scene. Sim toto vše skrývá.

  • RAM je úzké hrdlo (264 KB celkem, ~72 KB firmware): Canvas(320,240) přes celou obrazovku má 150 KB — příliš velké. Udržujte Canvasy malé / použijte Tilemap pro velká pole; HUD = Label, ne Canvas pruh; gc.collect() mezi scénami.
  • Používejte dirty-rect (Scene) pro reálné hry: statické pozadí + pár pohyblivých věcí → drobné přenosy → 100+ FPS. Překreslování celé obrazovky každý frame je vázáno na SPI (~20 ms @ 62,5 MHz ≈ strop 50 FPS).
  • Nezávislost na snímkové frekvenci: použijte picogame_clock.Clock(fps).tick() (nebo škálujte pohyb jeho dt) — jinak se rychlost mění s FPS (rychlé ťuknutí může vrhnout sprite přes celou obrazovku při 140 FPS). Viz Časování a herní smyčka.
  • Assety žijí ve flash (render core0 je čte přes XIP) — udržujte velkou grafiku jako zmrazené const moduly; preferujte PAL8 (8:1 oproti RGB565).
  • Nativní typy (Sprite, Bitmap, …) nemohou nést vlastní atributy.
  • Jediný dirty rect (v1): Scene sjednotí všechny změny do jednoho ohraničujícího obdélníku — skvělé pro lokalizovaný pohyb; sprite rozeseté přes celou obrazovku degradují směrem k překreslení celé obrazovky.
  • Barvy musí být wire-order (rgb565()); syrové RGB565 inty budou vypadat špatně.

Board: pajenicko_picopad (přeúčelovaný jako picogame engine firmware — vypnuté Wi-Fi náročné DVI/EVE, CIRCUITPY_PICOGAME=1). Vyžaduje ARM GCC ≥ 14 (CircuitPython 10.x). Pro toolchain viz poznámky picopad-build-env.

Terminál
cd circuitpython/ports/raspberrypi
. ../../../.venv/bin/activate
export PATH="<arm-gnu-toolchain-14.x>/bin:$PATH"
make BOARD=pajenicko_picopad -j$(nproc)
# -> build-pajenicko_picopad/firmware.uf2

Flash: podržte BOOTSEL, připojte PicoPad, zkopírujte firmware.uf2 na disk RPI-RP2. Pak zkopírujte svou hru + helpery do CIRCUITPY. Na zařízení s napjatou RAM dodávejte .mpy (ne .py) a dbejte na paměťová pravidla — viz HARDWARE.md. Ověření, že firmware skutečně má danou funkci: arm-none-eabi-nm build-.../firmware.elf | grep sprite_set_scale.


V kořeni projektu (zkopírujte do CIRCUITPY/code.py):

SouborCo ukazuje
examples/picogame_demo_code.pysprite libovolné velikosti, rychlý Display, rozpis FPS/časování
examples/picogame_scene_demo.pyretained Scene + dirty-rect (statické pole + pohyblivé objekty)
examples/picogame_play_demo.pyvstup z D-padu → Scene (pohyb omezený snímkovou frekvencí)
examples/picogame_hud_demo.pyHUD text přes přibalený font (picogame_font.py)
examples/picogame_tilemap_demo.pytilemap pozadí + sprite nad ním
examples/picogame_audio_demo.pyPWM audio: překrývající se pípnutí přes mixer (picogame_audio.py)
examples/picogame_scroll_demo.pykamera/posouvání: svět 640×480 s view sledujícím hráče (scene.set_view)
examples/picogame_particles_demo.pyčásticová vrstva: výbuch (A) + fontána (B) s gravitací (pg.Particles)
examples/ugame_ports/jumper_port/kompletní hra (jumper-wire) portovaná z _stage
examples/ugame_ports/vacuum_port/kompletní hra (vacuum-invaders) portovaná z _stage
examples/picogame_arkanoid.pykompletní hra Breakout/Arkanoid: Tilemap cihly + sprite + collide + částice + HUD
examples/picogame_squest.pystřílečka ve stylu Seaquest: sdružené sprite přes sprite.data, projektily + collide + částice, O2 HUD ukazatel, tone audio

Struktura projektu a deploy

Sekce “Struktura projektu a deploy”
lib/ engine Python helpers (picogame_*) -> copy needed ones to CIRCUITPY/lib/
examples/ games, demos, per-game assets -> a game becomes code.py at the root
tools/ asset converters (png2picogame, tinyjoypad_extract, cavern_pack)

Nativní engine je vestavěný C modul picogame (ve firmwaru). Python helpery zachovávají prefix picogame_*nikoli balíček picogame/, protože toto jméno je C modul a nelze ho na souborovém systému zastínit.

Deploy hry: flashněte firmware.uf2, zkopírujte helpery, které importuje, do CIRCUITPY/lib/, zkopírujte hru jako CIRCUITPY/code.py plus jakékoli assety, které načítá.

.mpy: helpery v lib/ lze předkompilovat na .mpy pomocí mpy-cross (odpovídajícího verzi CircuitPythonu) pro zkrácení času importu a úsporu RAM (žádný parse zdroje na zařízení). Vložte soubory .mpy do CIRCUITPY/lib/ místo .py.