为 ZX Spectrum 编写 Demoscene 破解开场动画
摘要
Mark Round 详细撰写了他为原版 128K ZX Spectrum 手工编写 demoscene crack intro 的技术过程,涵盖自定义字体滚动文本、AY 三声道配乐、视差星空等 Z80 汇编技巧与挑战。
<p><a href="https://lobste.rs/s/nroajo/writing_demoscene_intro_for_zx_spectrum">评论</a></p>
查看缓存全文
缓存时间: 2026/10/12 09:44
# 为 ZX Spectrum 编写 Demoscene Intro
来源:https://www.markround.com/blog/2026/zx-spectrum-intro
*探讨创作复古风格"crack intro"所用的工具、技巧与挑战*
**更新日期:** 2026年10月12日
立即下载 (https://www.markround.com/downloads/mdr-tnfstro.zip) 在 pouet.net 上查看 (https://www.pouet.net/prod.php?which=107200)
## 引言
我的 +3 上正在运行我的 intro (https://www.markround.com/assets/images/intro/speccy.jpg)
在80年代末,我把无数个小时蜷缩在 ZX Spectrum 前度过,照着杂志敲入 Sinclair BASIC 程序和简陋的汇编代码,试图重现我在朋友家看到的 Amiga crack intro。显然,它们从来没有哪怕接近过——但如今,几十年过去了,我终于做到了!我现在自豪地拥有了一个手工打造的复古 demoscene intro,可以在一台标准的 128Kb Spectrum 上运行。它包含了所有80年代末的经典元素:平滑滚动的文本(配'leet 定制字体!)、三声道 AY 配乐、弹跳的 VU 表,以及必不可少的视差星空。目前代码为 10287 字节,完整的磁带镜像为 19552 字节——考虑到我从去年12月13日就开始做了,这意味着我的平均代码产出率只有区区每周约 455 字节!
本文是我如何做到这一点的技术概述,我学到的一些很酷的技巧,以及一路上必须(重新)学习的关于 Speccy 和 Z80 汇编的一切。这既是我最喜欢的业余项目之一,也是最容易让我沮丧到差点一脚踹穿显示器的项目。我会在文末 [反思这一切](https://www.markround.com/blog/2026/zx-spectrum-intro#the-zen-of-z80),但如果你想直接看成品,下载按钮就在文章顶部,附带的 `.tap` 磁带镜像可以加载到任何模拟器中运行。还有一段 [YouTube 录像](https://youtu.be/ALbjMMgmAXo)、[pouet.net 上的作品页面](https://www.pouet.net/prod.php?which=107200),你也可以访问我的 [TNFS 站点门户](https://jsspeccy.markround.com/) 在浏览器中运行它——选择选项 4 查看最近上传的内容。
## 倒带,放回过去
但让我们短暂回到80年代末。作为一个8位、使用磁带加载的电脑世界的用户,第一次看到 Amiga 就像遇到了外太空的东西。和当年每个见过它的人一样,我被它展示的惊人图形和声音所震撼,更不用说那些 [开创性的游戏库](https://www.lemonamiga.com/games/votes_list.php)。但我发现自己更加着迷的是盗版游戏开头那些神秘的(而且超级酷的)"intro"。
它们由那些 [破解了版权保护并分发软盘游戏的软件盗版组织](https://www.kickstarter.com/projects/bitman/bootblock-rebels/) 创作,本身就是迷你艺术品——音乐、图形,通常还有一些很酷的特效来展示组织的实力。YouTube 上有一个很棒的这类 intro(或称"cracktro")[合集](https://www.youtube.com/watch?v=50WWFEBsgfk)。
看几个这样的视频,你很快就会注意到几乎所有 cracktro 共享的"设计"套路:屏幕顶部一个酷酷的、前卫的标志(如果在滴血或者有任何"金属感"就加分了),一些吹嘘的文字显示在某种特效之上——通常是滚动的视差星空,或者旋转的3D物体——以及更多的滚动文本,充满了向其他使用假名的破解者的致敬、问候,以及大量非法 BBS 系统的广告。不用说,对于一个80年代/90年代初的孩子来说,这可能是*有史以来最酷的东西*。
我第一次看到这个时觉得它绝对是*炸裂*的。正弦滚动器、海盗 BBS 广告、带剑的酷标志——当然,还有星空。老少咸宜。(https://www.markround.com/assets/images/intro/cracktros/horizon.png)
我第一次看到这个时觉得它绝对是*炸裂*的。正弦滚动器、海盗 BBS 广告、带剑的酷标志——当然,还有星空。老少咸宜。
这段音乐已经刻在了我的脑海里。考虑到 Lemmings 在90年代初的流行度,我想我的同学几乎人人都知道这个 intro。Skid Row 至今仍然活跃,这大概是那个时期最经典的 Amiga cracktro。(https://www.markround.com/assets/images/intro/cracktros/lemmings.png)
这段音乐已经刻在了我的脑海里。考虑到 Lemmings 在90年代初的流行度,我想我的同学几乎人人都知道这个 intro。Skid Row 至今仍然活跃,这大概是那个时期最经典的 Amiga cracktro。
无尽盗版。前卫。又一个酷标志、正弦波滚动器、星空和吹嘘文字。经典永不过时。(https://www.markround.com/assets/images/intro/cracktros/piracy.png)
无尽盗版。前卫。又一个酷标志、正弦波滚动器、星空和吹嘘文字。经典永不过时。
又一个所有经典元素的完美组合。酷标志,更酷的字体,滚动星空和底部滚动的文本。经典之作。(https://www.markround.com/assets/images/intro/cracktros/spirit.png)
又一个所有经典元素的完美组合。酷标志,更酷的字体,滚动星空和底部滚动的文本。经典之作。
标志。文字。滚动器。但没有星空——不过 Anthrox 的作品还是很扎实的 :)
标志。文字。滚动器。但没有星空——不过 Anthrox 的作品还是很扎实的 :)
[demoscene](https://en.wikipedia.org/wiki/Demoscene) 就是从这些 intro 中成长起来的。破解最终退居幕后,程序员、"美术师"和音乐家 [共同贡献](https://www.datagubbe.se/scenearch/) 出了长篇作品,展示他们的能力,与其他组织竞争,并将机器的性能推到极限。通过令人惊叹的智慧和纯粹的"代码即艺术",这个圈子至今仍然活跃,各团队制作出令人叹为观止的作品,比如 [这个](https://youtu.be/2jciCr8zEhw) 和 [这个](https://youtu.be/pYtleuGV7ok)——请注意,这两者都是在一台配备软盘、1Mb 内存和1985年声音芯片的 7MHz 家用电脑上运行的。
但尽管我很钦佩那种纯粹的技巧和奉献精神,真正让我着迷的始终是早期那些"单屏幕"滚动文本加星空的 cracktro。它们对我来说带有一种神秘的色彩,暗示着一个我只能从外部体验的圈子和文化——这正是为什么我花了那么多时间在 Sinclair BASIC 中,徒劳地试图重现它们,并梦想着有一天能拥有一台自己的 Amiga。
## 回归之路
我最终确实升级到了一系列 Amiga 系统,也加入了几个"scene"组织,并投身于 [各种活动](https://www.markround.com/blog/2026/06/17/25-its-only-when-you-look-back/),直到考试和青少年时代的结束到来。我那时就接触过 Z80 代码,有一段时间还知道足够的 68k 汇编来应付——部分来自 Amiga 的鼎盛时期,部分来自大学里几个"嵌入式系统"模块,在那里我需要编程红绿灯或电梯控制器。不过,由于在我的 `$DAYJOB` 中很少需要这类东西,这些知识基本上已经从我的记忆中消退了。
TNFS 站点截图 (https://www.markround.com/assets/images/intro/tnfs.png)
我在业余时间一直在从事一些 [项目](https://www.markround.com/projects),其中一个我最喜欢的是我的 [TNFS 站点](https://tnfs.markround.com/)——有点像一个老式的 BBS 系统或 Spectrum 的交互式"网站",它源于一个 [有趣的实验](https://www.markround.com/blog/2021/12/21/devops-for-the-sinclair-spectrum-part-1),看看我能把 Sinclair BASIC 推到多远。它依赖于安装在 Spectrum 上的一块现代网卡,这也是我与 "Desertkun"——另一位 TNFS 协议爱好者——聊天的原因。他目前正在为 Speccy 制作一款很棒的新 [WiFi 网卡](https://spectranext.net/),并好心寄给我几个原型让我帮他测试。
他的硬件和底层编码能力让我深受启发,在一次我们比较开发环境的视频通话中,他问我为什么不自己写一些 Z80 汇编。一句随口的话,但挑战已经立下了!当然,我无法用汇编重写整个网站,但我想到可以制作一个独立的 intro,以早期"BBStro"的风格来宣传它,*终于*成为一个拥有自己的 scene 作品的酷孩子了 :)
我是说,能有多难呢?(*哦——伏笔来了!*)
## 开发环境
我的开发环境相当直接,和我开发 TNFS 站点所用的是一样的技术栈。我用的是一个由 Makefile 驱动的构建系统,它会将我的代码通过 [Pasmo](https://pasmo.speccy.org/) 汇编器,我的 `zmakebas` fork 来构建 BASIC 文件,以及 [FUSE](https://fuse-emulator.sourceforge.net/) 模拟器进行测试。为了在真机上做最终测试,我将 `.tap` 文件上传到本地 TNFS 服务器,然后在 +3 上运行它们。
ASM Editor 截图 (https://www.markround.com/assets/images/intro/asm-editor.png)
有几个额外工具我必须提一下,因为它们最终产生了很大的影响——一个是我查找 Z80 指令时发现的令人惊叹的网页版 "asm-editor"。这个网站不仅有很好的 Z80 指令参考,还有一个编辑器,你可以在其中运行示例代码,查看内存和/或各种寄存器如何改变状态。以 [djnz](https://asm-editor.specy.app/documentation/z80/instruction/djnz) 指令为例——你可以构建它并逐步单步执行示例循环构造。或者试试我截图中展示的 [add](https://asm-editor.specy.app/documentation/z80/instruction/add) 指令示例,看看当一个位被移出寄存器时进位标志是如何工作的。虽然它使用的语法与 Pasmo 略有不同(所以我不能直接把代码粘贴进去),但在手动单步跟踪时它帮助巨大。
史上最棒的调试助手。(https://www.markround.com/assets/images/intro/dogs.jpg)
最后,可能是这个项目中最重要的调试工具——一本笔记本和一支笔,以及我的两只狗(跟 Biscuit 和 Pebble 说"嗨!"……)我会在 [总结部分](https://www.markround.com/blog/2026/zx-spectrum-intro#the-zen-of-z80) 更多地写这些,但这对我来说确实是一个非常不寻常的项目,来自数十年的更高级语言和编程范式的经验。我的大部分 z80 代码,只有在画出字节和位掩码、维护寄存器标志的表格、并手动画出屏幕内存的各个部分之后,我才真正理解。我发现和两只狗一起的晚间散步提供了理想的 [橡皮鸭调试](https://en.wikipedia.org/wiki/Rubber_duck_debugging) 练习,虽然我不确定它们对我长篇大论地喃喃自语 `hl` 寄存器对有什么看法。无论如何,"用狗调试法"强烈推荐!
## 起点
好了,让我们开始吧。我按照 [futurice.com](https://www.futurice.com/blog/build-environment-like-its-1986) 上的一个很棒的"Hello, world!"示例验证了我的构建环境。除此之外,我想要做的第一件事是加载我的标志图片和背景图片,它们将作为后续 intro 的"舞台"。在这个阶段我不太确定具体要做什么,只是想把标志放在顶部,滚动文本放在屏幕底部一行。由于我的 TNFS 站点页头看起来已经有点 demoscene 风格了,我快速编辑了一下,在其中放置了两条蓝色引导条,后续内容将放在它们之间。
TNFS 站点截图 (https://www.markround.com/assets/images/intro/memory_map.png)
Spectrum 的 `SCREEN$` 文件(通常显示为加载画面)只是屏幕内存的转储,从字节地址 16384 开始,长度为 6912 字节。这个奇怪的布局在后面非常重要,但要显示它,你只需要将文件加载到那个内存位置。经过几个小时查看 Speccy 内部结构和跟随各种指南之后,我有了一个非常简单的起点,它成为了后续一切的基础。
```asm
org $8000
screen EQU $4000 ; 屏幕内存第一个字节的地址,十进制为 16384
scrlen EQU 6912 ; 屏幕内存总长度,包含属性区
ula EQU $FE ; 控制边框的 ULA I/O 端口
black EQU 0 ; Spectrum 颜色编号,与 BASIC 使用的完全相同
start:
ld a,black ; 通过 ULA 将边框颜色设为黑色
out (ula),a
; 将我的标志头部图片复制到屏幕内存
ld hl,header ; HL = 此代码中屏幕文件的位置
ld de,screen ; DE = 屏幕内存起始地址
ld bc,scrlen ; BC = 要复制的 6912 字节
ldir ; 块复制,参见 https://jnz.dk/z80/ldir.html
; 忙等循环 - intro 的其余部分放在这里 :P
hold:
halt ; 休眠直到下一个中断(1/50 秒)
jr hold ; 然后再跳回继续休眠,永远循环
header:
incbin "assets/header.scr" ; 我的标志头部/背景图片
end start
```
现在看起来没什么好看的,但这个简单的起点让我接触了一些重要概念,比如通过 `out` 指令与 Spectrum 硬件(先是 [ULA](https://sinclair.wiki.zxnet.co.uk/wiki/ZX_Spectrum_ULA) 芯片,后来是 AY 声音芯片)交互,如何使用 Pasmo 的 [INCBIN](https://pasmo.speccy.org/pasmodoc.html#dirincbin) 指令引入二进制文件,使用 Z80 的块复制 `ldir` 指令,使用 [寄存器对](https://jnz.dk/z80/registers.html) 进行16位寻址,以及简单的标签跳转。
### Hello World
[](https://www.markround.com/assets/images/intro/hello.png)
下一步是在屏幕上打印一些内容,并探索 Speccy 内置的 ROM 例程——这些例程在某种程度上类似于现代操作系统中的系统调用。互联网上有大量 Spectrum ROM 的反汇编和指南,其中注释和链接最好的之一在 https://skoolkid.github.io/rom/。以此为参考,配合各种"Hello, world!"教程,我了解到这类东西是怎么构建起来的:
```asm
at EQU 22 ; 用于定位光标的 "at" 控制字符
row EQU 11 ; 打印消息的坐标
col EQU 9
prstring EQU $203C ; pr_string 例程在 ROM 中的位置
ld de,msg ; DE = 第一个字符的地址
ld bc,msglen ; BC = 要打印的字符数
call prstring ; 全部打印出来
; 忙等循环相同……
msg:
db at,row,col ; 等价于 BASIC 中的 "PRINT AT 11,9"
db "Hello, world!"
msglen EQU $-msg ; 总共 16 字节,pasmo 简写,$=程序计数器
```
我最终在代码中大多没有使用 ROM 例程,因为它们会引入各种我不需要的东西,但是
相似文章
ZX Spectrum System Tour: Text Mode – Bumbershoot Software
This post explores the ZX Spectrum's text mode from a machine language perspective, covering system organization, display routines, and input handling, as part of a series on programming the classic 8-bit computer.
ZX Spectrum:实验1位声音
作者在ZX Spectrum上实验PCM和PWM声音技术,以实现多声道1位音频播放,并记录了过程中的成功与失败。
将我的3D点云渲染器移植到ZX Spectrum 48K上
一位开发者将3D点云渲染器移植到ZX Spectrum 48K上,通过Z80汇编优化达到每秒14帧,并创建了一个预计算版本,运行速度为每秒40帧。
ZX Spectrum 系统之旅:声音
关于 ZX Spectrum 声音编程的技术综述,涵盖 1 位蜂鸣器和 AY-3-8910 芯片,并提供演奏 C 大调音阶的示例。
ZX Spectrum 的图形桌面
一个用于 ZX Spectrum 48K 的图形桌面环境,使用 Z80 汇编语言编写,具有重叠窗口、菜单和各种应用程序,且这些都适配于 48K 的 RAM。