Přeskočit na obsah

Jen C engine (bez pomocných knihoven)

Python knihovny picogame_* jsou ergonomie. Samotný engine - import picogame - je nativní C modul, který už dělá všechnu těžkou práci: rendering, kolize, transformace, šum, raycasting, polygonové 3D, DVI framebuffer. Postavíš na něm skutečnou hru jen s C modulem a stock CircuitPythonem. Tahle stránka ukazuje jak, a co ke všemu přidává každý helper.

Co je v C a co přidávají helpery

Sekce “Co je v C a co přidávají helpery”

C modul (import picogame as pg) exportuje engine:

  • Typy: Scene, Sprite, Bitmap, Tilemap, Canvas, StripDraw, Particles, Triangles, Display, Framebuffer.
  • Funkce: render, collide, raycast, project, road_edges, value2d / fbm2d šum, rgb565, ROMFS streaming. Další dvě existují jen ve forkových buildech: vblank (DVI na RP2350) a refresh_async (za opt-in make flagem) — pokud je použiješ, ošetři je přes hasattr.

To je všechno výpočetně náročné a distinktivní - sprity s runtime scale/rotací a blit efekty, dirty-rect kompozice, 0-RAM tilemapy a stripy, nativní text, kolize, procedurální šum, raycaster, Mode-7, flat-shaded 3D, DVI výstup. Helper knihovny z toho nedrží nic:

Helper Co přidává Ručně přes…
picogame_game boot wiring (Scene + Display + strip buffery) a cross-platform kvirky (Fruit Jam DVI, RGB444, simulátor) jeden pg.Scene(pg.Display(board.DISPLAY), …)
picogame_input tlačítka → bitmaska, nad keypad/digitalio, s per-board profily pinů stock keypad / digitalio
picogame_clock dt / fixed-step časování stock time.monotonic()
picogame_ui / fx / anim / pool / rand HUD widgety, screen-shake/tween, časování animací, object pooly, seeded RNG čistý Python
picogame_audio / synth / sfx / save / music zvuk a ukládání stock audiocore / synthio / nvm

Dvě věci stojí za jasné vyřčení: input, audio ani save nejsou v enginu vůbec - to je stock CircuitPython. Engine vždycky dostane jen výsledek: pozici spritu, naplněný buffer. Input je obzvlášť úplně oddělená vrstva - přečteš tlačítka, v Pythonu rozhodneš, že se něco posune o N pixelů, a jen nastavíš sprite.x / sprite.y. Engine tlačítko nikdy nevidí.

Celá hra jen s C modulem

Sekce “Celá hra jen s C modulem”

Jediný helper import, picogame_game, a jen kvůli displeji a jeho rozměru — všechno, co níž kreslí, hýbe nebo detekuje kolize, je C modul plus běžné board, time, array, terminalio a digitalio. Hráč se hýbe D-padem, sbírá minci přes nativní kolizi, při sebrání blikne, a kreslí se HUD s nativním textem.

Engine-only hra: hráč, mince a HUD s nativním textem - bez pomocných knihoven

