8792594c5a
Всё сразу: .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>
99 lines
3.2 KiB
Python
99 lines
3.2 KiB
Python
#!/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)])
|