1988年,世嘉把一台使用摩托罗拉68000处理器的16位游戏机推向了市场。那时候没有人会想到,三十多年后,还会有一批开发者在没有官方SDK的情况下,为这台主机持续编写新游戏。如果你关注过复古游戏圈,一定见过“Homebrew”这个词:玩家或独立开发者不依赖商业发行渠道,自己制作并运行在特定主机上的ROM。放在世嘉Mega Drive(海外叫Genesis)上,就是Genesis Homebrew游戏。
这件事听起来小众,但它背后藏着一套完整的嵌入式游戏开发流程。更关键的是,做16位主机开发最大的价值不是情怀,而是“约束”。64KB主RAM、64KB显存、64色总调色板、每行最多20个硬件精灵——在这种资源下,一个《机器战警》风格的游戏里,角色、街道、爆炸效果都必须被压缩进极其有限的数据空间。这种面向真实硬件上限做优化的能力,是现在天天写手机游戏逻辑时很难训练出来的。
今天的文章就以《机器战警》MD版这个玩家自制项目为线索,完整拆解一条MD/Genesis Homebrew游戏开发路径。先理解硬件原理,再搭建SGDK开发环境,然后是素材转换、C语言代码和模拟器验证。读完这篇文章,你至少能写出一个可以在模拟器和烧录卡上跑起来的“角色移动原型”,并知道下一步该往哪个方向深入。
1. 为什么在2025年还有人在做MD自制游戏
先回答一个很现实的问题:一台1988年发布的主机,现在做它图什么?
从商业层面看,MD自制游戏几乎没有利润,大多数项目是免费发布或以极低价格卖实体卡带,图的是社区认可和硬件收藏者的支持。但如果你把视角从“游戏产品”切换到“开发训练”,它的价值立刻变得非常具体。
现代游戏引擎帮你处理了绝大部分渲染、内存和输入问题。你不需要关心显卡驱动怎么写,也不需要关心纹理数据到底放在内存的哪一段。而在MD上,所有事情都落到最底层。你写的是C代码,但编译出来的程序直接跑在68K处理器上,你操作的是VDP硬件寄存器,你往64KB显存里塞的每一块tile数据都要自己规划。这种开发方式,本质上就是嵌入式开发,只是“工业设备”换成了“游戏机”。
所以,做MD自制游戏适合三类人。第一类是嵌入式开发者,想找一个有图形、有音乐、有输入、有交互的练习项目,比裸板点灯有趣得多。第二类是游戏开发者,想理解帧循环、精灵、双缓冲、调色板和显存预算这些概念在没有任何引擎遮蔽时是如何工作的。第三类是复古硬件爱好者,单纯想让自己喜欢的平台继续有新的内容产出。
这里有一个容易被忽略的判断:MD Homebrew的门槛主要在“素材转硬件数据”和“理解VDP”,而不是“会不会写代码”。只要你熟悉C语言的基础语法,剩下的难点大多集中在一条固定流程上:图片转tile、调色板注册、DMA送显。这篇文章的核心目标,就是把这条流程给你走通。
2. Genesis硬件核心概念:开发前必须理解的几个词
在写第一行代码之前,先花五分钟理解MD的硬件组成。它们决定了你代码里每一处设计。
2.1 主CPU与副CPU
MD的主CPU是摩托罗拉68000,频率7.67MHz(NTSC),它在当时是一颗非常强劲的处理器,但和现代CPU相比,它的内存访问速度和指令能力都很有限。控制角色移动、碰撞检测、游戏逻辑都由它完成。副CPU是Z80,主要用于驱动音频芯片,也承担一部分旧式Master System兼容任务。游戏开发者通常不直接操作Z80,而是通过SGDK的音频接口间接使用它。
2.2 VDP与显存
VDP(Video Display Processor)是MD的图形核心。它负责把显存里的tile数据合成成最终画面。显存总共64KB,非常小。这里没有任何“自动纹理压缩”,你放进去多少tile,就要精打细算多少字节。
画面上的背景由可滚动的“平面”(Plan)组成,MD提供两个主要背景平面Plan A和Plan B,外加一个窗口层。每个平面相当于一张二维的tile地图,每一个格子指向显存里的某个8×8像素tile。常见分辨率320×224下,屏幕大约显示40×28个tile,但平面本身可以比屏幕大,通过卷轴滚动显示不同区域。
2.3 调色板与颜色限制
MD总共有4个调色板,每个调色板16色,总计64色。注意,这里说的是“同时在屏幕上使用的颜色上限”,不是整个画面允许出现的颜色总数。背景可以用两个调色板,角色和精灵用另外两个调色板,这是最常见的分配方式。EDU:
PAL0 // 背景1 PAL1 // 角色精灵 PAL2 // 背景2/特效 PAL3 // UI或敌人颜色使用这么紧,意味着美术风格的统一比细节堆砌更重要。给《机器战警》做素材时,一个常见失误是角色动画帧里用了太多种颜色,导致一个角色必须占用两个调色板,其他角色就没得用了。
2.4 精灵
MD硬件支持最多80个精灵,但每行最多显示20个。超出20个的那一行,VDP会按优先级丢弃,表现就是“精灵闪烁”或“消失”。设计动作游戏时,Boss战、爆炸、子弹、角色同时出现在同一行,很容易踩到这个上限。
2.5 DMA
DMA是VDP自带的显存搬运通道。CPU给出源地址、目标地址和传输长度后,VDP可以自行把数据从ROM或RAM拷入显存,不需要CPU逐字搬运。SGDK会在VBlank期间自动处理一批DMA任务,这也是为什么你在代码里经常看到“所有绘制操作放在VBlank里执行”的惯例。
| 概念 | MD硬件情况 | 现代对比 |
|---|---|---|
| 纹理 | 8×8像素的tile | PNG/纹理图集 |
| 调色板 | 4组×16色,共64色 | RGBA自由颜色 |
| 精灵 | 80个硬件精灵,每行20个 | 无硬件精灵概念 |
| 背景卷轴 | 两个可滚动平面+窗口层 | 2D相机渲染 |
| 显存 | 64KB VRAM | 几百MB显存 |
| 内存 | 64KB主RAM | 动辄GB内存 |
看完这张表你会发现,MD游戏开发的本质就是“用小数据量做高表达”。这也是为什么很多现代开发者第一次接触MD开发,会觉得处处受限制,但一旦适应之后,会反过来觉得“约束反而让设计目标更清晰”。
3. 工具链:SGDK是怎么把C代码变成ROM的
MD没有官方SDK,早期开发靠厂商内部资料。现代自制游戏之所以能繁荣,很大程度归功于SGDK这个社区项目。SGDK的全称是“Sega Genesis Development Kit”,由开发者Stef主导维护。它提供了一套基于GCC的68K交叉编译工具链、资源编译器rescomp、运行时库以及大量硬件封装。
SGDK让你可以用C语言写游戏,而不是必须掌握68000汇编。但这不意味着汇编完全没用,当你需要精确控制VDP寄存器或做极致性能优化时,仍然要查看生成的汇编或者手写一小段内联汇编。
它的工作流程可以概括为:
- 用C语言编写游戏逻辑。
- 用rescomp把PNG、BMP、WAV等素材编译成tile数据、调色板数据和音频数据。
- 用m68k-elf-gcc把C源码编译成68000机器码。
- 链接脚本把代码和数据安排在正确的内存地址。
- 最终生成一个二进制ROM文件,可以喂给模拟器或者烧录到实体卡带。
这种工具链结构,和现代嵌入式开发的“交叉编译+烧录”流程几乎一模一样。区别只是目标平台从STM32换成了68K游戏机。
对比一下三种开发路线:
| 开发方式 | 难度 | 开发效率 | 适合场景 |
|---|---|---|---|
| 纯68000汇编 | 很高 | 低 | 深入学习硬件、极限优化 |
| SGDK+C语言 | 中等 | 高 | 大多数自制游戏项目 |
| 基于SGDK的模板/引擎 | 较低 | 很高 | 快速做游戏原型 |
对大多数项目,SGDK是当之无愧的首选。它虽然隐藏了一部分硬件细节,但VDP指令、调色板管理、精灵上限、DMA传输这类核心概念还是会被反复用到。所以,用SGDK不等于脱离硬件,而是“在硬件之上用合理抽象开发”。
4. 开发环境搭建:从安装SGDK到模拟器验证
接下来进入实操环节。本文以Linux/macOS环境为例,Windows用户可以借助WSL或使用SGDK发布包中自带的Windows工具链,思路完全一致。
4.1 安装SGDK
从SGDK的官方GitHub仓库下载最新版本,或者用git克隆到本地。SGDK目录里通常自带构建平台相关的工具程序,包括rescomp和交叉编译器。建议克隆后先确认bin目录下的工具能否运行。
将SGDK目录配置到环境变量,后面所有Makefile都会引用这个路径。
export SGDK=$HOME/dev/SGDK4.2 安装基础依赖
在Linux上,确保make、git和基础编译工具已经安装。macOS一般自带编译环境,如果没有,安装Xcode Command Line Tools即可。这一步不需要安装宿主机上的大型游戏框架,因为SGDK的构建只依赖GCC交叉工具链和make。
# Ubuntu/Debian 示例 sudo apt update sudo apt install build-essential make git4.3 安装模拟器
模拟器是开发阶段最重要的验证工具,推荐BlastEm。它精度高,支持调试输出,是SGDK社区使用最普遍的模拟器之一。Kega Fusion也是老牌选择,兼容性好,日常快速测试很方便。RetroArch的Genesis Plus GX核心也可以,但调试能力弱一些。
| 模拟器 | 精度 | 调试能力 | 使用建议 |
|---|---|---|---|
| BlastEm | 高 | 强,有VRAM/寄存器查看器 | 首选 |
| Kega Fusion | 中高 | 一般 | 日常简单测试 |
| Genesis Plus GX(RetroArch) | 中高 | 弱 | 体验兼容性 |
| 实机+烧录卡 | 极高 | 弱 | 最终验证 |
4.4 搭建最小工程目录
建议所有MD项目统一采用下面的目录结构,资源和代码分离,后续扩展逻辑清晰:
robo-md/ ├── res/ │ ├── robo.bmp │ ├── bg_city.bmp │ └── resources.res ├── src/ │ └── main.c ├── Makefile └── out/5. 从素材到资源:构建“机器战警”原型需要准备的数据
MD游戏开发最容易被低估的环节是素材转换。很多新手写好C代码,加载图片时花屏,第一反应是“代码错了”,其实是图片格式或调色板不对。
5.1 图片要准备成什么样
rescomp推荐使用BMP或PNG格式。为了保证转换结果正确,图片需要满足几个硬性要求:
- 如果是BMP,尽量用8位索引色。这样可以明确控制颜色数不超过16色。
- 尺寸要是8的倍数。背景图的尺寸一般和画面分辨率对应,比如320×224。
- 精灵图建议按“帧+方向”排列,比如一个角色原地4帧、左右方向各4帧。
比如一个《机器战警》风格的角色素材,可以先准备一张24×32像素的站立图。24×32这个规格正好是3×4个tile,非常适合做一个局部装甲,但缺一条腿的形象原型。
注意,这里说的24×32是精灵图整体尺寸,SGDK的SpriteDefinition支持把一个大的精灵图切分成多个硬件精灵进行绘制。尺寸超过硬件限制时,真实项目中需要谨慎处理。
5.2 用res文件声明资源
SGDK的rescomp通过一个resources.res文件统一管理素材。下面是一个最简单的声明:
// 文件路径:res/resources.res SPRITE sprite_robo "robo.bmp" 24 32 PALETTE pal_robo "robo.bmp" IMAGE bg_city "bg_city.bmp" 0解释一下这三行:
SPRITE sprite_robo "robo.bmp" 24 32:声明一个精灵,宽度24像素,高度32像素。PALETTE pal_robo "robo.bmp":从图片中提取调色板,命名为pal_robo。IMAGE bg_city "bg_city.bmp" 0:声明一个背景图像,最后的0表示保留第一张调色板。
rescomp会为这些声明生成对应的C语言全局变量,在代码里可以直接引用。生成的资源结构体里,通常会包含palette指针、tile数据和tilemap数据。开发者不需要关心底层二进制布局,但要知道每个资源都会占用ROM空间,而ROM里的数据最终会被DMA加载到显存。
素材准备到这里,代码才有意义。接下来开始写第一个可运行程序。
6. 显示背景:第一个可运行的SGDK程序
这一节的目标很简单:让屏幕显示一张城市街道背景,然后进入死循环持续刷新。这是所有MD项目的第一步,跑通这一步,说明工具链、资源管线和基础初始化全部正常。
在src/main.c里写入下面的代码:
// 文件路径:src/main.c #include <genesis.h> #include "resources.h" int main() { // 设置屏幕宽高,MD常见两种模式:256x224 / 320x224 VDP_setScreenWidth320(); VDP_setScreenHeight224(); // 设置背景调色板,PAL0是第一个调色板 VDP_setPalette(PAL0, bg_city.palette->data, 64); // 把背景图绘制到Plan A VDP_drawImage(PAL0, &bg_city, 0, 0); while (1) { // VBlank期间提交绘制任务,继续刷新下一帧 SYS_doVBlankProcess(); } return 0; }这段代码里的关键点:
VDP_setScreenWidth320()和VDP_setScreenHeight224():设置画面分辨率。MD的主频决定了NTSC和PAL模式略有不同,但320×224是大多数动作游戏的通用选择。VDP_setPalette(PAL0, bg_city.palette->data, 64):把资源文件的调色板数据加载到硬件的PAL0。第二个参数指向调色板数据,第三个参数是颜色数量,这里是64,表示从PAL0开始连续设置64个颜色槽。实际项目中根据调色板长度填写,不必盲目写64。VDP_drawImage(PAL0, &bg_city, 0, 0):把背景资源绘制到Plan A的(0,0)位置。这个函数内部会把tile数据通过DMA送入显存,并填充平面地图。- 主死循环里面必须有
SYS_doVBlankProcess()。VBlank是画面垂直消隐期,MD在这个时间段里可以安全地修改显存,避免画面撕裂。如果循环体太短,插一句“这个函数别忘了”都不为过。
如果一切正常,编译后运行,你会看到一张静态的城市街道背景。这一步的验收标准很单纯:画面不花屏、不黑屏、颜色和图片原稿一致。
7. 加入角色与手柄控制:让“机器战警”在街道上动起来
背景显示成功后,下一步就是主角精灵。这一节会加入一个简易的“机器战警”角色,并让玩家通过手柄控制它左右上下移动。这是很多MD动作游戏的最早原型。
先更新resources.res,确认精灵资源已经声明。然后编写带控制和精灵逻辑的main.c:
// 文件路径:src/main.c #include <genesis.h> #include "resources.h" int main() { u16 x = 140; u16 y = 160; Sprite* robo; VDP_setScreenWidth320(); VDP_setScreenHeight224(); // 背景调色板加载到PAL0,精灵调色板加载到PAL1 VDP_setPalette(PAL0, bg_city.palette->data, 64); VDP_setPalette(PAL1, pal_robo.data, 64); VDP_drawImage(PAL0, &bg_city, 0, 0); // 创建精灵,初始位置为(x, y),属性使用PAL1,不翻转 robo = SPR_addSprite(&sprite_robo, x, y, TILE_ATTR(PAL1, 0, FALSE, FALSE)); while (1) { u16 joy = JOY_readJoypad(JOY_1); if (joy & BUTTON_LEFT) { if (x > 0) x--; } if (joy & BUTTON_RIGHT) { if (x < 320 - 24) x++; } if (joy & BUTTON_UP) { if (y > 0) y--; } if (joy & BUTTON_DOWN) { if (y < 224 - 32) y++; } // 把新坐标写入精灵 SPR_setPosition(robo, x, y); // 提交精灵更新 SPR_update(); // 等待VBlank SYS_doVBlankProcess(); } return 0; }这段代码有几个需要特别解释的设计:
7.1 精灵属性
SPR_addSprite(&sprite_robo, x, y, TILE_ATTR(PAL1, 0, FALSE, FALSE))的参数中,TILE_ATTR指定了精灵使用的调色板编号、优先级、垂直翻转和水平翻转。这里把角色放在PAL1,和背景的PAL0分开,方便调色板管理。
注意,精灵的坐标边界判断用了硬编码的“320-24”和“224-32”,这是为了不让角色走出屏幕。真实项目中,边界条件通常由关卡数据控制,但原型阶段先保证角色不出屏即可。
7.2 手柄输入
JOY_readJoypad(JOY_1)返回主机1P手柄当前帧的状态,各个按键通过位掩码判断。SGDK不仅支持方向键,还支持A、B、C、Start等按键。做格斗或射击游戏时,还需要多帧连按检测和按键边沿检测,这里先不展开。
7.3 移动速度与帧率
代码里每次按键移动1像素,在MD的60FPS刷新下,每秒移动60像素。这个速度在动作游戏里偏慢,玩家会感觉角色“漂”。正确做法是定义移动速度,乘以帧间隔时间。比如每帧移动2像素,对应每秒120像素,才更像一个量产型机器警察的正常行走速度。
把这段代码编译运行,你会看到一只机器警察角色在街道背景上移动。虽然逻辑非常简单,但它已经包含了游戏循环的主干:读输入、更新逻辑、更新精灵、提交渲染、等待VBlank。之后的战斗、NPC、关卡切换,都是在这个循环上不断叠加新的子系统。
8. ROM的运行验证:模拟器、调试与真机烧录
代码写完之后,最关键的步骤是验证运行结果。很多第一次接触MD开发的人在这一步遇到问题,主要是不知道构建输出在哪里,也不知道模拟器怎么用。
8.1 构建ROM
在工程根目录执行make:
export SGDK=$HOME/dev/SGDK make -f MakefileMakefile的内容至少需要包含SGDK路径引入:
# 文件路径:Makefile SGDK ?= /home/user/dev/SGDK include $(SGDK)/makefile.gen编译成功后,输出文件通常在out目录下,名称为rom.bin或rom.md。不同SGDK版本的默认输出名可能不同,但都以out目录为基准。
8.2 在BlastEm中运行
blastem out/rom.binBlastEm启动后,默认Handicap用键盘模拟手柄。方向键对应手柄方向,按键映射可以在配置文件中调整。如果程序成功运行,你会看到背景图和角色。如果黑屏,最可能是初始化少了某个步骤,或者调色板没设置。
BlastEm还提供了VDP调试视图,可以查看当前VRAM中已经加载了哪些tile、精灵表内容是什么。遇到花屏时,先打开这个调试器,看tile数据有没有被正确写入。
8.3 在真机或烧录卡上验证
模拟器跑通只代表“大部分情况下正常”。MD上的实机运行还会暴露模拟器掩盖的问题,比如DMA时序、控制器兼容性、电源噪声导致的画面异常。有条件的话,购买一块Mega Everdrive或类似烧录卡,把rom.bin放进SD卡,在实体主机上运行。这也是SGDK项目推荐的真机验证方式。
这里有一个真实的工程提醒:烧录卡运行ROM,偶尔会因为某些SGDK版本与老旧烧录卡固件不兼容而失败。如果真机黑屏但模拟器正常,先检查烧录卡固件和ROM的region设置,而不是怀疑代码。
9. 常见问题与排查思路
MD自制开发过程中,下面几个问题出现频率最高。整理成排查表,方便遇到问题时直接对照。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 编译报错找不到m68k-elf-gcc | SGDK环境变量未设置,或工具链不完整 | 检查SGDK/bin目录下是否有交叉编译器 | 正确配置export SGDK,重新下载工具链 |
| ROM运行黑屏 | VDP初始化不完整,或主循环缺少SYS_doVBlankProcess | 确认代码进入while循环;检查VDP_setScreenWidth调用 | 补充屏幕设置,确保主循环调用VBlank |
| 背景花屏 | 图片颜色数超过16,或尺寸不是8的倍数 | 在res中检查图片格式;用图像软件查看颜色数 | 转换为8位索引BMP,限制颜色,调整尺寸 |
| 精灵显示闪烁 | 同一扫描线精灵超过20个 | 观察花屏位置;打开BlastEm精灵调试窗口 | 减少同屏精灵,或调整精灵的垂直分布 |
| 手柄按键无响应 | 读取了错误的玩家编号 | 确认是JOY_1还是JOY_2 | 正确指定JOY_1;检查模拟器按键映射 |
| 颜色和素材原稿不一致 | 调色板索引错位 | 检查PAL编号是否和TILE_ATTR参数一致 | 统一背景和精灵的调色板分配 |
| 角色移动卡顿 | 每帧移动间隔不固定,或计算量过大 | 观察移动帧率;优化循环体 | 使用固定步长移动,减少VBlank外绘制操作 |
| 烧录卡运行报错 | 烧录卡固件或ROM区域设置不对 | 检查烧录卡文档;尝试其他ROM验证 | 升级固件,确认region和ROM格式 |
这里最容易被新手误解的是“花屏”。花屏不一定是代码写错,很多时候是素材格式不合格造成的。调试顺序建议是:先确认编译输出没有错误,再用BlastEm的VRAM调试器查看tile数据,最后检查资源文件里的声明和图片本身。
10. 工程建议与后续学习方向
跑通《机器战警》MD版的角色移动原型之后,你已经掌握了MD自制游戏开发最核心的骨架。接下来能不能做出完整作品,取决于工程化管理做得好不好。
几点建议,都是实际项目中容易踩坑的地方:
第一,素材命名必须统一。不要出现bg_01、asd、最终版这种命名。建议采用“类型_对象_用途”的结构,比如spr_enemy_robot_fire_01、bg_street_night。资源文件多了以后,好的命名省的不只是时间,还有排查问题的成本。
第二,调色板要提前规划。每个调色板16色是死限制,设计角色和场景时,先列出全局调色板分配表。是机器人用PAL1、敌人用PAL2、道具用PAL3,还是场景风格优先?这个决策应该在美术阶段就完成,而不是写代码时临时换。
第三,精灵动画不要一帧一帧手写switch。可以建立帧数组和动画状态机。比如“站立”“行走”“射击”是三个状态,每个状态对应一组帧序号。SGDK本身没有强约束动画组织方式,但项目复杂后,统一的动画管理能避免大量重复代码。
第四,善用VBlank。MD的CPU和VDP并行工作,VBlank期间更新显存是最稳定的方式。不要在游戏逻辑里频繁调用VDP写函数,尽量把绘制任务集中在每帧末尾或VBlank回调里。
第五,开发过程中要保持“模拟器通过+真机验证”的双通道。BlastEm能解决大多数问题,但真机和烧录卡能发现模拟器忽略的时序差异。每个里程碑都烧录一次,避免最后一次性验证时问题堆积。
后续学习方向,按优先级排序:
- 阅读SGDK自带的示例项目,里面有背景卷轴、精灵动画、音频播发、手柄连发等全套用法。
- 学习VDP的滚动机制。做一个能左右滚动的横版街道场景,是《机器战警》类型游戏必须突破的下一步。
- 研究YM2612音频芯片和SGDK的音频接口。音乐和音效对动作游戏氛围的影响非常明显。
- 有条件的话,翻看68000汇编基础。不一定要全用汇编,但能读懂SGDK生成的汇编输出,对你排查性能问题有巨大帮助。
- 加入社区,阅读别人的Homebrew项目源码。很多实现技巧,比如精灵调度、调色板渐变、Boss战分镜,都是靠读代码学来的。
做一件32年前主机上的新游戏,本质上是和一台古老的硬件进行对话。你可能无法靠它赚钱,但这个过程会让你对游戏如何真正在硬件上运行,产生远比“用引擎拖节点”更深刻的理解。先把这份代码改造成你顺手的编辑器,然后试着让角色开一枪试试。