Přeskočit na obsah

Animace a sekvence

Tyto tři moduly ti ušetří ruční správu frame čítačů. picogame_anim pohání snímky sprite na základě času, picogame_seq ti umožní psát časové osy jako generátory a picogame_cutscene vykreslí obrázek přes celou obrazovku s minimálními nároky na RAM. Jsou to pure-Python helpery nad enginem - signatury najdeš v /cs/reference/.

Co to je: malý driver pro snímkovou animaci. Předáš mu seznam indexů snímků a fps, každý frame zavoláš tick(dt) se skutečně uplynulými sekundami a on sám nastaví sprite.frame. Sáhni po něm vždy, když bys jinak psal sprite.frame = (f // 4) % n - animace bude nezávislá na snímkové frekvenci (řízená skutečným dt, ne počtem volání smyčky) a matematiku dostaneš z update smyčky pryč.

Máš dvě třídy:

  • FrameAnim(sprite, frames, *, fps=8, loop=True) - přehraje jednu sekvenci. frames je list/tuple indexů snímků (předán odkazem, nikoli kopií - považuj ho za read-only). fps je rychlost animace; loop je keyword-only. Při vytvoření nastaví sprite na frames[0].
    • .tick(dt) - posune o dt skutečných sekund. Akumuluje čas a přepne snímek, když uplynulo dost. Pokud neloopující animace skončila nebo je frames prázdný, nedělá nic. Nic nevrací.
    • .configure(frames, fps=8, loop=True) - přesměruje tuto instanci na novou sekvenci a resetuje ji. Vrací self. Umožní ti znovu použít jednu FrameAnim místo alokace nové při každém přepnutí.
    • .reset() - vrátí na snímek 0, smaže příznak done a akumulátor času.
    • Atributy ke čtení: .done (True, když neloopující animace dosáhla posledního snímku), .i (aktuální index do frames), .frames, .fps, .loop.
  • AnimatedSprite(sprite, anims) - sprite s pojmenovanými stavy. anims je dict {name: (frames, fps, loop)}. Interně drží jednu znovu použitelnou FrameAnim (bez alokace při přepnutí).
    • .play(name) - přepne na danou pojmenovanou animaci. Vyhledá anims[name] a překonfiguruje. Volání play se jménem, které už hraje, je no-op, takže je bezpečné volat každý frame.
    • .tick(dt) - posune aktuální animaci.
import picogame_anim
hero = picogame_anim.AnimatedSprite(self.spr, {
"run": (DINO_RUN, 12, True),
"jump": ((DINO_JUMP,), 1, False),
})
hero.play("run")
# each frame, with dt = real seconds since last frame:
hero.play("jump" if self.jumping else "run") # cheap to call every frame
hero.tick(dt)

Pro jednu sekvenci (rotující mince, animace chůze) přeskoč dict a použij FrameAnim přímo: spin = picogame_anim.FrameAnim(sprite, list(range(COIN_FRAMES)), fps=15).

Pozor na: musíš předat skutečné dt (sekundy za snímek, např. z picogame_clock), ne počet snímků - pokud předáš 1, poběží animace rychlostí fps snímků za volání. List frames je držen odkazem, takže ho neměň, dokud je používán. .done se stane True pouze pro loop=False.

Co to je: způsob, jak psát časovanou a postupnou logiku jako generátory (coroutine vzor). Každý yield znamená “probuď mě příští frame”; Seq posune jeden generátor o jeden krok per tick(). Sáhni po něm pro cutscény, intra, postupnou AI a jakoukoliv časovou osu “dělej X po N snímcích”, která by se jinak proměnila v spleti frame čítačů a if-podmínek. Podsekvence skládej pomocí yield from.

Pomocníci pro generátory (volej přes yield from):

  • wait(frames) - pauza na frames snímků (tolikrát yields a nic nedělá).
  • over(frames, fn) - obecný tween: volá fn(t) každý frame s t rostoucím od 0 do 1 (konkrétně i/frames pro i v 1..frames) po dobu frames snímků.
  • move_over(sprite, x, y, frames) - lineárně posune sprite z jeho aktuální pozice na (x, y) za frames snímků přes sprite.move(...).

Driver:

  • Seq(gen=None) - obalí jeden generátor. Pokud je gen None, začíná jako .done.
    • .start(gen) - nasměruje ho na (nový) generátor a smaže done. Vrací self, takže je znovu použitelný.
    • .tick() - posune na další yield. Zachytí StopIteration a nastaví .done. Vrací příznak done (True po skončení), takže se na něj můžeš větvit.
    • .done - True, když sekvence skončila.
import picogame_seq as seq
def intro(hero, label):
yield from seq.wait(30)
label.set("GO!")
yield from seq.move_over(hero, 120, hero.y, 20) # glide over 20 frames
s = seq.Seq(intro(player, hud))
# each frame:
if not s.tick(): # advances one step; True once the intro is over
... # still running

Pozor na: tick() posune přesně o jeden krok (na další yield) za volání, takže ho spouštěj jednou za herní frame. Vše mezi yieldy se provede v jednom snímku - těžkou práci dávej za yield. Seq spouští jeden generátor; chceš-li souběžně více časových os, dej každé vlastní Seq, nebo je slučuj pomocí yield from uvnitř jednoho generátoru.

Co to je: přehrávač obrázků přes celou obrazovku s minimální spotřebou RAM. Snímek 320x240 má 150 KB v RGB565 (75 KB v PAL8), což se nevejde do ~138 KB heapu - viz /cs/memory/. Místo toho obrázek žije na flashi jako raw, row-major soubor a modul ho streamuje po jednom ~24řádkovém bandu skrz malý znovu použitý buffer, přičemž každý band blituje přímo na LCD. V RAM je vždy jen jeden band; po vykreslení si obrázek drží LCD, takže statická cutscéna pak nestojí nic. Sáhni po něm pro úvodní obrazovky, příběhové panely, mapy a mezichapterové karty.

Raw soubor si nejprve upečeš pomocí tools/bake_image.py (PNG na wire-order RGB565 řádky, nebo PAL8 plus paletu). Formáty bitmap enginu najdeš v /cs/scene-format/ a informace o displeji a tlačítkách v /cs/hardware/.

  • show(pg, display, buffer, path, w=320, h=240, band=24, palette=None) - vykreslí obrázek z path na display, streamuje band po bandu. buffer je tvůj strip buffer. Předej palette pro čtení 1-bajt-na-pixel PAL8 souboru; pro 2-bajtový RGB565 ho vynech. Nic nevrací.
  • play(pg, display, buffer, btn, path, w=320, h=240, band=24, palette=None) - zavolá show(), pak blokuje (polling btn) dokud není stisknuto A nebo B, pak se vrátí. Použij pro obrazovky “stiskni pro pokračování”; show() použij, když chceš pokračovat ve vlastní smyčce.
import picogame_cutscene as cut
import board
cut.play(pg, board.DISPLAY, bufA, btn, "intro.dat") # RGB565
cut.play(pg, board.DISPLAY, bufA, btn, "map.dat", palette=PAL) # PAL8 + palette

Pozor na: band MUSÍ dělit h beze zbytku (240 % 24 == 0), jinak ti budou chybět řádky. buffer musí mít alespoň w * band * 2 bajtů (RGB565) - nebo w * band pro PAL8. Soubor musí být raw, row-major, v wire order z bake_image.py; normální PNG nebude fungovat. play() zcela zablokuje tvou herní smyčku po dobu čekání na tlačítko.