65.9K
CodeProject 正在变化。 阅读更多。
Home

编程怀旧:重温迈克·维林用 Pascal 编写的马里奥游戏

2023 年 5 月 8 日

CPOL

8分钟阅读

viewsIcon

6542

重温迈克·维林用 Pascal 编写的马里奥游戏

自从我第一次接触电脑键盘以来,我一直是超级马里奥游戏(及其变体)的忠实粉丝。我记得第一次在我的老式 80386 电脑上玩它,并且在第一关的中间无法通过那个沟渠。

在我成功通过沟渠并进入更高的关卡后,我发现自己无法通过第四关。

我决定放弃,直到多年后我家里有了互联网连接才再次尝试这款游戏,很快我就发现我玩的是一个未完成的版本。那时(大约在 2000 年),MS-DOS 版马里奥游戏的原始作者 Mike Wiering 已经在他的网站上发布了源代码。与我的版本在启动时直接进入第一关不同,完整版支持双人游戏(MARIO 和 LUIGI)并带有一个带有其他选项的菜单。

mariostartup

编译源代码

这款游戏无法在现代计算机上运行——它在启动时就停留在黑色屏幕上,可能是因为一些非法的 VGA 函数调用。由于缺乏 16 位兼容性和全屏支持,它也无法在 Windows Vista 及更高版本或 64 位 Windows 版本上运行。如今,如果我想玩这款游戏,DosBox 是唯一的选择。有趣的是,这款 MARIO 游戏以及 Wiering Software (Wiering Software) 的类似游戏,如 Charlie II、Charlie the Duck 或 Super Angelo,在 DosBox 中运行得很好,但在虚拟机(如 Microsoft Virtual PC、VmWare 或 Sun VirtualBox)中运行时似乎存在时序问题(速度非常快)。

凭借一些 Pascal 编程知识和空闲时间,我决定仔细研究一下源代码,以了解 Pascal 在游戏编程中的应用,本文将讨论我发现的一些有趣的事实。

我学到的第一件事是,发布的已编译可执行文件(正如源代码随附的 *README.TXT* 中所述)经过了打包,以将文件大小减小到 57KB,可能是使用了某种 MS-DOS 打包工具。编译后的可执行文件可能高达几百 KB。在那个只有 360KB 软盘的年代,这可能是一个巨大的问题。

源代码组织

源代码被很好地组织成几个 Pascal 单元文件(*.PAS*)和精灵包含文件(*.00?*)。变量命名清晰,过程结构良好。尽管由于代码从未打算公开发布,因此内联注释很少,但愿意的人仍然可以理解和修改代码。

以下是对主要源代码文件的描述

  • MARIO.PAS:主应用程序
  • WORLDS.PAS:所有关卡数据都硬编码在此
  • BACKGR.PAS:支持绘制游戏背景(如天际线)的单元
  • BLOCKS.PAS:协助绘制动画
  • BUFFERS.PAS:支持将关卡和精灵数据读入缓冲区
  • CPU286.PAS:如果检测到 286 之前的 CPU,则停止程序
  • ENEMIES.PAS:定义马里奥的敌人,如乌龟、鱼或移动的物体
  • FIGURES.PAS:定义马里奥路线上对象的行为,但不包括敌人
  • GLITTER.PASTMPOBJ.PAS:显示闪光效果,例如马里奥吃到金币或触碰物品时出现的星星
  • JOYSTICK.PAS:支持使用游戏杆
  • KEYBOARD.PAS:处理键盘输入
  • MUSIC.PAS:使用 PC 扬声器播放声音
  • PALETTES.PAS:用于绘制游戏的颜色调色板
  • PLAY.PAS:主要游戏逻辑,例如马里奥如何与敌人、物体互动、获得金币等。
  • PLAYERS.PAS:定义马里奥和路易吉的行为。
  • STARS.PAS:绘制天空中的星星
  • STATUS.PAS:游戏状态栏
  • TXT.PAS:文本处理单元
  • VGA256.PAS:自定义 Turbo Pascal VGA 单元(模式 13h,320×200,256 色)

创建精灵图

精灵图首先使用 *GRED.EXE* 创建(参见源代码中包含的 *GRED.TXT*)

它将被保存为二进制文件(*.000*,例如:*TREE.000*),然后导出到一个 Pascal 文件,如下所示

Pascal 文件名为 *TREE.$00*。如果一个精灵图有多个状态,就像动画对象一样,扩展名会递增,例如 *TREE.001* 和 *TREE.$01*。精灵图将作为包含文件包含在 *FIGURES.PAS* 中。

{$I Tree.$00} {$I Tree.$01} {$I Tree.$02} {$I Tree.$03}

关键在于将所有精灵图和关卡信息存储在程序的代码段,而不是数据段。Pascal 程序的数据段最多只能包含 64K 数据,而游戏可能会超出这个限制。如果读取速度慢(早期游戏运行在软盘上)并且游戏由多个文件组成不是问题,那么另一种方法是将数据存储为外部文件。

然后可以通过仅包含 DB 指令的过程来编写代码,以访问存储在代码段中的隐藏数据。以下代码将在指定位置绘制 TREE000

PutImage (XPos, YPos, W, H, TREE000^);

PutImage 定义在 *VGA256.PAS* 中

procedure PutImage (XPos, YPos, Width, Height: Integer; var BitMap); 

关卡数据

