Manic Miner 是 1983 年 ZX Spectrum 平台上的标志性游戏,仅用 16KB ROM 实现了完整的关卡冒险,包括 20 个洞穴(cavern)、主角 Miner Willy 的跳跃物理、多种敌人(guardians)、物品收集与空气倒计时。其 Z80 汇编代码高度优化,SkoolKit 工具生成的完整 RAM 反汇编(https://skoolkit.ca/disassemblies/manic_miner/)提供了带注释的 HTML 视图,routines 索引(maps/routines.html)列出所有关键入口点,便于逆向工程。该资源揭示了复古游戏引擎的核心逻辑:属性缓冲(attribute buffer)驱动渲染、IX/IY 指针遍历数据表、位操作实现动画与碰撞。
房间加载机制(Routine at 35445)
房间加载的核心是 “Draw the current cavern to the screen buffer at 28672”(35445)。ZX Spectrum 屏幕分为像素缓冲(0x5C00-0x5FFF,实际工作缓冲 28672=0x7000?)与属性缓冲(0x5800-0x5BFF,工作 24064=0x5E00)。流程:
- IX 指向属性缓冲首(24064),循环 256 字节(上半屏),每字节标识图块类型(匹配 32800 开始的背景 / 地板 / 墙 / 传送带 / 致命砖)。
- 使用 CPIR 在 32800+72 字节表中搜索匹配属性,HL 定位图块 8 字节图形数据。
- DE 计算目标地址(上半 D=112=0x70,下半 D=120=0x78),LDIR 复制图形到 28672。
- 当前洞穴号存 33799,最后一关(19)额外复制 40960 图形。
参数建议:图块表每类型 9 字节(1 attr +8 graphic),渲染阈值 256 行 ×32 列。现代复现可使用 attr-first 渲染加速碰撞查询,避免全像素检查。风险:缓冲溢出,需验证 IX 增量(INC IX)。
Willy 移动与物理(Routines at 35515 & 35805)
主角移动分两阶段,主循环(34574)调用 35515 处理空中(jump/fall),35805 处理地面。空中状态(32875):0 = 地面,1 = 跳跃,2 = 开始下落,6 = 持续下落。
- 跳跃:计数器 32878(0-17,偶数步进)。Y 坐标(32872)= base + (jump-8)*scale,上行负,下行正。弧线由预计算表隐含(SUB 8, NEG if <8)。
- 重力 / 着陆:每帧 Y+=4px(ADD 8, AND 240 量化到 char 行)。检查脚下 2 格(attr buf +64):碎地板(32818)调用 35770 动画,致命砖(32845/32854)触发死亡。
- 墙壁碰撞:调整 attr 位置(32876,计算 H=92/93 + L=16*Y + X),比对墙 attr(32827)。若撞,Y+=16 滑下,重置左右移动位(32874 bit1)。
- 声音:OUT (254,A) 边框色切换,循环 B=D=pitch*8, C=32 持续。跳升高音,下落低音。
落地参数:空中阈值 6 帧后强制下落,Y &15==0 时检查 6 格高 sprite。键盘:主循环读行扫描(未详),速度 1-2px/frame。启发:状态机 + 缓冲检查高效,适用于低端硬件。
敌人行为(Horizontal Guardians at 36111)
守护者分水平(36111)、垂直(36593)、特殊(光束 36211、Eugene 36344 等)。水平敌数据 32958 开始,每 7 字节:byte0 = 动画速度 (bit7 = 慢,bit2 游戏时钟同步),byte1 = 当前 attr LSB (X),byte4 = 帧 (0-3 右行,4-7 左行),byte5/6 = 路径 min/max X。
- 循环 IY +=7,至 255 结束。
- 帧 ==3:X==max? 转左 (帧 = 7) : 帧 = 0, X++。
- 帧 ==4:X==min? 转右 (帧 = 0) : 帧 = 7, X--。
- 绘制 36266 前更新,精灵表 IX/IY。
垂直类似 Y 范围。碰撞:移动后检查 Willy attr 与守护重叠(Kill Willy 36101)。参数:路径宽 4-32 格,速度 1px/2-4 帧。AI 简单反弹,现代可扩展路径表。
碰撞检测与死亡
无像素精碰撞,全 attr 基:Willy attr(32876)比对敌 attr 或房间致命 / 碎 attr。守护绘制后若重叠,36101 启动死亡动画(缩放 sprite + 音效)。物品 36707 同理收集。传送门 36805 进入下一房间(36904 递增 33799,重新加载)。
阈值:重叠 1 格即死,回滚 Y/X 到上帧。空气 35388 递减,超时游戏结束。
声音系统(37596 等)
蜂鸣器单声道:OUT (254, color) 切换边框,产生方波。主题曲(Blue Danube)预存音表,循环播放音阶(pitch = 循环长,duration=C * 帧)。跳 / 落 / 死用动态 pitch(jump pos *8)。
参数:音阶 D=1+abs (jump-8)<<3,持续 32 帧。无中断,阻塞式。
工程启发与复现清单
Manic Miner 展示了 Z80 紧凑设计:72 字节图块表、7 字节敌表、状态字节驱动逻辑。复现清单:
- 内存:屏幕 28672 (pix), 24064 (attr), Willy 32872 (Y)/32876 (attr pos)/32875 (status)。
- 循环:主 34574,每帧 air--, move Willy, enemies, draw all。
- 监控:游戏钟 32957 bit2 限帧率,attr 匹配加速。
- 回滚:死亡前存 snapshot,超时 256 空气。
引用自 SkoolKit 注释:“Draw the tiles for the top half of the cavern to the screen buffer at 28672。”[1] 此逆向适用于现代 demake 或引擎研究,如 Godot tilemap + state machine。
资料来源: [1] https://skoolkit.ca/disassemblies/manic_miner/asm/35445.html [2] https://skoolkit.ca/disassemblies/manic_miner/maps/routines.html
(正文约 1250 字)