# Celá malá hra JEN na C modulu picogame.
import board, time, array
import terminalio, digitalio
import picogame as pg
import picogame_game # jen kvůli display() / screen(); nic dalšího ho nepotřebuje
W, H = picogame_game.screen()
# Jediný setup enginu: Scene na displeji + dva strip buffery, přes které renderuje.
# picogame_game.display() napřímo je portable všude - SPI panely, framebuffery i playground.
# (Obalení do pg.Display(...) přidá rychlou DMA cestu na SPI deskách; to i buffery
# vyřeší za tebe picogame_game.setup() podle desky.)
SH = getattr(pg, "STRIP_H", 8)
scene = pg.Scene(picogame_game.display(), bytearray(W * SH * 2), bytearray(W * SH * 2),
background=pg.rgb565(12, 14, 34))
# Bitmapa spritu vyrobená ručně (PAL8, 1 bajt/px).
def square(color, size=6):
data = bytearray(b"\x01" * (size * size))
pal = array.array("H", [0, color])
return pg.Bitmap(data, size, size, format=pg.PAL8, palette=pal, frames=1, stride=size, transparent=0)
player = pg.Sprite(square(pg.rgb565(90, 230, 130)), W // 2, H // 2)
player.anchor = (0.5, 0.5); player.scale = 4.0; scene.add(player)
coin = pg.Sprite(square(pg.rgb565(250, 210, 70)), 40, 60)
coin.anchor = (0.5, 0.5); coin.scale = 4.0; scene.add(coin)
# Input: stock digitalio na tlačítkách desky - syrové zapojení, které picogame_input skrývá.
def button(name):
io = digitalio.DigitalInOut(getattr(board, name))
io.switch_to_input(pull=digitalio.Pull.UP) # tlačítka PicoPadu jsou aktivně-nízká
return io
LEFT, RIGHT, UP, DOWN = (button(n) for n in ("SW_LEFT", "SW_RIGHT", "SW_UP", "SW_DOWN"))
def held(b): return not b.value # aktivně-nízké: stisk -> False
# HUD: nativní Canvas.text do 0-RAM StripDraw. Callback běží jednou na render-strip,
# takže kresli na (y - vy) pro obrazovkový prostor; band >= 12px výška fontu.
score = 0
BAND = terminalio.FONT.get_bounding_box()[1] + 4 # 12 + 4 = 16
def hud(view, vx, vy, vw, vh):
view.text(3, 2 - vy, "SCORE %d" % score, pg.rgb565(255, 230, 90), terminalio.FONT)
scene.add(pg.StripDraw(hud, 0, 0, W, BAND))
while True:
if held(LEFT): player.x -= 3
if held(RIGHT): player.x += 3
if held(UP): player.y -= 3
if held(DOWN): player.y += 3
player.x = min(max(player.x, 6), W - 6) # drž na obrazovce
player.y = min(max(player.y, BAND + 6), H - 6)
if player.overlaps(coin): # nativní C kolize
score += 1
coin.x = 30 + (score * 47) % (W - 60)
coin.y = BAND + 12 + (score * 31) % (H - BAND - 30)
player.flash = pg.rgb565(255, 255, 255) # nativní blit efekt
else:
player.flash = 0
scene.refresh() # nativní dirty-rect kompozitor
time.sleep(0.01)

▶ Vyzkoušej v prohlížeči

Scene + Sprite + overlaps + flash + Canvas.text + refresh je celé C engine; board, digitalio, terminalio, time je stock CircuitPython. Nic víc.

Kreslení přímo na DVI obrazovku

Sekce “Kreslení přímo na DVI obrazovku”

Na DVI desce (Fruit Jam) může Canvas kreslit přímo do picodvi.Framebuffer, který obrazovka zobrazuje — bez Scene, bez kopírování a bez volání refresh:

import board, displayio, picodvi, terminalio
import picogame as pg
displayio.release_displays()
fb = picodvi.Framebuffer(320, 240, color_depth=16,
clk_dp=board.CKP, clk_dn=board.CKN,
red_dp=board.D0P, red_dn=board.D0N,
green_dp=board.D1P, green_dn=board.D1N,
blue_dp=board.D2P, blue_dn=board.D2N)
# Plátno kreslí přímo do paměti, kterou DVI výstup zobrazuje.
screen = pg.Canvas(320, 240, buffer=fb)
def native(r, g, b):
# picodvi čte RGB565 v nativním pořadí bajtů; pg.rgb565() dává pořadí pro SPI
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
screen.clear(native(0, 0, 60))
screen.fill_rect(10, 10, 100, 60, native(255, 0, 0))
screen.fill_circle(200, 60, 40, native(0, 255, 0))
screen.fill_triangle(40, 220, 140, 120, 240, 220, native(255, 255, 0))
screen.text(20, 90, "Hello, DVI", native(255, 255, 255), terminalio.FONT)
pixels = memoryview(fb) # tytéž pixely, jedna položka na pixel
pixels[120 * 320 + 160] = native(255, 255, 255)

Primitiva a text z Canvasu nakreslené přímo do picodvi framebufferu na Fruit Jamu

Dvě věci, které je dobré vědět:

  • Barvy. Každá barva v picogame včetně pg.rgb565() je v pořadí bajtů, které chce SPI panel. picodvi čte běžné RGB565, takže pg.rgb565(255, 0, 0) tady vyjde modře — použij malý helper native() jako výše.
  • Jen tvary a text. Palety Bitmap jsou také v pořadí pro SPI, takže blit do tohoto plátna vyjde s prohozenými barvami. Pro sprity použij Scene s pg.Framebuffer(fb, 320, 240, native_rgb565=True) (to dělá picogame_game.setup() na Fruit Jamu), který převádí jednou za snímek. Plátno je RGB565, takže je potřeba color_depth=16.

Ty fiddly části kódu výše jsou přesně to, co helper knihovny odeberou: umístění HUD textu přes render-stripy (picogame_ui.HudBar), zapojení každého tlačítka podle desky (picogame_input.Buttons), časování nezávislé na frameratu (picogame_clock.Clock) a výběr správných bufferů a backendu pro každou platformu - SPI panel vs framebuffer, rychlé DMA kde je (picogame_game.setup()). Nic z toho nemění, co engine umí; jen to maže boilerplate.

Kdy sáhnout po enginu přímo

Sekce “Kdy sáhnout po enginu přímo”

Po syrovém C API sáhni, když chceš co nejmenší footprint, plnou kontrolu nad smyčkou, pochopit, co knihovny dělají, nebo si nad tím postavit vlastní framework. Pro všechno ostatní jsou helpery čistě pohodlí - ušetří boilerplate a zahladí rozdíly mezi deskami, ale hra, kterou postavíš, je stejně schopná tak i tak.