Přeskočit na obsah

Úryvky kódu — vzory k úpravě

Tohle jsou útržky hotové k vložení — kód pro konkrétní úlohu. Proč a vyšší strukturu za nimi najdeš v Herních vzorech. Kostra níže je celá hra v malém; ukázky za ní doplňují jednotlivé části. Vyber potřebný kus, uprav názvy a přizpůsob limity své hře. Kód používá stejné zvyklosti jako přibalené příklady.

Celý tvar: hotová kostra ke spuštění

Sekce “Celý tvar: hotová kostra ke spuštění”

Začni tady. Všechen měnitelný stav hry žije v jednom State objektu (st) a smyčka každého snímku žije ve funkci main() — uvnitř funkce se její jména vyhledávají jako rychlé lokály místo vyhledávání v globálním slovníku, což je na zařízení měřitelný zisk. Každá ukázka níže zapadá do tohoto tvaru. Zkopíruj ji, přejmenuj pole State a doplň větev play.

# Ready-to-use picogame skeleton: a TITLE -> PLAY -> GAME-OVER state machine, with ALL game state
# in one State object and the per-frame loop in a function (main). Copy it, rename the State fields,
# and fill in the PLAY branch. Runs as-is here in the browser and on a device.
import board
import terminalio
import picogame as pg
import picogame_game
import picogame_input
import picogame_clock
import picogame_shapes as shapes
import picogame_ui as ui
W, H = board.DISPLAY.width, board.DISPLAY.height # size-independent: never hardcode 320x240
BAR = 16 # reserved top HUD strip
scene, bufA, _ = picogame_game.setup(background=pg.rgb565(16, 20, 34), top=BAR)
btn = picogame_input.Buttons()
clock = picogame_clock.Clock(30)
hud = ui.HudBar(pg, board.DISPLAY, bufA, 0, 0, W, BAR, pg.rgb565(10, 12, 22))
label = hud.label(terminalio.FONT, 4, 3, pg.rgb565(235, 240, 255), "")
# The player: a generated shape now; swap in a Bitmap (png2picogame) later.
player = pg.Sprite(shapes.circle(14, pg.rgb565(240, 90, 90)), W // 2, H // 2)
player.anchor = (0.5, 0.5)
scene.add(player)
class State:
"""ALL mutable game state in ONE object, created once and never reassigned. reset() re-inits it
in place, so main() can hold `st` and every helper sees the same state. Add your fields here;
keep engine objects (sprites, pools, clock) as module globals, not in State."""
def __init__(self):
self.reset()
def reset(self):
self.mode = "title" # "title" -> "play" -> "over"
self.score = 0
st = State()
def show(text): # set the HUD text and repaint the bar
label.set(text)
hud.draw()
def new_game():
st.reset()
st.mode = "play"
player.move(W // 2, H // 2)
show("SCORE 0")
def main():
# The per-frame loop lives in a FUNCTION, not at module scope: here its names resolve as fast
# array-indexed locals instead of globals-dict lookups -- a measured win on device.
show("PRESS A TO START")
while True:
btn.poll()
if st.mode == "play":
# --- your game goes here: move, collide, score ---
player.x += (btn.is_pressed(btn.RIGHT) - btn.is_pressed(btn.LEFT)) * 3
if btn.just_pressed(btn.A):
st.score += 1
show("SCORE %d" % st.score) # set text where it CHANGES, not every frame
if btn.just_pressed(btn.B):
st.mode = "over"
show("GAME OVER - press A")
elif btn.just_pressed(btn.A): # title or game-over -> (re)start, no reload
new_game()
scene.refresh()
clock.tick()
main()
▶ Try it in the browser

Stránka Herní vzory vysvětluje ten tvar; ukázky níže jsou jednotlivé tahy.

Rozměry displeje načti místo pevného zadání

Sekce “Rozměry displeje načti místo pevného zadání”

Pokud se má rozvržení přizpůsobit různým podporovaným displejům, například 320×240 a 240×240, odvozuj pozice z W a H.

W, H = board.DISPLAY.width, board.DISPLAY.height
player = pg.Sprite(bm, W // 2, H - 40)

Text HUDu formátuj jen při změně

Sekce “Text HUDu formátuj jen při změně”

label.set() přeskočí překreslení nezměněného textu, ale samotný "%"-formát alokuje nový řetězec při každém volání. Ve snímkové smyčce by tak zbytečně vznikal odpad pro garbage collector. Nejdřív porovnej čísla a text formátuj jen při změně zobrazené hodnoty:

hud = picogame_font.Label(...) # nebo picogame_ui.HudBar; vytvořeno JEDNOU při načtení
if score != shown_score: # poslední hodnota zobrazená v popisku
shown_score = score
hud.set("SCORE %d" % score)

Všechny možnosti HUDu a popisků: Text a UI.

Pokud to jde, nastav popisek přímo v místě, kde se hodnota mění, například při zásahu nebo přechodu do další úrovně. Plynulé hodnoty nejdřív převeď na zobrazovanou jednotku:

secs = int(time.monotonic() - t0) # celé sekundy = co label zobrazuje
if secs != shown_secs:
shown_secs = secs
timer_lbl.set("TIME %d" % secs)

Střely a další krátkodobé objekty ber z fondu

Sekce “Střely a další krátkodobé objekty ber z fondu”

U často vznikajících objektů, jako jsou střely, nevytvářej při každém použití nový pg.Sprite(...). Pool je připraví předem; spawn() a free() pak přepínají viditelnost:

bullets = Pool(scene, bullet_bm, 8, anchor=(0.5, 0.5)) # JEDNA alokace, při načtení
b = bullets.spawn() # None, když je všech osm střel aktivních
if b:
b.move(ship.x + 14, ship.y)
for b in bullets.items: # pevný seznam -> smyčka nic nealokuje
if not b.visible:
continue
b.x += 6
if b.x > W + 8:
bullets.free(b) # zpátky do poolu k znovupoužití

HUD ikony s pevným počtem (životy, munice) Pool nepotřebují — obyčejný seznam spritů s přepínáním visible stačí.

Restart jedním reset() a jedním nikdy nepřepisovaným st

Sekce “Restart jedním reset() a jedním nikdy nepřepisovaným st”

Hodnoty jednoho herního pokusu drž v třídě State a při restartu je re-inicializuj na místě metodou reset(). st vznikne jednou a nikdy se znovu nepřiřadí — nikdy nepřepisovaný singleton — takže main() (i libovolný pomocník) ho může držet jako rychlý lokál bez rizika, že po restartu ukazuje na zastaralou kopii. Objekty enginu a hodnoty společné pro všechny pokusy (nejlepší skóre) zůstávají na úrovni modulu.

class State:
def __init__(self): self.reset() # __init__ jen zavolá reset -> jedno místo pro defaulty
def reset(self):
self.score = 0
self.lives = 3
self.px = 60.0
st = State() # vytvořeno JEDNOU, při načtení
def new_game():
st.reset() # každé pole zpět na dokumentovaný default, NA MÍSTĚ
bullets.free_all()
player.move(60, GROUND)

Pauza nebo menu nad živou scénou

Sekce “Pauza nebo menu nad živou scénou”

pg.render vykresluje mimo stav uložený scénou. Bez invalidate() proto může další refresh() nechat na obrazovce část překryvu. overlay() vykreslí překryv a zároveň označí scénu k obnovení. Překryv vykresli jednou a během pauzy scénu neobnovuj:

if btn.just_pressed(btn.START):
paused = not paused
if paused:
picogame_game.overlay(scene, board.DISPLAY, [pause_sd], bufA, 0, 0, W, H)
if paused:
clock.tick() # žádné update, žádný refresh -> overlay drží
continue
scene.refresh() # první refresh po odpauzování překreslí vše

Míchání: na zařízení není random.shuffle

Sekce “Míchání: na zařízení není random.shuffle”

random v CircuitPythonu nemá shuffle()/sample() (desktopový simulátor je má — běží na CPythonu). Fisher-Yates přes randint je tentýž algoritmus:

for i in range(len(deck) - 1, 0, -1):
j = random.randint(0, i)
deck[i], deck[j] = deck[j], deck[i]

Záblesk po zásahu na 1–3 snímky

Sekce “Záblesk po zásahu na 1–3 snímky”

Krátký záblesk zvýrazní okamžik zásahu. Nastav flash při kolizi a ve smyčce odpočítej zbývající snímky:

enemy.flash = WHITE # při zásahu
st.flash_t = 3
if st.flash_t > 0: # v herní smyčce
st.flash_t -= 1
if st.flash_t == 0:
enemy.flash = None

(Na zařízení sdílí blit efekty flash/tint/dither/shadow jeden slot — vyhrává poslední nastavený; simulátor je považuje za nezávislé, takže tohle nereprodukuje. Trvalý dither po konci flashe nastav znovu. Širší sada nástrojů: Efekty.)

Pípnutí při události

Sekce “Pípnutí při události”

K záblesku přidej zvuk. Tón sestav jednou při načtení a pak ho spusť při události. Vše schovej za if audio:, aby deska bez zvuku (nebo selhaná inicializace) prostě běžela potichu:

audio = None
try:
import picogame_audio
audio = picogame_audio.Audio() # PWM výstup + mixer; na deskách bez zvuku může selhat
boom = picogame_audio.tone(140, 200) # nízký úder, sestaven JEDNOU (RawSample v RAM)
except Exception: # málo paměti, žádný audio pin, ...
audio = None # -> běž potichu
if audio: # při zásahu / sebrání:
audio.sfx(boom) # bez čekání, na volném hlasu

Pípnutí z tone() jako tohle ve webovém playgroundu hraje (WebAudio). Kde který zvukový kanál zazní (playground / simulátor / zařízení) a hotovou sadu pojmenovaných efektů (hit(), coin(), boom()) najdeš v Audiu.

Tlačítka: jeden virtuální ovladač, mapovaný v settings.toml

Sekce “Tlačítka: jeden virtuální ovladač, mapovaný v settings.toml”

Programuj proti logickým tlačítkům enginu (UP DOWN LEFT RIGHT A B, volitelně X Y L1 L2 R1 R2 START SELECT) — nikdy proti pinům. Stejné code.py pak běží na jakékoli desce; mění se jen mapování v settings.toml (bez reflashe):

btn = picogame_input.Buttons() # čte GPIO + klávesovou matici + USB, vše OR
btn.poll() # jednou za snímek, před dotazy
if btn.is_pressed(btn.LEFT): px -= 2
if btn.just_pressed(btn.A): jump()
if btn.has(btn.X): show_x_hint() # jen když deska X namapovala
# přímá GPIO tlačítka (Pico, které sis sám zapojil)
PICOGAME_BUTTONS = "UP=GP2 DOWN=GP3 LEFT=GP4 RIGHT=GP5 A=GP6 B=GP7"
# …nebo skenovaná klávesová matice — NÁZEV=řádek,sloupec
PICOGAME_MATRIX_ROWS = "GP0 GP1 GP2 GP3"
PICOGAME_MATRIX_COLS = "GP4 GP5 GP6 GP7"
PICOGAME_MATRIX_MAP = "UP=0,1 DOWN=2,1 LEFT=1,0 RIGHT=1,2 A=3,3 B=3,2"

USB gamepad/klávesnice (desky s USB hostem) se připojí samy. Kompletní průvodce + USB remap klíče: Vstup a ovládání.

Tolerantnější skoky: čas po opuštění hrany a předčasný stisk

Sekce “Tolerantnější skoky: čas po opuštění hrany a předčasný stisk”

Obě časová okna může hlídat picogame_input.Timer. Hráč pak může skočit krátce po opuštění hrany nebo stisknout tlačítko těsně před dopadem:

coyote = picogame_input.Timer(6) # skok až 6 snímků po opuštění hrany
jbuf = picogame_input.Timer(5) # přijmi stisk až 5 snímků před dopadem
coyote.feed(on_ground) # v herní smyčce
jbuf.feed(btn.just_pressed(btn.A))
if jbuf.consume() and coyote.is_active:
jump()

Panel, který se překreslí jen při změně

Sekce “Panel, který se překreslí jen při změně”

StripDraw s always_dirty=False neuchovává pixelovou plochu panelu. Zůstane nečinný, dokud nezavoláš invalidate():

panel = pg.StripDraw(draw_panel, x, y, w, h, always_dirty=False)
scene.add(panel, fixed=True)
lines[1] = "HP %d" % hp # když se obsah změní:
panel.invalidate() # překreslí se jednou při dalším refreshi

Pohyb s desetinnou přesností bez vlastní pozice

Sekce “Pohyb s desetinnou přesností bez vlastní pozice”

Souřadnice x a y spritu jsou celá čísla. Pro plynulý pomalý pohyb použij vlastnosti fx a fy s pevnou řádovou čárkou (24.8), místo abys vedl vlastní desetinnou pozici:

ball.fx = ball.fx + 0.4 # zlomkový posun bez poskakování mezi pixely
ball.fy = ball.fy + vy * dt