- Autor
- Petr Šrámek
- Stav
- Funkční
- Licence
- MIT
2D herní engine pro CircuitPython na mikrokontrolérech. Vykreslování, kolize a scény řeší rychlé jádro v C, herní logiku píšete v Pythonu. Bez kompilace a flashování: upravíte code.py a hned vidíte výsledek. Vyzkoušet ho můžete i bez hardwaru v prohlížeči.

Co projekt umí
- Sprity libovolné velikosti, tilemapy, kamera, kolize a částicové efekty.
- Knihovny pro vstupy včetně USB gamepadu a klávesnice, animace, dialogy, zvuk a syntézu, ukládání skóre.
- Desítky až stovky FPS: Bouncing Balls běží 250 FPS proti 115 FPS v knihovně Stage.
- Vrstva kompatibility se Stage, takže staré hry běží dál.
- Webový i desktopový simulátor, editor scén a převod grafiky z PNG.
Jak to funguje
Hlavní brzdou je překreslování displeje po SPI. Celá obrazovka 240 × 240 trvá zhruba 144 ms, tedy necelých 7 snímků za sekundu. Picogame proto drží objekty scény (retained mode) a na displej posílá jen změněné obdélníky. Kreslí v úzkých pruzích tak, aby procesor stihl spočítat další pruh, zatímco se předchozí odesílá.
Druhou brzdou je RAM. RP2040 má 264 kB a samotný framebuffer 320 × 240 by zabral přes 150 kB. Engine žádný plný framebuffer nedrží a velké bloky alokuje hned na začátku, aby paměť nefragmentovala. Matematika je ve fixed pointu, goniometrie z tabulek.
Referenční zařízení je PicoPad. Běží ale na téměř každé desce s CircuitPythonem, zhruba 200 kB RAM a displejem ST7789 nebo ILI9341: Pico 1 a 2, Feather S3 TFT, PicoSystem nebo Fruit Jam s HDMI výstupem.






Ukázka: první krok tutoriálu
# Bounce -- step 1: get ONE thing on screen.
#
# What you learn: the picogame render loop. A game is (a) a Scene you add objects
# to ONCE, then (b) a loop that moves things and calls scene.refresh(). The engine
# is retained-mode: you don't redraw by hand, you change object state and refresh.
#
# New in this step: picogame_game.setup(), picogame_shapes.rect(), pg.Sprite,
# scene.add(), scene.refresh(), the frame clock.
#
# Run it: python3 sim/run.py tutorials/01-bounce/step1_hello.py --shot /tmp/s1.png
# On device: copy this file + the lib/ helpers to CIRCUITPY.
import picogame as pg
import picogame_game
import picogame_clock
import picogame_shapes as shp
W, H = picogame_game.screen() # never hardcode: the same game runs at 240x240 and 640x480
PADDLE_W, PADDLE_H = 44, 8
# setup() takes over the display and gives us a Scene + its two strip buffers.
scene, _, _ = picogame_game.setup(background=pg.rgb565(8, 10, 24))
clock = picogame_clock.Clock(40) # cap the loop to 40 FPS
# A "paddle" is just a Sprite whose bitmap is a solid rectangle. shp.rect(w,h,color)
# makes that bitmap -- a rectangle and an image sprite are the SAME kind of object
# (we'll prove that in step 9 by swapping the bitmap for art, with no other change).
paddle = pg.Sprite(shp.rect(PADDLE_W, PADDLE_H, pg.rgb565(220, 220, 230)),
(W - PADDLE_W) // 2, H - 16)
scene.add(paddle) # add it to the scene ONCE
while True:
scene.refresh() # the engine draws the scene
clock.tick() # sleep to the next frame
# Bounce -- step 3: a ball with velocity (whole-pixel movement).
#
# What you learn: velocity. Velocity is just how many pixels a thing moves each
# frame: velocity_x across, velocity_y down. Add the velocity to the ball's
# position every frame and it travels in a straight line. Here we move in WHOLE
# pixels -- integer velocity, integer position -- which is all this step needs.
#
# New vs step 2: a velocity (velocity_x, velocity_y) added to ball.x / ball.y each
# frame. The ball flies off-screen for now -- step 4 makes it bounce.
#
# Run: python3 sim/run.py tutorials/01-bounce/step3_ball.py --shot /tmp/s3.png
import picogame as pg
import picogame_game
import picogame_input
import picogame_clock
import picogame_shapes as shp
W, H = picogame_game.screen() # never hardcode: the same game runs at 240x240 and 640x480
PADDLE_W, PADDLE_H = 44, 8
BALL = 6
scene, _, _ = picogame_game.setup(background=pg.rgb565(8, 10, 24))
btn = picogame_input.Buttons()
clock = picogame_clock.Clock(40)
paddle = pg.Sprite(shp.rect(PADDLE_W, PADDLE_H, pg.rgb565(220, 220, 230)),
(W - PADDLE_W) // 2, H - 16)
ball = pg.Sprite(shp.rect(BALL, BALL, pg.rgb565(255, 240, 120)), W // 2, H // 2)
scene.add(paddle)
scene.add(ball)
velocity_x, velocity_y = 3, -3 # NEW: whole pixels moved per frame
while True:
btn.poll()
delta_x = btn.is_pressed(btn.RIGHT) - btn.is_pressed(btn.LEFT)
if delta_x:
paddle.move(max(0, min(W - PADDLE_W, paddle.x + delta_x * 5)), paddle.y)
# move the ball by its velocity (whole pixels)
ball.move(ball.x + velocity_x, ball.y + velocity_y)
scene.refresh()
clock.tick()




