examples/space: демо полного спрайтового стека (атлас W0 + авто-анимации)

Всё сразу: .atl с диска (mkatlas.py из генерённых лент) → atlas_load в
EMM-страницу (W0, ISR-стаб) → движок sprite_t.page → авто-анимации:
5 астероидов (ANIM_LOOP вращение + sprite_moveto к случайным целям
разных скоростей; по прибытии — взрыв ANIM_ONCE в точке + новая цель,
по SPR_ANIM_DONE взрыв прячется), маяк ANIM_PINGPONG; дабл-буфер,
FPS-метр, ESC.  ~48 fps (vsync-кап).

Грабли по дороге (оба — прикладные, не библиотека):
- фон рисовался rand()'ом с разными последовательностями на страницах
  → мерцание звёзд; фикс — фиксированный seed на draw_space;
- НЕ была скопирована палитра страницы 0 → 1 (у каждой страницы своя,
  см. gfx.h) — страница 1 показывалась чёрной, флип мигал
  «сцена/чёрный»; fps-плашка теперь рисуется на ОБЕИХ страницах
  (fps_draw=2 кадра при смене секунды).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 16:40:48 +03:00
parent eb9ca0179d
commit 8792594c5a
5 changed files with 309 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""Генератор спрайтов для examples/space (raw-ленты для mkatlas.py).
asteroid.raw — 16×16×4 (LOOP): серый астероид, кратеры вращаются по
фазам (шаг 22.5°).
explode.raw — 16×16×4 (ONCE): расширяющийся взрыв (жёлтое ядро →
кольца → разлёт искр).
beacon.raw — 16×16×4 (PINGPONG): пульсирующий маяк (крест с
растущим свечением).
Палитра — дефолтная EGA (индексы 0..15), 0xFF = прозрачно.
"""
import math
T = 0xFF # прозрачный
W = H = 16
CX = CY = 7.5
def blank():
return [[T] * W for _ in range(H)]
def disc(px, cx, cy, r, c):
for y in range(H):
for x in range(W):
if (x - cx) ** 2 + (y - cy) ** 2 <= r * r:
px[y][x] = c
def ring(px, cx, cy, r, c):
for a in range(0, 360, 4):
x = int(round(cx + r * math.cos(math.radians(a))))
y = int(round(cy + r * math.sin(math.radians(a))))
if 0 <= x < W and 0 <= y < H:
px[y][x] = c
def dot(px, x, y, c):
if 0 <= x < W and 0 <= y < H:
px[int(y)][int(x)] = c
def asteroid(phase):
px = blank()
disc(px, CX, CY, 6.8, 7) # LIGHTGRAY тело
ring(px, CX, CY, 6.8, 8) # DARKGRAY контур
for i in range(3): # кратеры на орбите
a = math.radians(phase * 22.5 + i * 120)
disc(px, CX + 3.6 * math.cos(a), CY + 3.6 * math.sin(a), 1.4, 8)
dot(px, CX - 2, CY - 3, 15) # блик WHITE
return px
def explode(phase):
px = blank()
if phase == 0:
disc(px, CX, CY, 2.2, 14) # YELLOW ядро
elif phase == 1:
disc(px, CX, CY, 3.8, 14)
ring(px, CX, CY, 4.6, 12) # LIGHTRED
elif phase == 2:
ring(px, CX, CY, 4.2, 14)
ring(px, CX, CY, 6.0, 12)
ring(px, CX, CY, 7.0, 4) # RED
else:
for a in range(0, 360, 30): # разлёт искр
r = 6.5
dot(px, CX + r * math.cos(math.radians(a)),
CY + r * math.sin(math.radians(a)), 4)
dot(px, CX + 4 * math.cos(math.radians(a + 15)),
CY + 4 * math.sin(math.radians(a + 15)), 6) # BROWN
return px
def beacon(phase):
px = blank()
r = 2 + phase * 1.6
for d in range(int(r) + 1): # крест-лучи
for xx, yy in ((CX - d, CY), (CX + d, CY), (CX, CY - d), (CX, CY + d)):
dot(px, round(xx), round(yy), 11) # LIGHTCYAN
disc(px, CX, CY, 1.4, 15) # WHITE ядро
if phase >= 2:
ring(px, CX, CY, r + 1, 9) # LIGHTBLUE ореол
return px
def save(name, frames):
with open(name, "wb") as f:
for fr in frames:
for row in fr:
f.write(bytes(row))
print(f"gen_sprites: {name} ({len(frames)} кадров)")
save("asteroid.raw", [asteroid(k) for k in range(4)])
save("explode.raw", [explode(k) for k in range(4)])
save("beacon.raw", [beacon(k) for k in range(4)])