正如 *README.TXT* 中提到的,这款游戏没有关卡编辑器。所有关卡都硬编码在 *WORLDS.PAS* 中。

一个典型的关卡由两个过程组成,一个关卡数据文件(*Level_1a*)和一个选项文件(*Options_1a*)。与精灵图类似,它们只是包含 DB 指令来存储数据的汇编过程。选项文件将定义关卡数据的解释方式。以“intro”关卡为例,查看 `Intro_0` 和 `Options_0`,它是选择菜单后面的背景。

每个汇编指令定义屏幕的每个垂直部分。一个 DB 是一个 13 个字符的字符串。每个字符定义屏幕上的一个对象,从下到上。字符 0 标记关卡的结束(例如 `DB 0`)。如果关卡选项不同,同一个字符在不同关卡中可能会被解释得不同——请参阅 *FIGURES.PAS* 中的 `ReDraw()` 函数。所有关卡数据都将使用 `ReadWorld()` 加载到变量 `WorldMap`(在 *BUFFERS.PAS* 中找到)。有些关卡可能有特定的管道,马里奥可以潜入其中进入不同的区域——这些被定义为子关卡,例如,请参见 `Level_1b`。

现代方法

有了一些空闲时间,我决定尝试一下,看看如何重新利用关卡数据来显示每个关卡的概览,而无需从头开始重写所有内容。我的第一个任务是将精灵图保存为图像文件,这很容易,因为 GRED 文件格式有文档。很快我就用 VB.NET 编写了一个工具,可以加载精灵图二进制文件并在 PictureBox 中显示。

然后所有精灵图都将被转换为 VB.NET 资源。下一个挑战是导出关卡数据。基于 *WORLDS.PAS* 中的 `PlayWorld` 函数,我编写了我的 *LVL2BIN.PAS*,它将所有关卡数据导出到一个二进制文件(*.LVL*)。

用法如下

WriteLevelToBin(@Level_1a^, ‘Level1a.LVL’);

对于关卡选项,为了方便修改,我没有将其导出为二进制文件,而是将其转换为 XML 文件。

大部分代码来自 *BUFFERS.PAS* 中的 `ReadWorld()` 函数。我将其改编到 VB.NET 中,并做了一些小的修改,以适应 VB.NET 中的零基数组索引(Pascal 支持非零基数组)以及 VB.NET 中缺少集合数据类型

var Ch: Set of Char;

….

Ch = [C] + [#1 .. #13];

例如,以下简单的 Pascal 代码

Dim Ch As New List(Of Char)

Ch.Add(C)

For i As Integer = 1 To 13

If Not Ch.Contains(Chr(i)) Then Ch.Add(Chr(i))

Next

在 VB.NET 中变得复杂,并且可能成本更高——必须使用一个List来模拟一个集合。

下面显示了关卡查看器在运行中的效果,它读取关卡数据(*.LVL*)和选项(*.XML*)并在屏幕上显示。

(为简单起见,敌人和背景尚未绘制。)

在我编写代码时,有一件事让我感到惊讶。尽管关卡 0(intro)和关卡 2 的砖块看起来不同(见下图),但它们实际上来自同一个精灵图(*BRICK0.000*)。

procedure ReColor (P1, P2: Pointer; C: Byte);

procedure ReColor2 (P1, P2: Pointer; C1, C2: Byte);

技巧在于 *FIGURES.PAS* 中的以下两个函数

以上看似复杂的汇编代码的作用是遍历图像中的每个像素,并将其颜色分别修改 1(对于 `ReColor`)或 2(对于 `ReColor2`)的常量,以使新图像看起来不同。这使得同一个图像可以用于两个不同的关卡,但看起来仍然不同。我将它们都转换到了 VB.NET。

Private Function ReColor(ByVal fig As Bitmap, ByVal factor As Integer) As Bitmap

Private Function ReColor2(ByVal fig As Bitmap, _
ByVal factor1 As Integer, ByVal factor2 As Integer) As Bitmap

然而,尽管使用了完全相同的常量和相同的调色板,我生成的关卡 2 的显示结果并不完全相同。

我无法找到确切的问题所在,只能推测是因为我可能忽略了某些东西。

此时,如果我想继续,就可以绘制背景、动画精灵图(乌龟、鱼……)并实现完整的游戏。

彩蛋

游戏中有一些彩蛋可以在 *PLAY.PAS* 中找到。要激活秘籍模式,请按 **P** 暂停游戏,然后按 **TAB**。在秘籍模式下输入 2305 可以让你进入下一关。另外,如果你想玩灰度模式,请按 **MONO**。

类似游戏:Charlie the Duck、Charlie II 和 Super Angelo

根据 Wiering Software 的说法,这三款游戏都是基于原始马里奥源代码开发的;然而,它们的源代码从未发布。Charlie II(我最喜欢的游戏)还有一个 Windows 版本,可能是用 C++ 编写的。截至 2011 年,很明显这些游戏将不再有 DOS 版本,任何未来的版本可能只会是 Flash 格式。如果你想尝试,请点击这里这里

我希望这篇文章能对那些想将游戏移植到其他平台的人,或者想学习 Pascal 游戏开发的人有所帮助。

用于转换精灵图和查看关卡的完整 .NET 源代码可以在这里下载。

另请参阅

参考文献

  1. 开放游戏源代码:马里奥克隆
© . All rights reserved.