什么是“横向滚动黑客”?来自老游戏?

发布于 2024-12-15 09:43:02 字数 559 浏览 2 评论 0原文

我听说旧的街机横向卷轴游戏使用特定的编程技巧来实现高性能的横向卷轴。

据我所知,几年前,机器的功能还不足以像现在那样每帧重新绘制整个屏幕。有一些技术,例如脏矩形,可以在背景静止且只有精灵移动时最小化重新绘制所需的屏幕区域。

上述方法仅在背景不变时有效(因此大多数屏幕像素保持静止)。

垂直滚动游戏,就像老式射击游戏一样,由于滚动而导致每一帧的背景发生变化,因此游戏变得更加困难。然而,人们可以利用像素馈送到显示器的方式(逐行)。我想,可以使用更大的缓冲区,并将数据指针每帧“向下”移动一些行,以便从另一个位置开始重新绘制,从而给人平滑滚动的印象。仍然只有精灵(以及屏幕边缘的一些背景)需要重新绘制,这是一个重大的优化。

然而,对于横向卷轴游戏来说,事情并不是那么简单和明显。不过,我知道有人在过去的某个地方进行了优化(有一些限制),允许旧机器水平滚动背景,而无需每帧重新绘制它。

IIRC 它被用在许多老游戏中,主要是 80 年代的节奏游戏,以及演示场景制作中。

您能描述一下这种技术并说出它的作者吗?

I have heard that old arcade side scrolling games used a specific programming hack to enable performant side scrolling.

I understand that years ago the machines weren't powerful enough to repaint the whole screen every frame as it's done nowadays. There are techniques, such as dirty rectangles, which allow to minimise the screen area needed to repaint when the background is stationary and only the sprites move.

The above approach only works when the background doesn't change (and hence most of the screen pixels remain stationary).

Vertical scrolling games, like old school shoot'em ups, have the thing a bit more difficult with the background changing every frame due to the scroll. However, one could take advantage of the way pixels are fed to the display (line-by-line). I imagine that one could use a bigger buffer and shift the data pointer some lines "down" every frame, so that it will be redrawn starting from another position, thus giving the impression of a smooth scroll. Still only sprites (and a bit of the background at the edge of the screen) would need to be redrawn, which is a serious optimisation.

However, for side scrolling games, the thing is not that simple and obvious. Still, I'm aware that somebody, somewhere in the past, has though of an optimisation which (with some limitations) allowed the old machines to scroll the background horizontally without redrawing it every frame.

IIRC it was used in many old games, mostly 80's beat'em ups, as well as in demoscene productions

Can you describe this technique and name its author?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

↙温凉少女 2024-12-22 09:43:02

我已经为老式的 C64 编写了游戏,正是这样做的。基本上有两件事需要注意:

  1. 这些游戏没有使用位图图形,而是使用“重新映射”字符字体,这意味着 8x8 像素块实际上只是一个字节。

  2. 接下来要注意的是,硬件支持将整个屏幕替换为七个像素。请注意,这不会以任何方式影响任何图形 - 它只是使发送到电视的所有内容都有点移位。

所以 2) 使得真正平滑地滚动 7 个像素成为可能。然后你移动每个字符 - 对于全屏来说正好是 1000 字节,计算机可以处理,同时你将滚动寄存器向后移动 7 个像素。 8 - 7 = 1 意味着您看起来又滚动了一个像素...然后就这样继续下去。所以 1) 和 2) 结合起来就产生了真正平滑滚动的错觉!

之后第三件事开始发挥作用:光栅中断。这意味着当电视/显示器即将开始在指定位置绘制扫描线时,CPU 会收到中断。该技术使得创建分屏成为可能,这样您就不需要滚动整个屏幕,这与我的第一个描述相反。

更详细地说:即使您不想要分屏,光栅中断无论如何也非常重要:因为它在当时和今天一样重要(但今天框架对您隐藏了这一点)以进行更新在适当的时间出现在屏幕上。当电视/显示器在可见区域的任何位置更新时修改“滚动寄存器”会导致称为“撕裂”的效果 - 您可以清楚地注意到屏幕的两个部分彼此不同步一个像素。

还有什么可说的呢?嗯,重新映射字符集的技术使得制作一些动画变得非常容易。例如,传送带、齿轮等可以通过不断改变屏幕上代表它们的“角色”的外观来制作动画。因此,只需更改字符映射中的单个字节,跨越整个屏幕宽度的传送带就可以看起来像在各处旋转。

