Přeskočit na obsah

picogame — průvodce funkcemi (kdy co použít)

Úkolově zaměřená prohlídka enginu: u každé funkce co to je, krátký příklad a kdy ji použít — a po čem sáhnout místo ní. Doplňuje ostatní dokumenty:

Barvy jsou RGB565 inty v pořadí drátu (C = pg.rgb565). Příklady předpokládají scene z picogame_game.setup() (nebo pg.Scene(...)).


1. Která kreslicí plocha?

Sekce “1. Která kreslicí plocha?”

Nejčastější rozhodnutí. Vše viditelné je jedna z těchto vrstev scény (přidaná přes scene.add(item, fixed=False), zdola→nahoru; fixed=True ignoruje camera).

Potřebuji vykreslit…PoužijProč / na co si dát pozor
pohybující se objekt (hráč, nepřítel, střela, mince)Spritezákladní stavební prvek; pro mnoho identických použij Pool
velkou mřížku (mapa, dlážděné pozadí, cihlová zeď)Tilemap1 bajt/buňku oproti 2 bajtům/pixel; překresluje jen změněné buňky
tvary / panel / HUD grafiku, která se mění zřídkaCanvasdrží pixely → dirty-rect ji přeskakuje, dokud je statická. Stojí w*h*2 bajtů
animovaný celoobrazovkový efekt (silnice, gradient, obloha, plazma)StripDrawkreslí přímo do stripu, 0 bajtů. Překresluje každý frame
jednorázový blit bez udržované Scenerender()immediate mode, mimo Scene

Praktické pravidlo: vybírej podle pohybu, ne velikosti. Statické/kumulující se → Canvas (drží pixely, téměř nulová zátěž CPU, dokud se nemění). Animované celoobrazovkové → StripDraw (nulová RAM, překresluje každý frame). Nikdy nealokuj celoobrazovkový Canvas(320, 240) (150 KB) na RP2040 — rozděl ho do pásů, dlaždic, nebo použij StripDraw.

# Statický HUD panel → Canvas (překresluje se jen když do něj kreslíš)
panel = pg.Canvas(120, 40); panel.move(4, 4); scene.add(panel, fixed=True)
panel.fill_round_rect(0, 0, 120, 40, 6, INK); panel.frame3d(0, 0, 120, 40, LT, DK)
# Animovaná pseudo-3D silnice → StripDraw (žádný buffer); lokálně k view (0,0) == obrazovka (vx, vy)
def road(v, vx, vy, vw, vh):
for ly in range(vh):
v.fill_rect(0, ly, vw, 1, shade(vy + ly))
scene.add(pg.StripDraw(road, 0, SKY_H, W, ROAD_H)) # přidej jako fixed ve scrollující scéně

StripDraw scanline road StripDraw — celoobrazovková animovaná plocha (pseudo-3D silnice + obloha), kreslená přímo do stripů s nulovou RAM bufferu.

Tilemap grid Tilemap — mřížka tile bitmap (1 bajt/buňku); levný způsob jak vyplnit velkou/scrollující plochu.

Viz: examples/picogame_stripdraw_demo.py, examples/journey_hw/journey240.py (silnice + intro + dialog vše StripDraw), picogame_microrace (StripDraw silnice), picogame_tetris/_pacman (Tilemap).


2. Transformace sprite: runtime scale/angle vs. předpečené framy

Sekce “2. Transformace sprite: runtime scale/angle vs. předpečené framy”

Sprite umí škálovat a rotovat při kreslení (scale, angle, kolem anchor) — nebo můžeš zapéct rotované framy do bitmapy a jen krokovat frame.

spr.anchor = (0.5, 0.5)
spr.scale = 1.6 # 1.0 = nativní (rychlá cesta); desetinné ok (pulz)
spr.angle = 30 # stupně, kolem anchor
SituacePoužijProč
občasné / plynulé škálování (pulz mince, růst bosse, hloubka)runtime scalejedna bitmapa, libovolný faktor
občasná rotace, libovolný úhel (naklánějící se auto, otáčení)runtime anglejedna bitmapa; nearest-neighbour, takže trochu blikotá
neustále rotující objekt v pevných krocích (asteroid, loď čelem do 16 směrů)předpečené framy (shapes.poly_frames, nebo art framy)ostřejší a levnější na frame než opětovná rotace
mnoho objektů rotujících každý framepředpečené framyruntime affine je per-pixel; pečené framy jsou prostý blit

Sprite rotation and scale Stejná bitmapa: horní řada = runtime angle (0/30/60/90°), dolní řada = runtime scale (0,6→2,6×).

Pravidlo: angle/scale pro pár sprite nebo plynulé/libovolné hodnoty; pečené framy pro mnoho nebo stále rotujících sprite. Celočíselný scale zůstává pixelově ostrý; rotace vždy blikotá (kompromis pixel-artu). scale=1.0, angle=0 je rychlá cesta blitu — nech je tam, dokud je objekt v klidu.

Viz: examples/journey_hw/journey240.py (scale mince, naklánění auta), tutorials/02-starship & picogame_asteroids (předpečené rotační framy).

Sprite blit efekty — přebarvení/průhlednost zdarma (žádné bitmapy navíc)

Sekce “Sprite blit efekty — přebarvení/průhlednost zdarma (žádné bitmapy navíc)”

Kromě geometrie má Sprite levné per-pixel blit efekty — vždy jeden naráz (nastavení jednoho ostatní zruší). Nestojí žádnou grafiku navíc ani žádnou RAM navíc:

