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:
- REFERENCE.md — jednostránkový tahák signatur.
- PICOGAME.md — kompletní popis API.
- HARDWARE.md / MEMORY.md — rozpočet RAM a fragmentace.
- ENGINE_ERGONOMICS.md — proč je engine takto tvarovaný.
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žij | Proč / na co si dát pozor |
|---|---|---|
| pohybující se objekt (hráč, nepřítel, střela, mince) | Sprite | základní stavební prvek; pro mnoho identických použij Pool |
| velkou mřížku (mapa, dlážděné pozadí, cihlová zeď) | Tilemap | 1 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řídka | Canvas | drží pixely → dirty-rect ji přeskakuje, dokud je statická. Stojí w*h*2 bajtů |
| animovaný celoobrazovkový efekt (silnice, gradient, obloha, plazma) | StripDraw | kreslí přímo do stripu, 0 bajtů. Překresluje každý frame |
| jednorázový blit bez udržované Scene | render() | 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 — celoobrazovková animovaná plocha (pseudo-3D silnice + obloha), kreslená přímo do stripů s nulovou RAM bufferu.
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| Situace | Použij | Proč |
|---|---|---|
| občasné / plynulé škálování (pulz mince, růst bosse, hloubka) | runtime scale | jedna bitmapa, libovolný faktor |
| občasná rotace, libovolný úhel (naklánějící se auto, otáčení) | runtime angle | jedna 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ý frame | předpečené framy | runtime affine je per-pixel; pečené framy jsou prostý blit |
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:
| Chci | Použij | Poznámky |
|---|---|---|
| vržený stín / ztmavení sprite | spr.shadow = True | neprů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áře | spr.tint = RED (0=vyp) | násobí — zachovává stínování sprite (na rozdíl od flash) |
| duch / mlha / rozplynutí sprite | spr.dither = 0..16 | Bayer 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á).

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_animwalk = picogame_anim.FrameAnim(spr, [0, 1, 2, 1], fps=8) # založeno na čase# každý frame: walk.tick(dt)| Situace | Použij |
|---|---|
| pár sprite, pevný herní tick | ruč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é stavy | picogame_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.

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řístup | Cena |
|---|---|---|
| plochá barva | background Scene | zdarma |
| dlážděná / opakující se scenérie | Tilemap z tile bitmap | 1 B/buňku |
| hrubě stínovaná obloha / terén | noise → 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 pole | StripDraw | 0 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
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 boxif 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 collideif collide.hit(a, b): ... # AABB přímo z pozic/velikostí dvou spriteif 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| Situace | Použ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).

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_poolbullets = 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)| Situace | Použ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()
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 obrazovceSvě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ětaoy = 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.

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 s | RAM | Cena za frame |
|---|---|---|---|
| skóre / životy / popisek (text) | SceneLabel — text jako fixed vrstva scény | jedna malá textová bitmapa | ~0 (překresluje jen při změně textu) |
| pevný pás/panel, kterého se scéna nemá dotýkat | rezervovaná zóna + HudBar | 0 (scratch buffer) | 0 (scéna ho nikdy nemaluje) |
| orámovaný widget / ukazatel, který se mění zřídka | malý 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) | 0 | překresluje každý frame |
Zbytek této sekce je jak a proč u každého z nich.

A. Textový řádek → SceneLabel (fixed vrstva scény)
Sekce “A. Textový řádek → SceneLabel (fixed vrstva scény)”import picogame_ui as uiscore = ui.SceneLabel(scene, pg, FONT, 6, 4, TEXT_FG, BAR_BG) # x, y, fg, bgscore.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ásybar = ui.HudBar(pg, board.DISPLAY, bufA, 0, H - 22, W, 22, BAR_BG) # dolní páshearts = [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š | Rezervuj | Proč |
|---|---|---|
| skóre / životy / stavový pás | top= / 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ři | scé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 scrollujebar.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”- Jen text (skóre/životy/popisek)? → A
SceneLabel. - 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). - Potřebuje tvar/zkosení/ukazatel, ale mění se zřídka? → C malý
Canvas(dimenzovaný na widget). - 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é polev = pg.value2d(x * 0.6, y * 0.6, seed=3) # 0..1 hladký value noisePouž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_audioaudio = 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čcePro: 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 snds = snd.Synth() # PWMAudioOut -> Mixer -> Synthesizerzap = 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 .mid → synthio.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žij | RAM |
|---|---|---|
| nahrané SFX / skutečnou hudební stopu | A picogame_audio (WAV) | každý sample rezidentní |
| velkou banku chiptune SFX | B 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).
12. Ukládání (NVM)
Sekce “12. Ukládání (NVM)”import picogame_savesave = 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_sceneview = picogame_scene.load(pg, SCENE) # SCENE = pečený dict (sprite, tilemapy, zóny…)solid = view.is_solid(tx, ty); pt = view.point("spawn")| Situace | Použij |
|---|---|
| ručně psané úrovně, editor úrovní, datově řízené mapy | picogame_scene + webový editor → pečený SCENE dict |
| jednorázové / vysoce dynamické / procedurální scény | prostý 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ém | Sáhni po |
|---|---|
| velká animovaná celoobrazovková plocha | StripDraw (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íří objekty | Pool + 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 .py | dodej .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 kopieProti: 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ý buffersheet.use(frame) # čtení z flash při změně -> sprite ho ukážeProti: č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řístup | Cena heapu | Výměna grafiky bez reflash? | Nejlepší pro |
|---|---|---|---|
Frozen (FROZEN_MPY_DIRS) | ~0 (XIP z flash) | ne | většina rezidentní grafiky na napjatém buildu |
Soubor → RAM (readinto) | celý list w*h*frames | ano | listy, které se vejdou + rychlou iteraci grafiky |
Streaming (StreamSheet) | ~jeden frame | ano | pár VELKÝCH sprite/pozadí, které se nevejdou |

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 inpbtns = inp.Buttons() # ve výchozím PicoPad profil; předej vlastní pro jinou deskubtns.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 keypad | digitalio 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).
| Situace | Backend |
|---|---|
| PicoPad / RP2350 / PicoSystem / ESP32-S3 | rychlý (fast=True, výchozí) |
port bez common-hal/picogame/Display.c | přenositelný (automaticky) |
| A/B-měření obou na jedné desce | př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íl | Sekce |
|---|---|
| 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á obloha | picogame_fx (REFERENCE.md) |
| animovaná voda/láva nebo přebarvení přes palette | picogame_palette (REFERENCE.md) |
| seedovaný/deterministický random, férové spawny (7-bag) | picogame_rand (REFERENCE.md) |
| coyote time / jump buffering | picogame_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 |