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.
Obsah
Sekce “Obsah”- Koncepty
- Rychlý start
- API reference
- Text a fonty
- Asset pipeline
- Výkon a omezení
- Sestavení firmwaru
- Příklady
Koncepty
Sekce “Koncepty”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.Display— rychlý: 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 seScene.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).
Rychlý start
Sekce “Rychlý start”import time, array, boardimport picogame as pg
display = board.DISPLAYdisplay.auto_refresh = False # the engine drives the display itselfdisplay.root_group = None
W, H = 320, 240bufA = 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] = 1hero_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)API reference
Sekce “API reference”Modul picogame
Sekce “Modul picogame”| Název | Popis |
|---|---|
RGB565 | konstanta formátu (16bitová barva, wire order) |
PAL8 | konstanta formátu (8bitový index do palette) |
rgb565(r, g, b) -> int | vytvoří wire-order RGB565 barvu z 8bitových složek |
collide(x1, y1, x2, y2, ax1, ay1, ax2, ay2) -> bool | př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) -> bool | box↔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) -> float | hladký 2-D value noise, 0..1 (rychlé C) |
value1d(x, *, seed=0) -> float | hladký 1-D value noise, 0..1 |
fbm2d(x, y, *, octaves=4, seed=0, lacunarity=2.0, gain=0.5) -> float | fraktální (fBm) 2-D noise, 0..1 — terén/mraky/jeskyně |
fbm1d(x, *, octaves=4, seed=0, lacunarity=2.0, gain=0.5) -> float | fraktá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— proPAL8buffer položek wire-order RGB565 (např.array("H", [...])).frames— frame animace uspořádané vodorovně; framefje ve sloupcif*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 neboNone),transparent(transparentní hodnota neboNone).
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 kolemanchor.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+anglese skládají.shadow— když jeTrue, 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 sescale/angle.bitmap— čtení/zápis zdrojovéhoBitmap. 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šímrefreshpř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/ypak odkazují na tento bod, takže růst/zmenšování přes výměnubitmapzů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/yvracejí 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}.
Display(busdisplay)
Sekce “Display(busdisplay)”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/StripDrawa vrátí přidanou položku, takžespr = 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řesset_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, neboNone, 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í0a 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*2bajtů 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žijteTilemappro velká posouvaná pole. VizHARDWARE.md. Pro animovanou plochu přes celý frame dejte přednostStripDrawníž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*2bajtům uCanvas. Pseudo-3D silnice přes celou obrazovku má 0 B jakoStripDrawvs 150 KB jakoCanvas. - 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
Canvaslevně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/hlinena 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,heightpřesunou nebo změní velikost vrstvy za běhu; po zmenšení zavolejtescene.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 fixed —scene.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. Vizexamples/picogame_stripdraw_demo.pyaexamples/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žspeedpx/tick, žijícíchlifeticků, ve wire-order barvě (použijtepicogame.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.
Text a fonty
Sekce “Text a fonty”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í.
Audio
Sekce “Audio”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_audioaudio = picogame_audio.Audio() # PWM on board.AUDIO, 4 voices, 22050 Hz mono 16-bitpew = audio.load("pew.wav") # reusable WaveFile (keep the reference)audio.sfx(pew) # fire-and-forget on a round-robin sfx voiceaudio.music(audio.load("song.wav")) # looping music on the reserved voice 0beep = 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_clockclock = picogame_clock.Clock(30) # cap to 30 FPSwhile 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šímasyncioú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; volejtepoll()jednou za frame, pakis_pressed(mask),just_pressed(mask),just_released(mask)s konstantamiUP DOWN LEFT RIGHT A B X Y. Žádné per-gamedigitaliopropojová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ýbusdisplaypřes přenositelnýbus.sendrenderer místo DMADisplay— stejná cesta jako na portech bez DMA backendu (všude správné, pomalejší).Scenepřijímá buďpicogame.Display, nebo prostýbusdisplay.picogame_math— matematické helperylength/distance/normalize/angle_rad/from_angle_rad/clamp.picogame_anim—FrameAnim/AnimatedSprite: řídíframesprite ze sekvence frame na časovém základě (tick(dt)), místo ručníhoframe//4 % n.picogame_pool.Pool(scene, bitmap, capacity, anchor=None, fixed=False)— znovupoužitelný pool sprite s pevnou velikostí: předalokujecapacitysprite (všechny skryté) přidaných do scény.spawn()(bez argumentů) odkryje volný sprite a vrátí ho (neboNone, 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čteif not s.visible). Jako příznak „živý“ používásprite.visiblea per-entity stav drží vsprite.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 vboot.py.schemajename -> (struct char, default), např.{"hiscore": ("I", 0), "level": ("B", 1)}. NVM je jediná sdílená oblast, takže předejte per-gamekey(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_shapes—rect/circle/ring/from_mask/atlas/poly_framesgenerují 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_ui—SceneLabel(text nezávislý na kameře přesfixedvrstvu 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_clockscene, 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()Asset pipeline
Sekce “Asset pipeline”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.
# 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 --mapNa zařízení:
import hero, tiles, levelspr = 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 dataVolby: --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á tabulkuREMAP; 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.
Výkon a omezení
Sekce “Výkon a omezení”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), PAL8bytestilesety (ne obříarrayliterá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žijteTilemappro 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 jehodt) — 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é
constmoduly; preferujtePAL8(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ě.
Sestavení firmwaru
Sekce “Sestavení firmwaru”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.
cd circuitpython/ports/raspberrypi. ../../../.venv/bin/activateexport PATH="<arm-gnu-toolchain-14.x>/bin:$PATH"make BOARD=pajenicko_picopad -j$(nproc)# -> build-pajenicko_picopad/firmware.uf2Flash: 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.
Příklady
Sekce “Příklady”V kořeni projektu (zkopírujte do CIRCUITPY/code.py):
| Soubor | Co ukazuje |
|---|---|
examples/picogame_demo_code.py | sprite libovolné velikosti, rychlý Display, rozpis FPS/časování |
examples/picogame_scene_demo.py | retained Scene + dirty-rect (statické pole + pohyblivé objekty) |
examples/picogame_play_demo.py | vstup z D-padu → Scene (pohyb omezený snímkovou frekvencí) |
examples/picogame_hud_demo.py | HUD text přes přibalený font (picogame_font.py) |
examples/picogame_tilemap_demo.py | tilemap pozadí + sprite nad ním |
examples/picogame_audio_demo.py | PWM audio: překrývající se pípnutí přes mixer (picogame_audio.py) |
examples/picogame_scroll_demo.py | kamera/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.py | kompletní hra Breakout/Arkanoid: Tilemap cihly + sprite + collide + částice + HUD |
examples/picogame_squest.py | stří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 roottools/ 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.