Pseudo-3D (Mode-7 a raycasting)
Dva způsoby, jak z 2D enginu dostat 3D vzhled, oba kreslené do bezbufferového StripDraw view, takže stojí nula retained RAM. picogame_mode7 dá ubíhající podlahu (trať závoďáku, létající koberec); picogame_ray dá stěny a chodby (dungeon, bludiště). Signatury najdeš v /cs/reference/.
| Co chceš | Sáhni po | Jak to kreslí |
|---|---|---|
| Podlahu ubíhající k horizontu | picogame_mode7.Camera (řídí C Canvas.mode7) | C primitivum, per-scanline - rychlé |
| Stěny / first-person chodbu | picogame_ray.Raycaster | čistý Python DDA - pomalejší, lad’te |
Obojí kreslí jen řádky pod horizontem a oblohu nechává na tobě; řádky nad horizontem si vyplníš sám (plná barva, gradient nebo fx.Sky).
picogame_mode7
Sekce “picogame_mode7”
Mode-7 perspektivní podlaha: každý řádek obrazovky pod horizontem navzorkuje jednu vzdálenost do textury, takže plochý top-down obrázek působí jako podlaha ubíhající k horizontu. Veškerá matematika je v tomto Python helperu; per-scanline vyplnění dělá C engine primitivum Canvas.mode7, takže je laciné.
import picogame_mode7 as m7
m7.Camera - perspektivní podlaha
Sekce “m7.Camera - perspektivní podlaha”Camera(fov=0.66)- drží zorné pole (vyšší = širší, víc rybí oko). Kameru vytvoř jednou a každý snímek jí předej pozici..draw(canvas, texture, x, y, angle, horizon, height, y_off=0)- vyplnícanvas(CanvasneboStripDrawview) podhorizonubíhajícím pohledem natexture.x/yje pozice kamery ve světových (dlaždicových) jednotkách,angleje směr v radiánech,horizonje řádek obrazovky s linií horizontu aheightje výška kamery (vyšší = podlaha ubíhá pomaleji, vidíš dál). VStripDrawcallbacku předejy_off=vy, aby perspektivní dělení použilo absolutní řádek obrazovky.
texture musí mít rozměry mocninu dvou a jedna světová jednotka = jedna celá dlaždice textury. Bezešvě dlaždicovatelná textura (tráva, silnice s rumble pruhy) může použít velký height a wrapovat donekonečna; jednorázový neopakující se obrázek (jeden uzavřený okruh) potřebuje malý height, jinak se vzdálenost začne opakovat.
import picogame as pgimport picogame_mode7 as m7
cam = m7.Camera(fov=0.9)
def ground(view, vx, vy, vw, vh): for r in range(vh): # oblohu nad horizontem si vyplň sám if vy + r < HORIZON: view.fill_rect(0, r, vw, 1, SKY) cam.draw(view, TRACK, car.x, car.y, car.heading, HORIZON, 5.0, y_off=vy)
scene.add(pg.StripDraw(ground, 0, 0, W, H)) # 0 retained bajtů# ...každý snímek: posuň car.x/y/heading, pak scene.refresh()picogame_ray
Sekce “picogame_ray”
Raycaster ve stylu Wolfensteinu: jeden DDA paprsek na sloupec obrazovky najde nejbližší stěnu a každý sloupec se vykreslí jako vzdáleností stínovaný svislý pruh. Čistý Python, žádný zásah do firmware - stěny jsou plné fill_rect sloupce do StripDraw view.
import picogame_ray
picogame_ray.Raycaster - first-person raycaster stěn
Sekce “picogame_ray.Raycaster - first-person raycaster stěn”Raycaster(world, wall_colors, sky, floor, fov=0.66, stride=2)-worldje seznam stejně dlouhých řetězců ('0'= prázdno,'1'..'9'= typy stěn);wall_colorsmapuje každý typ stěny na dvojici(barva_čela, barva_boku)(boční o kus tmavší = hloubkový cue zdarma);sky/floorjsou wire-RGB565 pozadí.stridevrhá jeden paprsek na N sloupců - knoflík výkon/kvalita (viz níže)..cast(px, py, ang, sw, sh)- vrhne paprsky pro pozici kamery(px, py)a směranga nakešuje rozsah stěny pro každý sloupec. Volej jednou za snímek předscene.refresh()..draw(view, vx, vy, vw, vh)-StripDrawcallback: vykreslí pásy oblohy/podlahy a sloupce stěn. Sousední shodné sloupce se sloučí do jednohofill_rect..solid(x, y)- je buňka mapy na celočíselných(x, y)stěna? Použij pro kolizi pohybu (mimo mapu = stěna)..zbuf- pocast()seznam vzdáleností stěny pro každý sloupec (hloubkový buffer, proti kterému testujeproject_sprite)..project_sprite(sx, sy, margin=0.2)- promítne světový bod(sx, sy)na obrazovku pro poslednícast(). Vrátí(screen_x, size, depth), neboNone, když je bod za kamerou nebo schovaný za bližší stěnou.sizeje výška na obrazovce v px ve škále stěn - nastavsprite.scale = size / výška_bitmapy; seřaď sprity podledepthod nejvzdálenějšího, aby ty bližší kreslily navrch.marginje vůle z-testu, aby sprite těsně u stěny nezmizel.
import picogame as pgimport picogame_ray
MAP = ["1111111111", "1000000001", "1011100201", "1000000001", "1111111111"]WALLS = {1: (pg.rgb565(150, 150, 160), pg.rgb565(95, 95, 110)), 2: (pg.rgb565(170, 90, 60), pg.rgb565(110, 55, 35))}
rc = picogame_ray.Raycaster(MAP, WALLS, pg.rgb565(30, 30, 48), pg.rgb565(40, 34, 30), stride=2)scene.add(pg.StripDraw(rc.draw, 0, 0, W, H))# ...každý snímek:rc.cast(px, py, ang, W, H) # nejdřív cast, pak vykresleníscene.refresh()# ...pohyb bez procházení stěnami:if not rc.solid(int(nx), int(py)): px = nxNepřátelé & pickupy - billboard sprity
Sekce “Nepřátelé & pickupy - billboard sprity”Raycaster kreslí jen stěny; nepřátelé a pickupy jsou běžné Sprite, které se každý snímek zvětší a umístí podle project_sprite a depth-testují proti stěnám, takže se schovají za roh. Přidej sprity do scény až za StripDraw, ať kreslí navrch, a místo vytváření každý snímek použij picogame_pool.Pool.
GUYS = [Enemy(3.5, 2.5), Enemy(6.5, 3.5)] # pozice ve světě (dlaždice)for g in GUYS: g.spr = pg.Sprite(DEMON_BMP, -40, -40) # DEMON_BMP je 8 px vysoký g.spr.anchor = (0.5, 0.5) scene.add(g.spr) # AŽ ZA StripDraw -> navrch stěn
def frame(): rc.cast(px, py, ang, W, H) # cast stěn (naplní rc.zbuf) GUYS.sort(key=lambda g: -((g.x - px) ** 2 + (g.y - py) ** 2)) # od nejvzdálenějšího for g in GUYS: p = rc.project_sprite(g.x, g.y) if p: sx, size, _ = p g.spr.move(sx, HORIZON) # na řádek horizontu g.spr.scale = size / 8.0 # bitmapa je 8 px vysoká g.spr.visible = True else: g.spr.visible = False # mimo obraz nebo za stěnou scene.refresh()