Přeskočit na obsah

Efekty & juice

Vrstva “juice”: třesení obrazovky, dither přechody, plynulé tweeny, sledovací kamera, rastrové pozadí a triky s paletou. Všechno je čistý Python nad enginem - bez změny firmware - a většina z toho nestojí skoro žádnou RAM, protože kreslí po scanlinech přes pg.StripDraw nebo mutuje paletu místo pixelů. Holé signatury najdeš v /cs/reference/; tato stránka popisuje chování jednotlivých symbolů a záludnosti.

Pomocné efekty, které instancuješ k Scene a voláš na nich tick() každý frame. Sáhni po nich, když hra funguje, ale působí ploše: zásah musí víc praštit (Shake, InvertFlash), přechod potřebuje dýchat (Fade), hodnota by měla plynule dojíždět místo skočit (Tween), svět je větší než obrazovka (Camera) nebo pozadí chce hloubku (Sky, Scanlines).

import picogame_fx as fx

fx.Shake - třesení obrazovky na principu trauma

Sekce “fx.Shake - třesení obrazovky na principu trauma”

Doznívající shake, který se skládá s tvou kamerou místo aby s ní bojoval. Přidávej trauma při nárazech, pak každý frame volej tick().

  • Shake(scene, max_offset=6, decay=0.03, seed=0x9E37) - max_offset je maximální pixel offset (asi 6 sedí na 320x240; nad 10 to zakrývá dění). decay je trauma ztracené za frame (asi 0.03 vypadá jako “kopnutí”, ne “vibrování”).
  • .add(amount) - přidá trauma v rozsahu 0..1, oříznuté na 1.0. Asi 0.6 na zásah nebo explozi, 0.15 na malý náraz. Trauma se před použitím umocní na druhou, takže malé události skoro netřesou a velké praští naplno.
  • .tick(cam_x=0, cam_y=0) - přidá doznívající náhodný offset k (cam_x, cam_y) a zavolá scene.set_view s výsledkem. Vrací True, dokud shake trvá. Předej sem offset kamery, aby shake a pohybující se kamera nevolaly set_view zvlášť a nepřepisovaly si výsledky.
import picogame_fx as fx
shaker = fx.Shake(scene, max_offset=6)
# ...při zásahu:
shaker.add(0.8)
# ...každý frame (bez kamery, takže dej 0,0):
shaker.tick(0, 0)

Záludnosti: Shake si bere scene.set_view jen pro sebe, dokud běží - pokud kamerou taky pohybuješ ručně, předej ten offset do tick(cam_x, cam_y) místo aby ses volal set_view zvlášť. Viz /cs/hardware/ pro velikost obrazovky, vůči které max_offset ladíš.

fx.Fade - dither přechod / ztmavení / záblesk

Sekce “fx.Fade - dither přechod / ztmavení / záblesk”

Overlay přes StripDraw, který přes rect rastruje barvu řazeným (Bayerovým) ditherem - žádné alpha blending, klasický 1-bit/Game Boy look. V klidovém stavu se schoulí na rect 0x0, takže na dirty-rect rendereru nestojí nic.

  • Fade(scene, width, height, x=0, y=0, color=0, cell=8) - pokryje rect (x, y, width, height); výchozí hodnoty pokryjí celou obrazovku. Sub-rect ztmaví jen tu oblast (panel za dialogem, postranní panel). color=0 je černá; cell je velikost dither bloku. Přidáno do scény s fixed=True, takže ignoruje kameru.
  • .set(level) - skočí okamžitě na level (0 = průhledné .. 16 = plné). Použij 16 pro start jako neprůhledný před fade-in.
  • .to(target, speed=2.0) - postupuje k target rychlostí speed levelů za frame. Vrací self.
  • .out(speed=2.0) / .into(speed=2.0) - zkratky pro to(16) (k neprůhledné) a to(0) (k průhledné).
  • .dim(level=8) - skočí na částečné ztmavení, např. 50% dim za menu. .clear() skočí na 0.
  • .pulse(level=12, speed=2.0) - vystoupá na level a pak automaticky zpět na 0; plynulý celoobrazovkový záblesk. Nech level pod 16, ať zůstane průhledný dither, nikdy plná zeď.
  • .tick() - posune level k target o speed. Vrací True, když je cíl dosažen.
  • .is_done (property) - True, když level == target.
import picogame_fx as fx
fader = fx.Fade(scene, W, H)
# ...spusť fade do černé:
fader.out(speed=2)
# ...každý frame, fade zpět, jakmile dosáhneme černé:
if fading and fader.tick():
fader.into(speed=2)