I have written games for the good old C64 doing exactly this. And there are basically two things to be aware of:

  1. These games were NOT using bitmapped graphics, but instead used "remapped" character fonts, which means that chunks of 8x8 pixels were actually hurdled around as just one byte.

  2. The next thing to note is that there was hardware support for displacing the whole screen seven pixels. Note that this didn't in any way affect any graphics - it just made everything sent to the TV a little bit displaced.

So 2) made it possible to really smooth scroll 7 pixels away. Then you moved every character around - which for a full screen was exactly 1000 bytes, which the computer could cope with, while at the same time you moved the scrolling register back 7 pixels. 8 - 7 = 1 means that it looked like you scrolled yet another single pixel... and then it just continued that way. So 1) and 2) combined made the illusion of true smooth scrolling!

After that a third thing came into play: raster interrupts. This means that the CPU gets an interrupt when the TV/monitor was about to begin drawing a scan line at a specified location. That technique made it possible to create split screen so that you weren't required to scroll the entire screen as opposed to my first description.

And to be even more into details: even if you didn't want a split screen, the raster interrupt was very important anyway: because it was just as important then as it is today (but today the framework hides this from you) to update the screen at the right time. Modifying the "scroll register" when the TV/monitor was updating anywhere on the visible area would cause an effect called "tearing" - where you clearly notice the two parts of the screen are one pixel off sync with each other.

What more is there to say? Well, the technique with remapped character sets made it possible to do some animations very easily. For example conveyors and cog wheels and stuff could be animated by constantly changing the appearance of the "characters" representing them on screen. So a conveyor spanning the entire screen width could look as it was spinning everywhere by just changing a single byte in the character map.

待天淡蓝洁白时 2024-12-22 09:43:02

我在 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 缓冲区),但它工作得很好。

对于这个问题可能有更好的解决方案(而且肯定是今天使用的更好的解决方案),但它对我有用。

I did something similar way back in the 90s, using two different approaches.

The first one involved "windowing," which was supported by the VESA SVGA standard. Some cards implemented it correctly. Basically, if you had a frame buffer/video RAM larger than the displayable area, you could draw a large bitmap and give the system coordinates for a window within that area that you wanted to display. By changing those coordinates, you could scroll around without having to re-fill the frame buffer.

The other method relied on manipulating the BLT method used to get a completed frame into the frame buffer. Blitting a page to the frame buffer that was the same size as the screen is easy and efficient.

I found this old 286 assembler code (on a functioning 17 year old floppy!) that copied a 64000 byte (320x200) screen from an off-screen page to the video buffer:

  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;

The rep movsw moved CX words (where a word is two bytes in this case). This was very efficient since it's basically a single instruction that tells the CPU to move the whole thing as quickly as possible.

However, if you had a larger buffer (say, 1024*200 for a side scroller), you could just as easily use a nested loop, and copy a single row of pixels per loop. In the 1024-pixel wide buffer, for instance, you could copy bytes:

start          count            
0+left         320
1024+left      320 
...
255*1024+left  320

where left is the x coordinate within the large background image that you want to start at (left side of the screen).

Of course, in 16-bit mode, some magic and manipulation of segment pointers (ES, DS) was required to get a buffer larger than 64KB (in reality, multiple adjacent 64k buffers), but it worked pretty well.

There were probably better solutions to this problem (and definitely better ones to use today), but it worked for me.

你曾走过我的故事 2024-12-22 09:43:02

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

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

Arcade games frequently featured customized video chips or discrete logic to allow scrolling without the CPU having to do (much) work. The approach would be similar to what danbystrom was describing on the C-64.

Basically the graphics hardware took care of fine scrolling characters (or tiles) and the CPU then handled replacing all tiles once the scrolling registers have reached their limit. I am currently looking at the Irem m-52 board which deals with multiple scrolling backgrounds in hardware. Schematics can be found online.

窝囊感情。 2024-12-22 09:43:02

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

For right scrolling on the Commodore Amiga we used the Copper to right-shift the screen up to 16 pixels. When the screen had shifted we added 2 bytes to the start address of the screen buffer while on the right side we used the Blitter to copy graphics from the main memory to the screen buffer. We would set the screen buffer slightly larger than the screen view so that we could copy the graphics without you seeing a flickering effect from the copying on the right side of the viewport.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文