ChciPoužijPoznámky
vržený stín / ztmavení spritespr.shadow = Trueneprůhledné pixely ztmaví cíl
hit-flash (bílé bliknutí při zásahu)spr.flash = WHITE (pulz 1–3 framy; 0=vyp)plochá barva, ztrácí detail — ideální pro 1-framové bliknutí
barevné osvětlení / zranění / zmrazení / zářespr.tint = RED (0=vyp)násobí — zachovává stínování sprite (na rozdíl od flash)
duch / mlha / rozplynutí spritespr.dither = 0..16Bayer stipple, žádná alpha; animuj úroveň pro rozplynutí
levná rotace o 90° (ostrá, bez blikotání)spr.transpose = True (+flip_x/y = všech 8 orientací)jen rychlá cesta (scale 1, angle 0); prohodí w/h

Tohle je nejlevnější šťáva na zařízení (viz také picogame_fx pro celoobrazovkový Shake/Fade a picogame_palette pro animovanou vodu/přebarvení). flash/tint/dither/transpose jsou předvedeny společně v examples/picogame_fxdemo.py. Po in-place úpravě bitmapy nebo palette zavolej spr.touch(), aby se dirty-rect překreslil.

Tilemapy nesou stejnou orientaci na buňku: tm.tile(x, y, idx, flip_x=True, flip_y=False, transpose=False) (příznaky orientace jsou jen keyword) — zkombinuj to s deduplikovaným tilesetem (png2picogame.py --dedup), aby rotované/zrcadlené tile stály jednu uloženou tile (velká úspora RAM; orientační rovina se alokuje, jen když ji mapa používá).


Stejný sprite blitnutý pěti způsoby: normal, flash, tint, dither, shadow

3. Animace: frame-by-frame vs. picogame_anim

Sekce “3. Animace: frame-by-frame vs. picogame_anim”

Bitmapa drží N framů; ty posouváš sprite.frame.