Záludnosti: level běží od 0 do 16, ne 0..255 nebo 0.0..1.0. Bílý Fade rychle pulzující (fx.Fade(scene, W, H, color=pg.rgb565(255, 255, 255)).pulse()) je levný záblesk při zásahu, ale InvertFlash níže je ještě levnější.

fx.Tween - plynulé dojíždění skaláru k cíli

Sekce “fx.Tween - plynulé dojíždění skaláru k cíli”

Exponenciální ease-out pro jedinou hodnotu prováděný každý frame: UI posuny, pop-up škálování, číslo, které by mělo “dohánět” plynule. Žádné keyframes ani rozvrhy.

  • Tween(value=0.0, speed=0.2) - speed je podíl zbývající mezery, který se uzavře každý frame (0..1).
  • .to(target, speed=None) - nastav nový cíl (a volitelně novou rychlost). Vrací self.
  • .set(value) - okamžitě snapne hodnotu i cíl na value.
  • .tick() - posune hodnotu o speed zlomek k cíli a vrátí novou hodnotu. Snapne přesně, když je rozdíl do 0.01.
  • .is_done (property) - True, jakmile se hodnota rovná cíli.
import picogame_fx as fx
y = fx.Tween(0)
y.to(100) # posuň panel dolů na y=100
# ...každý frame:
panel.y = int(y.tick())

Záludnosti: pouze ease-out, takže nikdy nepřestřelí ani neodskočí. tick() vrací hodnotu - čti návratovou hodnotu, ne .value, pokud chceš čerstvě posunuté číslo.

fx.Camera - plynulá sledovací kamera

Sekce “fx.Camera - plynulá sledovací kamera”

Sleduje bod ve světě a produkuje offset pohledu scény, vystředěný a volitelně omezený na velikost světa. Použij ji, když je level větší než obrazovka.

  • Camera(scene, w, h, lerp=0.18, world_w=0, world_h=0) - w/h je velikost obrazovky; lerp je vyhlazování sledování za frame; world_w/world_h (pokud nejsou nula) omezí pohled, aby nepřesáhl okraj světa.
  • .follow(tx, ty, snap=False) - posouvá střed kamery k (tx, ty) o lerp, nebo tam snapne s snap=True. Vrací self, takže můžeš řetězit.
  • .apply() - vypočítá offset a přímo zavolá scene.set_view. Bez alokace; vrací None. Použij, když není žádný shake.
  • .offset() - vypočítá a vrátí offset jako n-tici (ox, oy) (alokuje). Předej to do Shake.tick(ox, oy) pro složení obou efektů.
import picogame_fx as fx
cam = fx.Camera(scene, W, 240, world_w=bounds_w)
# ...každý frame:
cam.follow(player.x, 120).apply()

Záludnosti: apply() i Shake volají scene.set_view, takže nespouštěj obě naslepo. Pro kombinaci si vezmi ox, oy = cam.follow(...).offset() a předej to do shaker.tick(ox, oy), ať se shake skládá na kameru. Viz /cs/scene-format/, co set_view dělá s pohledem.

fx.Sky - vertikální gradientní pozadí

Sekce “fx.Sky - vertikální gradientní pozadí”

Gradientní pásmo po scanlinách (obloha, backdrop, den-noc) kreslené přes StripDraw s nulovou RAM - žádný buffer. Přidej ho jako první; je to vrstva na pozadí.

  • Sky(scene, x, y, w, h, top, bottom) - vyplní rect, interpoluje každý scanline od wire-RGB565 barvy top k bottom. Přidáno s fixed=True. Měň .top/.bottom v čase pro cyklus den-noc.
import picogame as pg
import picogame_fx as fx
sky = fx.Sky(scene, 0, 0, W, HORIZON,
pg.rgb565(60, 120, 240), pg.rgb565(200, 230, 255))

Záludnosti: kreslí fill_rect za každý scanline, takže drž výšku pásma rozumnou; je to levné, ale ne zadarmo u vysokého celoobrazovkového gradientu.

fx.Scanlines - CRT scanline overlay

Sekce “fx.Scanlines - CRT scanline overlay”

