《超级马里奥3》使用了什么样的技术可以在128KB中写进这么多东西?

我目前的工作是制作手机游戏, 回头再看FC上的有些游戏, 不由得非常惊愕了
关注者
828
被浏览
244,172

13 个回答

我写过FC模拟器,对这个比较了解。

  • 代码部分: 全是手写的6502汇编,要多精简有多精简。
  • 声音部分: FC有两路方波生成器,一路三角波生成器,一路随机噪声,一路PCM。PCM采样可以实现很好的效果,但数据量巨大,所以大多数游戏没用。用方波/三角波来发声就类似最简单的MIDI音乐,数据量很小。
  • 图形部分: 这是主要占卡带容量的部分,下面简单说一下
    • FC的图形处理单元以sprite的方式组织图像,程序能控制的最小显示单位不是一个点,而是8x8的sprite。
    • “那整个画面卷动的时候不是应该一块一块(8像素)的跳动吗?”——PPU另外有卷轴寄存器,可以按像素级指定卷动的量。也就是说sprite虽然是8x8的,但在屏幕上不见得总是对齐到8x8的格子里。
    • sprite里具体每个点的颜色也不像现在是豪华的16位或32位RGB直接指定,而是一个2bit的索引,从一个16色的调色板里选。
    • 既然是2bit的索引值,最多就只能指定4色。而且因为00固定代表透明,所以实际上最多只有3色。
    • 那为啥要用到16色的调色板?实际上是4个4色的调色板。每4个相邻的sprite(16x16)可以从这4个调色板里指定一个。所以你去数,FC的一个16x16 sprite里最多只能显示4种颜色,因此会有那种经典的单调感。
    • 16色的调色板有两个,一个背景层用,一个角色层用。调色板从FC PPU固定死的64种颜色中选取颜色。因为这64个颜色里黑色/透明重复了多次,所以实际不同的颜色只有54种。
    • 不管你看没看晕,上面的重点是,FC游戏里面一个像素只占用2bit的卡带空间。
    • 并且因为所有显示内容都按sprite组织,大大方便重复使用。要显示形状一模一样的两朵云,或者几十个砖块,只需要在显存里重复写下这些sprite的编号,而不是重复sprite的数据(8x8x2/8=16byte)
    • 因为调色板的存在,形状一样而颜色不同的图案也不需要占额外的空间,只要设置一下调色板就行了,包括变色的效果也是这样做的。红色的Mario和绿色的Luigi就是同一套Sprite。
    • 透明、滚动、前后景叠加、碰撞检测都是PPU完成,不需要另外写程序。
    • sprite的数据也不存在由CPU从卡带读入到RAM的过程(除了后期的磁碟机),而是直接由卡带映射到FC的64KB地址空间进行访问。不同的卡带硬件(所谓的mapper)支持由程序控制将卡带上的不同地址映射到FC的同一地址空间,所以尽管8位的FC只有64KB寻址能力,但卡带容量可以做到256KB甚至更大。

所以说白了就是在当年技术条件有限的情况下,实现了各种基础的图像压缩算法,以及对开发人员要求极高的数据重用……

之所以美版魂斗罗只有128KB容量,而日版有256KB,就是因为美版砍掉了日版里的各种片头/过场/片尾的动画和图案。对,本人目前的头像就是被美版Contra精简掉的内容之一。

稍微说点,不会写模拟器,就说说有关图像构成的吧。

比如这么一张“贴图”,它的分辨率是16*16,包括透明在内共有四种颜色。

如果用PNG存储,大概是378B,已经很小了。

然而用FC存储要多少呢?如果忽略掉上色问题,那么在FC中「不经压缩地」存储一张「能够区分出四种颜色的16*16图像」,就只要64B

因为FC的图像基本单位正常是8*8作为一个图块(上图是四个图块),这里就以左上角的一个图块来讲解,涉及到的数据量是16B,其他图块的数据量和是一样的。

首先,很容易看出,一个图块包括透明色,一共就四种颜色,这是游戏平台的限制,用一个两位二进制数表示一种颜色,所以共有00 01 10 11四种用于区分图像颜色的数值,由于调色版的定制,在图中,00是透明,01是肉色,10是红色,11是黑色。

不过FC存储的图块结构一般不是直接连续存储这个两位二进制数(GB就是连续存储),而是将每个颜色的低位和高位分离出来,各自连续存储。所以这里我们同样要对图像进行颜色分离,把一张图片变换成两张图片(若引起不适还请见谅)。

左图是将红色全部替换成黑色,并删除肉色,这么做是为了连续记录图像点阵的高位数据(高位是1的有红色和黑色,透明和肉色的高位是0)。

右图是将肉色全部替换成黑色,并删除红色,这么做是为了连续记录图像点阵的低位数据(低位是1的有肉色和黑色,透明和红色的低位是0)。


这么做的话,我们就得到了两张单色图像,实际使用的时候,如果两方读取到的都是0(00),那就显示透明了,如果在某位读取到1而在另一位读取到0(10或01),那么就显示对应的颜色,如果高低位读取到的都是1(11),那么就显示黑色。

现在我们将两张图片上的点用0代表透明 1代表颜色,进行标注,可以得到

高位的连续数据
01110011
10001111
10011111
01111101
01100001
01100010
00111111
00110000

低位的连续数据
01110011
11111100
11110000
01011111
01011111
01011111
00111111
00101111

比如左上角第一个像素点,读取到00,便是透明

这个二进制的点阵,就是最终存储到游戏当中的图像数据,二进制中每8位代表一个字节,这里一行就是一个字节,16行正好16B。

(可选)你可以把这个二进制点阵转换成平时更多使用的16进制代码,将上面的二进制点阵,转换成十六进制便是

738F9F7D61623F30 73FCF05F5F5F3F2F

这种转换,微软的计算器都能做到。

这样就完成了一个图块,它的数据大小是16B,未经压缩,FC游戏正常的图像都是这么存储的。

游戏ROM中对应图像的四行。你可以用同样的方法反推导出图片,修改并再次导入回去(当然,如果真要改,有专用工具,不用这么麻烦)。

所以其实并不是说游戏用了什么技术能写进这么多东西,而是本身的设计就是如此装入数据,说白了,对数据所能提供的信息量进行限制,那么用于表达数据所需要的数据量自然也就跟着减小,对于图像来说,单个图块最多4色就是一个明显的限制,更不用说什么同屏允许多少图块,同屏多少颜色等等的限制了。

注:实际游戏中两个点阵中读取到的0和1一共对应四种颜色00 01 10 11,实际上是哪种颜色是由调色板处理完成,并不是说00就一定是透明,11就一定是黑色。这里的例子是Mario,然而Luigi用的图像数据也还是这一段,不过通过调色版选择了其他颜色(比如红色换成绿色)来代表Luigi。

上面的讲述改来改去,也不知道如何能够用比较合适的方式来说清楚,先这样吧。