spr.frame = (frame // 6) % spr.bitmap.frames # ručně: nový frame každých 6 ticků
import picogame_anim
walk = picogame_anim.FrameAnim(spr, [0, 1, 2, 1], fps=8) # založeno na čase
# každý frame: walk.tick(dt)
SituacePoužij
pár sprite, pevný herní tickručně frame = (f // n) % N — nulová režie, naprosto jasné
mnoho sprite, nebo chceš přehrávání založené na čase (dt), nebo pojmenované stavypicogame_anim (FrameAnim / AnimatedSprite)

Většina arkádových her běží na pevných FPS, takže ruční forma je norma; po picogame_anim sáhni, když máš několik animací nebo chceš stavy walk/idle/jump podle jména.


Čtyřfázový cyklus výbuchu, který picogame_anim posouvá v čase

4. Pozadí: tilemap-shade vs. scrollující obrázek vs. noise

Sekce “4. Pozadí: tilemap-shade vs. scrollující obrázek vs. noise”

Celoobrazovkové pozadí jako pixelový buffer je obrovské (320×240 = 150 KB). Vybírej podle obsahu:

PozadíPřístupCena
plochá barvabackground Scenezdarma
dlážděná / opakující se scenérieTilemap z tile bitmap1 B/buňku
hrubě stínovaná obloha / terénnoise → shade Tilemap (1 B/buňku)~5 KB na obrazovku
malovaný scrollující pás (parallax)dva Sprite pásu, které se wrapujíband_w*band_h*2, sdílená bitmapa
animovaný gradient / scanline poleStripDraw0 B
# Noise obloha jako shade tilemap (levné): mapuje fbm -> jeden z N barevných framů
sky = pg.Tilemap(shp.color_frames(8, 8, SHADES), gw, gh)
for gy in range(gh):
for gx in range(gw):
sky.tile(gx, gy, int(pg.fbm2d(gx*0.15, gy*0.13, octaves=3, seed=11) * (N-1)))
# Parallax pás: dvě kopie jedné bitmapy, scrolluj a wrapuj (bitmapa je sdílená = 1x RAM)
bg = [pg.Sprite(band, 0, Y), pg.Sprite(band, 320, Y)]; scene.add_all(bg)
for b in bg:
b.fx -= 1
if b.fx <= -320: b.fx += 640

Noise shade tilemap background Pozadí z noise fbm2d mapovaného na shade Tilemap — organická variace za ~5 KB místo 150 KB pixelového bufferu.

Pravidlo: scrollující svět → Tilemap; procedurální vzhled → noise tilemap; malovaný parallax → wrapující pásové sprite; animované pole → StripDraw. Noise obloha 320×960 je 600 KB jako Canvas, ale ~5 KB jako shade Tilemap.

Viz: examples/journey_hw/journey240.py (shmup noise obloha, racer StripDraw silnice, pictor scroll pásu), examples/picogame_pictor.py (wrapující pás louky).


5. Kolize: collide (AABB) vs. collide (kruh)

Sekce “5. Kolize: collide (AABB) vs. collide (kruh)”
if pg.collide(x1, y1, x2, y2, ax1, ay1, ax2, ay2): ... # box vs box
if pg.collide(x1, y1, x2, y2, px, py): ... # box vs bod (6 argumentů)

Inkluzivní AABB — boxy kolidují při doteku (boxy spritu předávej jako (x, y, x+w, y+h); spustí se při kontaktu). Pozn.: collide je inkluzivní, na rozdíl od půlotevřených pixelových rozsahů u render — jiné domény (hitboxy vs pixely).

import picogame_collide as collide
if collide.hit(a, b): ... # AABB přímo z pozic/velikostí dvou sprite
if collide.is_within(a, b, 22): ... # kruhové: středy do 22 px (bez sqrt)
if collide.hit_point(a, px, py): ... # bod uvnitř sprite a
SituacePoužij
hranaté věci (pálka/míč, plošiny, tile buňky)pg.collide (nebo collide.hit) — AABB
kulaté věci / střely vs. blobs / sběratelnécollide.is_within — kruhové, shovívavé, rychlé (bez sqrt)
čtení přímo ze sprite (žádné ruční rect)collide.* — bez alokací, používá pozici+velikost sprite
kolize s tile mřížkou (zdi)vyhledej tilemap.tile(tx, ty) přímo

Pravidlo: AABB pro boxy a tile; kruh (within) pro střely/sběratelné a vše, co má působit kulatě. collide.* je pohodlná forma (žádné dočasné rect); pg.collide když už máš explicitní souřadnice.

Viz: tutorials/01-bounce (AABB), examples/picogame_pictor.py & tutorials/02-starship (kruhové zásahy střel), picogame_pacman (vyhledání tile).


Překryv AABB boxů (vlevo) vs. kruhový překryv within podle poloměru (vpravo)

6. Spawnery: picogame_pool vs. ruční seznam

Sekce “6. Spawnery: picogame_pool vs. ruční seznam”

Střely, nepřátelé, particles-jako-sprite — věci neustále vytvářené a ničené. Nikdy nevytvářej/neruš sprite za běhu (to víří heap → fragmentace). Předem alokuj a recykluj.

import picogame_pool
bullets = picogame_pool.Pool(scene, bullet_bm, 12, anchor=(0.5, 0.5))
b = bullets.spawn() # první volný sprite, zviditelněn (None pokud plný)
if b: b.move(x, y); b.data = {"vy": -6}
...
for b in bullets.items:
if b.visible:
b.fy += b.data["vy"]
if b.fy < -8: bullets.free(b)
SituacePoužij
mnoho stejné bitmapy (střely, jiskry, koule)picogame_pool.Pool
pár prvků různých bitmap (smíšené typy nepřátel)ruční seznam recyklovatelných sprite; přehoď .bitmap při spawnu
krátkožijící tečky s fyzikou (úlomky, šplíchnutí)Particles (§7), ne sprite

Pravidlo: stejná bitmapa → Pool; smíšené bitmapy → ruční recyklační seznam (přehoď .bitmap); levné tečky → Particles. Zlaté pravidlo je předem alokuj, nikdy nealokuj za frame.

Viz: examples/picogame_pictor.py (shuriken Pool + ruční smíšený seznam nepřátel), tutorials/02-starship, picogame_flappy.


7. Particles — kdy ano, kdy ne

Sekce “7. Particles — kdy ano, kdy ne”
ps = pg.Particles(220, size=2, gravity=0.12, fade=True); scene.add(ps)
ps.emit(x, y, count=14, speed=3, life=22, color=C(245, 220, 70)) # výbuch
# každý frame: ps.tick()

Particle bursts Tři výbuchy emit() o pár tick() později — stovky mizejících teček v jedné levné vrstvě.

Použij pro levné, neinteraktivní tečky: výbuchy, jiskry, prach, třpyt mince, stopy. Jedna vrstva zvládne stovky teček mnohem levněji než sprite.

Nepoužívej Particles pro věci, které potřebují bitmapu, kolizi nebo individuální řízení (to jsou Sprite/Pool). Particles jsou fire-and-forget čtverečky.

Viz: examples/picogame_pictor.py (výbuchy), tutorials/01-bounce, picogame_missile.


8. Camera & scrollování

Sekce “8. Camera & scrollování”
scene.set_view(ox, oy) # pozice počátku scény na obrazovce

Svět větší než obrazovka: drž entity ve world souřadnicích, každý frame pohni camera. Ne-fixed vrstvy se posouvají s camera; fixed=True vrstvy (HUD, dialog) zůstávají na místě.

ox = int(max(W - WORLD_W, min(0, W // 2 - hero_x))) # následuj + omez na okraje světa
oy = int(max(H - WORLD_H, min(0, H // 2 - hero_y)))
scene.set_view(ox, oy)

Pozor: změna view překreslí celou obrazovku (žádný zisk dirty-rect při scrollování) — v pořádku pro aktivní scrollování, ale statická camera je mnohem levnější. StripDraw vrstvy jsou ve screen-space, takže je ve scrollující scéně přidávej jako fixed.

Viz: tutorials/03-quest, examples/picogame_platformer, picogame_soccer.


Svět větší než obrazovka posunutý přes set_view, s fixní HUD lištou nahoře

9. HUD / stavový řádek — čtyři způsoby jak ho postavit

Sekce “9. HUD / stavový řádek — čtyři způsoby jak ho postavit”

Stavový řádek (skóre, životy, popisek, ukazatel) je nejčastější HUD a picogame ti dává čtyři způsoby jak ho nakreslit. Liší se ve dvou věcech, které na RP2040 hrají roli: kolik RAM řádek stojí a kolik práce dělá za frame. Vybírej podle toho, co řádek obsahuje a jak často se mění. Rychlá mapa:

Tvůj řádek je…Postav ho sRAMCena za frame
skóre / životy / popisek (text)SceneLabel — text jako fixed vrstva scényjedna malá textová bitmapa~0 (překresluje jen při změně textu)
pevný pás/panel, kterého se scéna nemá dotýkatrezervovaná zóna + HudBar0 (scratch buffer)0 (scéna ho nikdy nemaluje)
orámovaný widget / ukazatel, který se mění zřídkamalý Canvas (fixed vrstva)w*h*2 bajtů~0 dokud je statický (dirty-rect ho přeskočí)
animovaný řádek (gradient, pulzující ukazatel)StripDraw (nulový buffer)0překresluje každý frame

Zbytek této sekce je jak a proč u každého z nich.

The four status-bar approaches stacked: SceneLabel text, a flat HudBar strip with score + lives, a bevelled Canvas gauge, a StripDraw gradient bar


A. Textový řádek → SceneLabel (fixed vrstva scény)

Sekce “A. Textový řádek → SceneLabel (fixed vrstva scény)”
import picogame_ui as ui
score = ui.SceneLabel(scene, pg, FONT, 6, 4, TEXT_FG, BAR_BG) # x, y, fg, bg
score.set("SCORE %05d" % n) # překresluje JEN když se řetězec změní

Jak: SceneLabel přidá textový Sprite do scény jako fixed vrstvu (nezávislou na camera — zůstává na místě, zatímco svět scrolluje). .set() vymění bitmapu sprite jen když se řetězec liší; scene.refresh() ji kreslí jako každou jinou vrstvu. Neprůhledné bg zajistí, že nový text plně přepíše starý, takže není potřeba samostatné mazání.

Pro: triviální; nejlevnější HUD s pohyblivým textem; scrolluje správně (zůstává připnutý); používají ho všechny HW-ověřené hry. Proti: jen text (žádný rámeček, gradient ani tvar); je to vrstva scény, takže sprite procházející pod ní stojí dirty-rect překreslení toho překryvu.

Použij pro: skóre, počet životů, časovač, jednořádkový popisek nad scrollujícím světem.


B. Rezervovaná zóna + HudBar (řádek žije MIMO scénu)

Sekce “B. Rezervovaná zóna + HudBar (řádek žije MIMO scénu)”
scene, bufA, bufB = picogame_game.setup(background=SKY, top=18, bottom=22) # vykroj pásy
bar = ui.HudBar(pg, board.DISPLAY, bufA, 0, H - 22, W, 22, BAR_BG) # dolní pás
hearts = [bar.add(pg.Sprite(heart_bm, W - 16 - i * 20, H - 11)) for i in range(3)]
score = bar.label(FONT, 6, 4, WHITE, "SCORE 0")
bar.redraw() # vykresli jednou; pak redraw() JEN když se HUD změní
# ... později, při ztrátě života / tiku skóre:
hearts[2].visible = False; bar.set_text(score, "SCORE %d" % n); bar.redraw()

Jak: Scene(..., top=/bottom=/left=/right=) (vystavené přes picogame_game.setup) řekne scéně, aby vykreslovala jen vnitřní rect a ořezala každý dirty rect na něj — takže scéna nikdy nemaluje rezervovaný okraj. Ten okraj vlastníš ty. HudBar do něj kreslí přes pg.render pomocí scratch strip bufferu (např. bufA) — sám nedrží žádný buffer — vyplňuje plochým pozadím a blituje své sprite/labely. redraw() voláš jen když se něco změní.

Pro: odpověď „nastav jednou, překresli při změně” — 0 udržované RAM a 0 nákladů za frame (scéna se těch řádků doslova nemůže dotknout). Nejlepší pro postranní panely na šířku (left=/right= → sloupec Tetris/Fruitris), orámované hřiště (všechny čtyři), nebo pevný horní/dolní pás. Proti: pozadí je plochá barva (gradient → použij C nebo D); řádek je ve výchozím stavu statický (každou změnu řídíš přes redraw()); ukrajuje místo z herní plochy.

ChcešRezervujProč
skóre / životy / stavový pástop= / bottom=pevný řádek s nulovou prací za frame
postranní panely (skóre, další dílek)left= / right=sloupcová rozložení na šířku
orámované hřištěvšechny čtyřiscéna je vnitřní rect; rámeček je tvůj

C. Canvas řádek (udržovaný pixelový buffer, pro orámovaný/ukazatelový widget)

Sekce “C. Canvas řádek (udržovaný pixelový buffer, pro orámovaný/ukazatelový widget)”
bar = pg.Canvas(120, 20) # buffer o w*h*2 bajtech, který si držíš
bar.frame3d(0, 0, 120, 20, LIGHT, DARK) # zkosení; také fill_rect/line/circle/round_rect...
bar.fill_rect(2, 2, hp * 116 // 100, 16, GREEN)
scene.add(bar, fixed=True) # fixed = zůstává na místě, zatímco svět scrolluje
bar.move(8, 6)
# překresli ukazatel jen když se hp změní:
bar.fill_rect(2, 2, 116, 16, DARK); bar.fill_rect(2, 2, hp * 116 // 100, 16, GREEN)

Jak: Canvas je vrstva, která drží své pixely. Logika dirty-rect scény ji přeskakuje, dokud se nic nemění, takže statický Canvas nestojí nic za frame; když do něj kreslíš (tvary, zkosení, výplň ukazatele), ten rect se překreslí. Přidej ho jako fixed=True, aby nescrolloval.

Pro: plné vektorové primitivy (frame3d, fill_round_rect, circle, line, …) → skutečné orámované panely, zkosené ukazatele, tvarované výpisy; levné dokud je statické. Proti: drží w*h*2 bajtů — řádek 320×20 na celou šířku je ~13 KB, mrtvá zátěž na RAM-napjaté desce. Dimenzuj Canvas na widget, nikdy ne na celou obrazovku (od toho je B).

Použij pro: malý zkosený ukazatel, portrétový rámeček, orámovanou minimapu — cokoli s tvarem, co se mění jen občas.


D. StripDraw řádek (nulový buffer, pro ANIMOVANÝ řádek)

Sekce “D. StripDraw řádek (nulový buffer, pro ANIMOVANÝ řádek)”
def draw_energy(view, vx, vy, vw, vh): # view = Canvas na živý strip
view.clear(BAR_BG) # lokálně k view (0,0) == obrazovka (vx, vy)
for x in range(vw): # např. gradient / pulzující ukazatel
view.fill_rect(x, 0, 1, vh, ramp(x, energy))
scene.add(pg.StripDraw(draw_energy, 0, H - 16, W, 16), fixed=True) # fixed ve scrollující scéně

Jak: StripDraw nedrží žádný pixelový buffer. Při každém refresh, pro každý render strip překrývající jeho rect, zavolá tvůj callback(view, vx, vy, vw, vh) s Canvas view mířícím přímo na živý strip — kreslíš do framu přímo (fungují všechny Canvas primitivy). Jeho rect se překresluje každý frame.

Pro: 0 RAM i pro řádek na celou šířku a může se měnit každý frame zdarma (žádný buffer k synchronizaci) — gradienty, scanline ukazatele, pulzující energetický řádek, procedurální pás pozadí. Proti: platí CPU každý frame (je vždy dirty) a je ve screen-space — ve scrollující scéně ho přidej jako fixed=True, jinak se rozmaže.

Použij pro: jakýkoli řádek, jehož pixely se skutečně mění každý frame, nebo animovaný pás na celou šířku, na který nechceš utratit 13 KB.


Rozhodnutí na jeden nádech

Sekce “Rozhodnutí na jeden nádech”
  1. Jen text (skóre/životy/popisek)? → A SceneLabel.
  2. Pevný pás/panel, plochá barva, mění se při událostech? → B rezervovaná zóna + HudBar (0 RAM, 0 za frame — výchozí pro skutečný stavový řádek).
  3. Potřebuje tvar/zkosení/ukazatel, ale mění se zřídka? → C malý Canvas (dimenzovaný na widget).
  4. Pixely se mění každý frame (gradient, animace)? → D StripDraw.

Past, které se vyhnout: Canvas řádek na celou šířku pro statický text/plochý obsah — spálí ~13 KB pro nic. Použij A pro text nebo B pro pás místo toho; po C sáhni jen když widget skutečně má tvar a po D když skutečně animuje.

Pro ne-řádkové HUD prvky: víceřádkový rámeček zprávy → ui.TextBox; menu s kurzorem → ui.Menu.

Viz, jeden na přístup: A tutorials/01-bounce/step7_hud.py + examples/picogame_hud_demo.py (text přes font) · B examples/picogame_pictor.py (rezervovaný horní řádek skóre + dolní řádek životů), examples/picogame_playarea_demo.py (vystředěný sloupec + postranní panely) · C examples/picogame_canvas_demo.py (animovaný ukazatel na Canvas) · D examples/journey_hw/journey_mono.py (StripDraw silnice + rámeček dialogu; status_bar() je forma SceneLabel).


10. Noise — procedurální variace

Sekce “10. Noise — procedurální variace”
h = pg.fbm2d(tx * 0.1, ty * 0.1, octaves=4, seed=7) # 0..1 fraktální výškové pole
v = pg.value2d(x * 0.6, y * 0.6, seed=3) # 0..1 hladký value noise

Použij pro organickou, opakovatelnou variaci: terén/výškové mapy, rozvržení jeskyní, stínování mraků/oblohy, rozptýlenou dekoraci, zrnitost textury. Deterministické podle seed — stejný seed, stejný svět. Na zařízení je to rychlá fixed-point C implementace.

Nepoužívej pro herní náhodnost (na spawny/dropy použij PRNG/LCG) — noise je hladký a korelovaný, ne uniformní. fbm2d (víceoktávový) pro přirozený detail; value2d (jedna oktáva) když chceš jen hladké pole.

Viz: examples/journey_hw/journey240.py (tráva RPG, obloha shmup), picogame_demo_showcase.


11. Audio: samplovaný WAV vs. syntetizovaný vs. MIDI

Sekce “11. Audio: samplovaný WAV vs. syntetizovaný vs. MIDI”

Dva enginy, vybírané podle toho, odkud zvuk pochází — nahraný sample nebo generovaný za běhu — plus MIDI pro sekvencovanou hudbu. Sdílejí PWM výstup, ale směňují RAM za věrnost.

A. picogame_audio — samplovaný WAV (PWM + Mixer). Přehrává .wav soubory (a square-wave tone() pro testovací pípnutí). Vícehlasý Mixer překrývá SFX pod jeden hudební hlas.

import picogame_audio
audio = picogame_audio.Audio() # PWMAudioOut -> Mixer (4 hlasy)
audio.sfx(picogame_audio.tone(660, ms=80)) # fire-and-forget pípnutí (žádný soubor)
audio.music(audio.load("theme.wav")) # hlas 0, ve smyčce

Pro: jakýkoli zvuk, který umíš nahrát; přesná reprodukce; naprosto jednoduché. Proti: každý sample je rezidentní RAM (load() drží WaveFile naživu) — nákladné pro plnou sadu SFX; samply musí odpovídat formátu Mixeru (výchozí 22050 Hz / mono / 16-bit signed).

B. picogame_synth — syntetizovaný (synthio). Generuje audio v reálném čase (oscilátory

  • ADSR + LFO + filtry), takže celá banka SFX stojí téměř žádnou RAM — stavíš objekty Note, ne buffery.
import picogame_synth as snd
s = snd.Synth() # PWMAudioOut -> Mixer -> Synthesizer
zap = snd.note(72, snd.SQUARE, decay=0.08, # SFX, postavený jednou
bend=snd.pitch_bend(-12, 120)) # volitelný pitch slide (laser)
s.sfx(zap) # spouští se čistě při opakování

Pro: drobná RAM bez ohledu na počet zvuků; ladíš pitch/obálku/filtr v kódu; skvělé pro chiptune SFX. Proti: jen na zařízení (žádný simulátor — ošetři import); je to syntéza, takže nezní jako nahraný sample; vyžaduje synthio ve firmwaru (obě naše desky ho mají).

C. MIDI hudba (picogame_synth.load_midi). Soubor .midsynthio.MidiTrack na hudebním hlasu syntezátoru. Celá skladba za cenu syntezátoru — žádný audio sample vůbec.

s.music(snd.load_midi("tune.mid")) # sekvencované pozadí, ~0 sample RAM
ChcešPoužijRAM
nahrané SFX / skutečnou hudební stopuA picogame_audio (WAV)každý sample rezidentní
velkou banku chiptune SFXB picogame_synth (synthio)~0 (generované)
sekvencovanou hudbu na pozadíC load_midi na hlasu syntezátoru~0 (generované)
jen testovací pípnutípicogame_audio.tone()jeden krátký RawSample

Praktické pravidlo: synth pro mnoho malých efektů + sekvencovanou hudbu (RAM-levné, jen na zařízení); WAV když záleží na konkrétním nahraném zvuku a můžeš si dovolit bajty. Pozn.: přibalená dema běží na zařízení tiše (žádné audio zapojené) — zvuk je opt-in na hru. Nepékej zvukové háčky do buildu bez audia (device soubor journey zahodil své mrtvé volání sfx()/music=; sim/video verze je ponechává pro nahraný soundtrack).

Viz: picogame_audio.py (WAV) · picogame_synth.py + examples/picogame_train.py (synthio SFX) · tools/sfx_synth.py (offline chiptune pro videa).


import picogame_save
save = picogame_save.Save("arkanoid", {"hi": ("I", 0), "level": ("B", 1)})
data = save.load() # výchozí hodnoty pokud prázdné/poškozené/nesoulad klíčů
data["hi"] = max(data["hi"], score); save.save(data)

Použij picogame_save pro nejvyšší skóre, postup, nastavení — zapisuje microcontroller.nvm, který přežije restart i smazání filesystému. Založené na schématu, takže nový build se změněným schématem čistě spadne zpět na výchozí hodnoty.

Neukládej herní stav do souboru na CIRCUITPY — NVM je správné úložiště (žádné opotřebení FS, přežije reflash). Viz picogame_save_demo, MEMORY.md.


13. Úrovně: deklarativní scéna vs. kód

Sekce “13. Úrovně: deklarativní scéna vs. kód”
import picogame_scene
view = picogame_scene.load(pg, SCENE) # SCENE = pečený dict (sprite, tilemapy, zóny…)
solid = view.is_solid(tx, ty); pt = view.point("spawn")
SituacePoužij
ručně psané úrovně, editor úrovní, datově řízené mapypicogame_scene + webový editor → pečený SCENE dict
jednorázové / vysoce dynamické / procedurální scényprostý kód (postav Scene přímo)

Pravidlo: mnoho podobných úrovní nebo neprogramátoři, kteří je tvoří → deklarativně; jediná zakázková scéna → prostě ji nakóduj. Viz SCENE_FORMAT.md, editor/, picogame_platformer_scene.


14. RAM & výkon — rozhodnutí, na kterých na RP2040 záleží

Sekce “14. RAM & výkon — rozhodnutí, na kterých na RP2040 záleží”

RP2040 má ~138 KB použitelného Python heapu. Většina „proč se to nevejde / proč to spadlo” spadá pod několik voleb:

ProblémSáhni po
velká animovaná celoobrazovková plochaStripDraw (0 B) místo Canvas
velká statická plocha, které se nevyhnešCanvas dimenzovaný na obsah; nebo ho podlož picogame_arena
hodně grafiky / mnoho framů, které chceš rezidentnízmraz do flash (FROZEN_MPY_DIRS) → referencované z flash, ~0 heap (přístup PicoLibSDK)
heap fragmentuje při dlouhém běhu (volné ≠ souvislé)uchop velké buffery brzy; arena; nevíř
spawny/úrovně víří objektyPool + recyklace; gc.collect() mezi scénami
sprite/bitmapa příliš velkáPAL8 = 1 B/px; velikost = w*h*frames; zahoď přebytečné framy; ořež okraje
pád importu na velkém .pydodej .mpy; velká data jako bytes, ne literál array

Pravidla:

  • Vybírej podle pohybu (Canvas vs. StripDraw), předem alokuj (pooly/arena), nikdy nevytvářej/neuvolňuj velké nebo mnoho objektů za frame, gc.collect() na hranicích scén.
  • gc.mem_free() je celkové volné, ne největší souvislý blok — viz MEMORY.md.
  • Na napjatém buildu sniž strip_h (např. 12) pro rezervu; dodej .mpy.

Viz: HARDWARE.md, MEMORY.md, lib/picogame_arena.py, examples/journey_hw/ (StripDraw + monolit bez arény).


15. Kde grafika & zvuk žijí: frozen vs. file-in-RAM vs. streaming

Sekce “15. Kde grafika & zvuk žijí: frozen vs. file-in-RAM vs. streaming”

Pixely bitmapy musí být někde a na 138 KB heapu o tom volba rozhoduje, zda se hra vejde. Nejdřív past: CIRCUITPY je FAT filesystém ve flash, ale NENÍ memory-mapped — import velkého .mpy nebo čtení souboru ho zkopíruje do heapu. Jen frozen data jsou zero-copy (spuštěná/čtená na místě z flash). Takže „je to ve flash” ≠ „je to zdarma”. Tři úrovně:

A. Zmrazené do flash (FROZEN_MPY_DIRS). Grafika je modul s literálem bytes (DATA = b'...') zmrazený do firmwaru; pg.Bitmap(DATA, ...) ho referencuje na místě — ~0 heap. To je přístup PicoLibSDK: většina rezidentní grafiky na napjatém buildu.

import dino_art # frozen modul: DATA=b'...', WIDTH, FRAMES, ...
spr = pg.Sprite(dino_art.bitmap(pg), x, y) # bitmap() obalí DATA ve flash, bez kopie

Proti: změna grafiky znamená reflash; zvětšuje obraz firmwaru.

B. Soubor → RAM (readinto jednou). Dodej .bin na CIRCUITPY, přečti ho do předem dimenzovaného bytearray při načtení, naporcuj ten blob do Bitmap. Celý list je rezidentní (w*h*frames bajtů; PAL8 = 1 B/px).

size = os.stat(BIN)[6]; blob = bytearray(size)
with open(BIN, "rb") as f: f.readinto(blob) # JEDNA čistá alokace -- NE read() (ten fragmentuje)
mv = memoryview(blob)
spr_bmp = pg.Bitmap(mv[off:off + stride * h], w, h, format=pg.PAL8, palette=pal,
frames=n, stride=stride, transparent=0)

Pro: výměna grafiky bez reflash; rychlá iterace. Proti: drží celý list v heapu.

C. Streaming (picogame_stream.StreamSheet). Otevři .bin jednou a drž jeden frame v RAM; use(i) seekne + readinto ten frame na vyžádání. RAM ≈ jeden frame bez ohledu na počet framů — způsob jak ukázat sprite sheet příliš velký na rezidentní držení.

sheet = picogame_stream.StreamSheet(pg, "jill.bin", 64, 100, 11, PAL, transparent=0)
spr = pg.Sprite(sheet.bitmap, x, y) # sdílený jednoframový buffer
sheet.use(frame) # čtení z flash při změně -> sprite ho ukáže

Proti: čtení z flash na změnu framu (v pořádku pro pár velkých sprite na animačních rychlostech, plýtvavé pro stovky drobných); .bin musí být frame-major (tools/pack_sheet.py).

PřístupCena heapuVýměna grafiky bez reflash?Nejlepší pro
Frozen (FROZEN_MPY_DIRS)~0 (XIP z flash)nevětšina rezidentní grafiky na napjatém buildu
Soubor → RAM (readinto)celý list w*h*framesanolisty, které se vejdou + rychlou iteraci grafiky
Streaming (StreamSheet)~jeden frameanopár VELKÝCH sprite/pozadí, které se nevejdou

Relative heap cost of the three tiers: frozen is a sliver (~0 bytes), streaming a small bar (~one frame), file-to-RAM a full-width bar (whframes)

Pravidlo: zmraz to, co vždy potřebuješ, streamuj těch pár velkých věcí, které se nevejdou, drž malé často používané listy v RAM — kombinuj všechny tři v jedné hře.

Viz: examples/dino_art.py (frozen) · examples/cavern_assets.py (soubor→RAM přes readinto) · examples/pic_jill.py + examples/picogame_pictor.py (streaming) · tools/pack_sheet.py.


16. Vstup: picogame_input (automatický keypad / digitalio backend)

Sekce “16. Vstup: picogame_input (automatický keypad / digitalio backend)”

Existuje jeden vstupní modul — picogame_input — se stabilním veřejným API (Buttons + UP/DOWN/.../ALL + profily desek + is_pressed/just_pressed/just_released/clear). Backend si vybírá automaticky podle desky: vestavěný CircuitPython modul keypad, kde existuje, jinak digitalio polling. Kód tvé hry je v obou případech identický:

import picogame_input as inp
btns = inp.Buttons() # ve výchozím PicoPad profil; předej vlastní pro jinou desku
btns.poll()
if btns.is_pressed(btns.LEFT): ...
if btns.just_pressed(btns.A): ...

Backend A — keypad (výchozí, kde je dostupný). Stojí na CircuitPython modulu keypad: C vrstva skenuje klávesy na pozadí s hardwarovým debounce a řadí události stisku/uvolnění do fronty, kterou poll() vyprazdňuje. just_pressed pochází z událostí stisku, takže rychlé tapy nikdy neuniknou a bounce nemůže vyvolat falešnou hranu. To opravilo „duchy pohyby” Trainu.

Backend B — digitalio polling (automatický fallback). Kde keypad chybí (simulátor, deska bez něj), Buttons transparentně spadne zpět na surové vzorkování pinů každý poll() — vzorkování po framech už filtruje bounce kratší než frame. Žádná závislost, plně funguje v simulátoru. Předej prefer_keypad=False pro vynucení této cesty.

Běžíš na…Backend, který dostaneš
skutečném HW s keypad (PicoPad atd.)keypad — hardwarový debounce + fronta událostí, žádné ztracené tapy
simulátoru nebo desce bez keypaddigitalio polling — automatický fallback, stejné API

Pravidlo: nevybíráš — picogame_input zvolí nejlepší backend pro danou desku a hra běží beze změny všude. Volej clear() při přechodech scén/menu, aby podržené tlačítko nepropustilo hranu do další obrazovky.

Viz: examples/picogame_train.py a většina ostatních příkladů (vše používá picogame_input).


17. Časování & pohyb: Clock (dt) vs. FixedStep

Sekce “17. Časování & pohyb: Clock (dt) vs. FixedStep”

picogame_clock dává dva tvary smyčky pro „nedovol snímkové frekvenci měnit hru”:

A. Clock(fps) — frame cap + delta time. tick() spí do hranice framu a vrací reálné dt; škáluj pohyb podle dt, aby tap nemrštil sprite vysokou rychlostí při vysokých FPS.

clock = picogame_clock.Clock(30)
while True:
dt = clock.tick() # omezí na 30 FPS, vrací uplynulé sekundy
x += speed * dt # pohyb nezávislý na snímkové frekvenci
scene.refresh()

Pro: naprosto jednoduché; plynulý proměnný pohyb. Proti: fyzikální kroky se liší velikostí, takže kolize / stohování se mohou chovat trochu jinak při různých snímkových frekvencích (nedeterministické).

B. FixedStep(step_fps) — akumulátor s pevným časovým krokem. Spouští herní logiku ve stejných krocích bez ohledu na čas vykreslení; vykresluješ jednou po dohnání. Deterministická fyzika.

fixed = picogame_clock.FixedStep(60)
while True:
for dt in fixed.steps(): # 0..max_steps kroků s konstantním dt pro uplynulý čas
update(dt) # dt je vždy 1/60 -> reprodukovatelné
scene.refresh()

Pro: deterministické, stabilní kolize/stohování (plošinovky, pinball, cokoli fyzikálního); „nevybuchne” při výkyvu framu (omezuje kroky, aby se vyhnul spirále smrti). Proti: trochu více instalatérství; vykreslovací a logické frekvence jsou oddělené.

Tvá hra je…Použij
casual / arkádová, pohyb jen potřebuje být plynulýClock (dt-škálovaný)
fyzikální — skoky, odrazy, stohování musí být reprodukovatelnéFixedStep
v pohodě na stálém capu, žádnou dt matematiku nechcešClock a ignoruj dt (pevný pocit)

Pravidlo: Clock pro plynulost, FixedStep pro determinismus. Oba jsou čistý Python (žádný firmware potřeba); Clock.tick_async() existuje, pokud běžíš asyncio smyčku.

Viz: examples/picogame_pinball.py, examples/picogame_tetris.py, examples/picogame_arkanoid240.py (všechny používají Clock) · FixedStep žije v lib/picogame_clock.py s rozpracovaným příkladem ve svém docstringu.


18. Vykreslovací backend: rychlý DMA Display vs. přenositelný bus.send

Sekce “18. Vykreslovací backend: rychlý DMA Display vs. přenositelný bus.send”

Scene umí tlačit pixely dvěma způsoby; picogame_game.setup(fast=…) jeden vybere. Stejný kreslicí kód, jiný transport:

scene, a, b = picogame_game.setup(fast=True) # rychlý DMA Display, kde ho port poskytuje
# scene, a, b = picogame_game.setup(fast=False) # přenositelný bus.send (funguje na jakémkoli portu)

A. Rychlý Display (async/queued DMA, double-buffered). Backend specifický pro port (pg.Display) blituje další strip, zatímco aktuální se přenáší přes SPI, a streamuje raw DMA s otevřeným GRAM oknem. Poskytuje ho port raspberrypi (PicoPad, RP2350, PicoSystem) a port espressif (ESP32-S3, přes esp-idf queued DMA). To je výchozí a to, co na těch deskách chceš.

B. Přenositelný bus.send. Blituje strip, pošle ho přes blokující bus.send displeje, opakuj — jeden buffer, žádný překryv CPU/přenosu. Správné na jakémkoli CircuitPython portu; je to automatický fallback, kde žádný rychlý backend neexistuje, a setup() ho vybere transparentně (pg.Display(display) if fast and hasattr(pg, "Display") else display).

SituaceBackend
PicoPad / RP2350 / PicoSystem / ESP32-S3rychlý (fast=True, výchozí)
port bez common-hal/picogame/Display.cpřenositelný (automaticky)
A/B-měření obou na jedné descepřepni fast= (viz examples/picogame_bench.py)

Kolik to vlastně přinese? Jediná výhoda rychlé cesty je překryv per-strip CPU blitu s SPI přenosem (oba backendy už DMA samotný přenos), takže skryje nejvýše min(blit, transfer) na strip — a jen když překreslení zabírá více stripů. Naměřeno na skutečném hardwaru (picogame_bench.py):

ZátěžZisk FPS (rychlý vs. přenositelný)
typická hra — malé dirty rect (jeden strip)~0 % (no-op; jeden strip = blokující první send)
celoobrazovkové překreslení, lehká kompozice~5–10 % (ESP32-S3 +4 %, PicoPad +8 %)
celoobrazovkové, těžký per-pixel blit (velké škálované/rotované sprite)až ~25–30 % (PicoPad +27 %)

Takže zisk roste s cenou per-strip blitu — je největší přesně na nejtěžších scénách, ne plochých 5–10 %. Vrcholí, když blit ≈ transfer, a klesá, jakmile je scéna tak blit-bound, že skrytý přenos je menší člen. Pro normální dirty-rect hru očekávej v podstatě nic; výhra se projeví na celoobrazovkových efektech a kompozici náročné na transformace.

Praktická poznámka: deska zapne rychlou cestu přes CIRCUITPY_PICOGAME_FAST_DISPLAY = 1 ve svém mpconfigboard.mk (hradluje jak typ pg.Display, tak common-hal soubor). fast= se málokdy dotkneš — nech ho na True; sám se sníží na přenositelný, kde je potřeba (stojí ~1 KB flash a nikdy neuškodí).

Viz: examples/picogame_bench.py (N poskakujících sprite + FPS; přepínače FAST/STRESS/HEAVY reprodukují tři řádky výše).


Rychlý rejstřík — „Chci…”

Sekce “Rychlý rejstřík — „Chci…””
CílSekce
nakreslit mapu / scrollující svět§1 Tilemap, §4, §8
nakreslit animovaný celoobrazovkový efekt levně§1 StripDraw, §4
nakreslit stavový řádek / HUD / panel / ukazatel§9 (čtyři způsoby: SceneLabel · rezervovaná zóna · Canvas · StripDraw)
rotovat / škálovat sprite§2
přebarvit / flash / duch / rotace o 90° sprite (blit efekty)§2 (shadow/flash/tint/dither/transpose)
screen shake / fade / plynulá camera / gradientová oblohapicogame_fx (REFERENCE.md)
animovaná voda/láva nebo přebarvení přes palettepicogame_palette (REFERENCE.md)
seedovaný/deterministický random, férové spawny (7-bag)picogame_rand (REFERENCE.md)
coyote time / jump bufferingpicogame_input.Timer (REFERENCE.md)
zmenšit tileset (dedup rotovaných/zrcadlených tile)png2picogame.py --dedup (PICOGAME.md)
animovat sprite§3
detekovat zásahy§5
vystřelit hodně střel / spawnit nepřátele§6, §7
následovat hráče s camera§8
nechat terén / oblohu přirozeně variovat§10
přehrávat zvuky (WAV vs. synth vs. MIDI)§11
uložit nejvyšší skóre§12
tvořit mnoho úrovní§13
vejít se do RAM§14
uložit velkou grafiku / mnoho framů (frozen vs. RAM vs. streaming)§15
číst tlačítka (automatický keypad / polling backend)§16
držet pohyb nezávislý na snímkové frekvenci (dt vs. fixed step)§17
pochopit rychlé DMA vs. přenositelné vykreslování§18