Ztmaví každý N-tý řádek pro CRT/LCD-grid vzhled. StripDraw, nulová RAM. Přidej ho jako poslední, ať leží navrchu.

  • Scanlines(scene, x, y, w, h, step=2, dark=pg.rgb565(0, 0, 0)) - step=2 ztmaví každý druhý řádek; dark je barva overlayu. Předpočítá 1px dither řádek a blituje ho jednou na každý ztmavený řádek (jeden blit místo smyčky na každý pixel).
import picogame_fx as fx
scanlines = fx.Scanlines(scene, 0, 0, W, H) # přidej JAKO POSLEDNÍ, navrch všeho

Záludnosti: záleží na pořadí - přidej ho až po všech herních vrstvách, jinak bude přemalováno.

fx.InvertFlash - hardwarový hit-flash zadarmo

Sekce “fx.InvertFlash - hardwarový hit-flash zadarmo”

Přepne celý panel na jeho negativ na pár framů pomocí hardwarové inverze barev řadiče (pg.invert). Žádný StripDraw, žádný buffer, žádný repaint - levnější než Fade. Potřebuje panel třídy ST7789/ST7735; na simulátoru je to tichý no-op.

  • InvertFlash(display, frames=3, normal=True) - display je např. board.DISPLAY; frames je délka záblesku. normal je klidový stav inverze panelu. PicoPad posílá INVON při inicializaci, takže jeho klidový stav je normal=True (výchozí); předej normal=False jen pro panel, jehož init neprovádí inverzi.
  • .pulse(frames=None) - teď přepni z klidového stavu (volitelně na vlastní počet framů).
  • .tick() - odpočítej a obnov klidový stav, jakmile záblesk skončí. Vrací True, dokud záblesk trvá. Volej ho po scene.refresh(), ať INVON/INVOFF je poslední bus operace framu.
import board
import picogame_fx as fx
flash = fx.InvertFlash(board.DISPLAY, frames=6)
# ...při zásahu:
flash.pulse()
# ...po scene.refresh():
flash.tick()

Záludnosti: nastav normal správně, jinak panel zůstane invertovaný - na PicoPadu nech výchozí normal=True. Efekt existuje jen na reálném hardwaru; viz /cs/hardware/, které řadiče podporují INVON/INVOFF.

Levné barevné efekty na PAL8 art mutací palety místo pixelů - klasický Game Boy trik. Sáhni po něm pro animovanou vodu/lávu (cycle), přebarvení sdílené bitmapy pro hráče 1/2 nebo normální/vystrašený stav (swap), nebo plynulé ztmavení/barevný přechod (fade) - vše za hrstku zápisů do pole a nulové extra art. Položky palety jsou wire-order RGB565 inty (to, co vrací pg.rgb565() a co nese pg.Bitmap jako svou array('H') paletu).

import picogame_palette as palette

  • snapshot(palette) - vrátí kopii palety jako array('H'). Ulož originál jednou, před jakýmkoliv fadingem nebo cyclingem, abys mohl fadovat relativně k němu nebo ho restore obnovit.
  • restore(palette, base) - zkopíruje base zpět do palette na místě.
  • cycle(palette, lo, hi, step=1) - rotuje položky [lo..hi] včetně o step (s přetečením), na místě bez alokace, takže je bezpečné volat každý frame. Rezervuj skupinu indexů pro tekoucí barvy, nakresli s nimi art a ty se samy animují.
  • swap(dst_palette, src_palette) - zkopíruje jednu paletu přes druhou (do délky kratší). GBC-style přebarvení: drž jednu PAL8 bitmapu a předávej jí různé palety pro každou variantu. Levnější než druhá bitmapa.
  • fade(palette, base, t, target=0, skip=None) - interpoluje každou položku palette od uložené base k wire barvě target podle t (0.0 = base .. 1.0 = target). target=0 (černá) způsobí fade out; bílý target zmizí do bílé. skip nechá jeden index beze změny (např. průhledný index).
import picogame_palette as palette
# ...každý frame, animuj rezervovaný pás vodních barev:
palette.cycle(water_bmp.palette, 1, 6)
water.touch() # řekni rendereru, že se paleta změnila

Záludnosti: dirty-rect renderer čte paletu při blitování, ale sám od sebe si změny palety nevšimne. Po každém cycle/swap/fade/restore zavolej sprite.touch() na spritech používajících tu bitmapu (nebo scene.invalidate() / přemaluj oblast tilemapu) - jinak se vizuálně nic nezmění. Cena je repaint dotčených spritů v daném framu, takže cyclinguj malý pás (pruh vody), ne celou obrazovku. Viz /cs/memory/, proč je to lepší než mít druhou přebarvenou bitmapu.