什么是旧游戏中的“横向卷动黑客”?

15
我听说老式的侧卷游戏使用了特定的编程技巧来实现高效的侧卷。据我所知,多年前的游戏机没有现在那么强大,无法每一帧都重新绘制整个屏幕。有一些技术,比如脏矩形,可以在背景静止而只有精灵移动时最小化需要重新绘制的屏幕区域。
上述方法仅适用于背景不变的情况(因此大部分屏幕像素保持静止)。
像老派射击游戏这样的垂直滚动游戏由于滚动而导致背景每帧改变,但是人们可以利用像素进入显示器的方式(逐行)。我想人们可以使用更大的缓冲区并将数据指针每帧向下移动几行,以便从另一个位置重新绘制,从而产生平滑滚动的效果。仍然只需要重新绘制精灵(和屏幕边缘的一点背景),这是一个严重的优化。
然而,对于侧卷游戏,事情并不简单明了。尽管如此,我知道过去某个地方的某个人想到了一种优化方法(有一些限制),使得旧的机器可以水平滚动背景而无需每帧重新绘制。
据我所记,它被用于许多旧游戏中,主要是80年代的格斗游戏,以及demoscene作品。
你能描述一下这种技术并说明它的作者吗?

1
两个非常好的不重叠的答案!我希望我能够接受两个 :-) - Kos
4个回答

11

我曾经为传统的C64编写过游戏,实现平滑滚动有两个基本要点:

  1. 这些游戏并没有使用位图图形,而是使用了“重新映射”字符字体,这意味着8x8像素块会被转换为一个字节。

  2. 需要注意的下一点是,硬件支持将整个屏幕向上或向下移动七个像素。需要注意的是,这不会以任何方式影响任何图形——它只是使发送到电视机的所有内容都稍微偏移了一点。

因此,第二个要点使得真正平滑滚动7个像素成为可能。然后你就可以移动每个字符——对于一个完整屏幕,恰好是1000个字节,计算机可以处理,同时你将滚动寄存器向后移动7个像素。8-7=1就意味着看起来你又滚动了一个单独的像素......然后继续这样进行即可。因此,1)和2)结合在一起才产生了真正平滑滚动的错觉!

之后,第三个要点也变得非常重要:扫描线中断。这意味着当电视/显示器要在指定位置开始绘制扫描线时,CPU会收到一个中断。这种技术使得可以创建分屏,因此你不需要滚动整个屏幕,而是只需滚动部分区域。

更具体地说,即使你不想使用分屏,光栅中断也非常重要:因为现在和过去一样(但现在的框架已经将其隐藏),在正确的时间更新屏幕非常重要。如果当电视/显示器在可见区域的任何位置更新时修改“滚动寄存器”,那么就会产生“撕裂”效果——你会清晰地注意到屏幕的两个部分与彼此相差一个像素。

还有什么需要说的吗?嗯,使用重新映射字符集的技术可以轻松地进行一些动画制作。例如,输送带和齿轮等物品可以通过不断改变屏幕上代表它们的“字符”的外观来进行动画制作。因此,跨越整个屏幕宽度的输送带可以通过只更改字符映射中的一个字节来看起来在任何地方都在旋转。


很好。我在电脑上尝试过类似的东西,但受限于只能将 256 个不同的“字符”放入集合中。 - 3Dave
C64的视频芯片中还有一个标志,可以将显示水平缩小一个字符,这样您就不会注意到当前滚动的额外字符。 - Karoly Horvath
@yi_H 确实如此。现在你真的在深入细节了! :-) - Dan Byström
请注意,在c64上,您还必须移动1000个字节的颜色RAM,这不能像屏幕RAM那样在复制屏幕时进行缓冲。 - Fabrizio Stellato
这就是为什么滚动条总是只使用四种颜色的原因! - Dan Byström

8

我在90年代的时候也使用了类似的方法,采用了两种不同的方式。

第一种方法涉及“窗口化”,这是VESA SVGA标准支持的。某些卡片正确地实现了它。基本上,如果你有一个帧缓冲区/视频RAM大于可显示区域,你可以绘制一个大位图并给系统坐标,以显示该区域内的窗口。通过改变这些坐标,您可以进行滚动而无需重新填充帧缓冲区。

另一种方法依靠操作BLT方法,该方法用于将已完成的帧传输到帧缓冲区。将与屏幕大小相同的页面传输到帧缓冲区非常容易和高效。

我找到了一些旧的286汇编代码(在一个正常运行的17年老软盘上!),该代码将64000字节(320x200)屏幕从屏幕外页复制到了视频缓冲区:

  Procedure flip; assembler;
    { This copies the entire screen at "source" to destination }
    asm
      push    ds
      mov     ax, [Dest]
      mov     es, ax
      mov     ax, [Source]
      mov     ds, ax
      xor     si, si
      xor     di, di
      mov     cx, 32000
      rep     movsw
      pop     ds
    end;
rep movsw指令移动CX个字(在此情况下,每个字是两个字节)。由于这基本上是一条指令,它告诉CPU尽可能快地移动整个东西,因此非常高效。
然而,如果您有一个更大的缓冲区(比如侧向滚动游戏中的1024 * 200),您可以很容易地使用嵌套循环,并且每次循环复制一行像素。例如,在1024像素宽的缓冲区中,您可以复制字节:
start          count            
0+left         320
1024+left      320 
...
255*1024+left  320

其中left是您想要从大背景图像的哪个x坐标(屏幕左侧)开始的位置。

当然,在16位模式下,需要对段指针(ES、DS)进行一些魔法和操作才能获得大于64KB的缓冲区(实际上是多个相邻的64k缓冲区),但它运行得非常好。

这个问题可能有更好的解决方案(今天肯定有更好的解决方案),但对我来说它起作用了。


2

街机游戏经常采用定制视频芯片或离散逻辑,以允许滚动而无需CPU(大量)工作。该方法类似于danbystrom在C-64上所描述的内容。

基本上,图形硬件负责处理精细滚动字符(或图块),然后CPU处理替换所有图块,一旦滚动寄存器达到其极限。我目前正在研究Irem m-52板,该板可以通过硬件处理多个滚动背景。电路图可以在网上找到。


1
为了在Commodore Amiga上进行右滚动,我们使用了Copper来将屏幕向右移动最多16个像素。当屏幕移动后,我们会在屏幕缓冲区的起始地址添加2个字节,而在右侧,我们使用Blitter将图形从主存储器复制到屏幕缓冲区。我们会将屏幕缓冲区设置得略大于屏幕视图,这样我们就可以在视口右侧复制图形而不会看到闪烁效果。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接