我如何将 BBC Micro Elite 改造为双人游戏

Lobsters Hottest 新闻

摘要

本文详细介绍了破解经典 BBC Micro 游戏 Elite 以支持双人游玩的技术过程,涉及对 6502 汇编代码的修改以及分屏视图几何结构的解释。

<p><a href="https://lobste.rs/s/el3fvf/how_i_converted_bbc_micro_elite_into_two">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/09/03 20:07

# 双人精英版技术细节——6502处理器上的精英游戏 来源:https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html ## 我如何将BBC Micro版精英游戏改为双人游戏 在底层设计中,BBC Micro版精英游戏固执地采用单人模式。这完全合理,因为游戏最初就是如此设计的,但这意味着任何试图从贝尔与布拉本(Bell and Braben)的优雅代码中挖掘出第二玩家的人都会面临一些挑战。本文描述了我构建双人精英版的过程。内容较多,因此我将其分为若干章节,按照我解决这些问题的顺序依次展开。您可以从头到尾阅读,也可以直接点击链接跳转到相关章节: - 双人精英版的工作原理概述 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#overview) - 关于基于位的标志位说明 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#bitflags) - 分割太空视图 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#splitview) - 绘制玩家1的太空视图 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#player1) - 绘制玩家2的太空视图 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#player2) - 玩家2视图背后的几何原理 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#geometry) - 从方向向量的角度 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#orvect) - 从旋转矩阵的角度 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#rotmat) - 玩家2视图背后的算术运算 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#arithmetic) - 目标计算 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#targets) - 在太阳和行星方面的“作弊” (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#sunplanet) - 双人玩家的响应式控制 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#controls) - 其他杂项 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#misc) 与我所有的修改版一样,双人精英版是在原始游戏源代码基础上进行修改的;它不是全新的游戏,而是基于原版的修改版。具体到双人精英版,原版游戏是精英的6502第二处理器版本 (https://elite.bbcelite.com/6502sp/),并且已经应用了无闪烁修改版 (https://elite.bbcelite.com/hacks/flicker-free_elite/)。在此基础上,我进一步添加了为支持第二玩家所需的所有修改。 您可以在该项目的GitHub仓库 (https://github.com/markmoxon/elite-two-player-6502-second-processor) 中查看所有相关代码。如果在源代码中搜索“Mod:”,您将找到我为将原版6502第二处理器版精英游戏改造为双人版所做的所有更改。这些更改根据需要分布在宿主处理器 (https://github.com/markmoxon/elite-two-player-6502-second-processor/blob/main/1-source-files/main-sources/elite-source.asm) 和I/O处理器 (https://github.com/markmoxon/elite-two-player-6502-second-processor/blob/main/1-source-files/main-sources/elite-z.asm) 的源代码中,并且每一处更改都与原版游戏其他源代码具有同等详细程度的文档说明(换句话说,每一行代码都得到了详细解释)。 希望您能像我享受将其变为现实的过程一样,享受阅读关于双人精英版的内容。 ## 双人精英版工作原理概述 在8位精英游戏的核心中,有一个巧妙的设计,使得任何处理游戏代码的人工作起来都大大简化。这就是:玩家始终处于宇宙的中心。例如,当玩家向前移动时,他们实际上并未移动;相反,局部气泡中的所有其他事物都在向后移动。而当玩家向右滚动时,他们实际上并未旋转;相反,局部气泡中的所有事物都在围绕他们向左旋转。您可以在关于旋转宇宙 (https://elite.bbcelite.com/deep_dives/rotating_the_universe.html) 的深入探讨中阅读所有相关内容,这对理解双人精英版至关重要。 始终将玩家置于原点,使得游戏代码的某些方面变得简单得多。例如,判断敌方飞船是否在玩家的激光瞄准范围内几乎是微不足道的;我们首先检查敌人是否在玩家前方(即敌人的z坐标z > 0),如果是,再检查它是否足够靠近z轴,位于敌舰的可瞄准区域内(即敌人的x和y坐标满足x² + y² < t,其中t来自敌舰蓝图的可瞄准区域值)。由于z轴笔直指向玩家飞船的前方,从船头直接沿着激光线延伸,因此我们不需要关心旋转或方向,只需做一些简单的乘法运算,结果就非常精确。您可以在关于处于十字准线内 (https://elite.bbcelite.com/deep_dives/in_the_crosshairs.html) 的深入探讨中阅读所有相关内容。 不幸的是,这个巧妙设计使得在精英游戏中添加第二个玩家变得相当困难。如果整个宇宙以一个玩家为中心,它就不能同时以另一个玩家为中心,那么我们如何解决这个问题呢?一种方法可能是运行两个独立的精英游戏实例,每艘飞船一个,让它们以某种方式相互通信;本质上是在同一台机器内构建一个联网版的精英游戏。这可能行得通,但需要大量内存和大量CPU资源,而且很快就会变得非常复杂。 相反,双人精英版完全拥抱了原版游戏核心中以玩家为中心的设计,并在这个久经考验的游戏引擎上有效地附加了第二个玩家。让我们考虑游戏内的双人精英版屏幕: [双人精英版截图] 局部气泡仍然以一个玩家为中心;我们称他们为玩家1,上方的太空视图显示的是玩家1驾驶舱的视角。从非常真实的意义上说,上方视图是完全标准的精英游戏,只是裁剪到了屏幕的较小部分 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#splitview)。当玩家1旋转或加速时,他们并未移动,而是整个局部气泡围绕他们移动;上方太空视图在几乎所有重要的方面都是正常的精英游戏。 那么,玩家2只是作为另一个飞船被生成在玩家1的局部气泡中。玩家2并非处于宇宙中心;相反,他们就像原版游戏中的NPC飞船一样,所以当玩家1旋转或移动时,整个气泡——包括玩家2的飞船和附近的任何其他物体(如行星或太阳)——会反方向旋转和移动,就像原版中所有非玩家飞船一样。区别在于,玩家2的飞船不是由游戏的战术例程控制的;相反,它由玩家2的操纵杆和键盘上的副飞行键控制。因此,当人类玩家2转动操纵杆或按下“加速”按钮时,玩家2的飞船会在玩家1的局部气泡内旋转或移动。玩家2的飞船不会围绕自身旋转宇宙,它只是像预期的那样在太空中移动。这并不太难理解——毕竟,原版游戏中的NPC飞船就是这样工作的,因此通过操纵杆而不是算法来移动NPC飞船并不难想象。 确实,在双人精英版中,您可以将玩家2配置为AI飞行员,在这种情况下,控制玩家2飞船的代码会切换回原始的战术例程,而不是读取操纵杆和键盘。因此,我们有一个熟悉的模型:一个以玩家1为中心的宇宙,其中包含行星、太阳、玩家2的飞船以及最多两枚导弹。玩家1的太空视图很容易绘制,因为玩家2只是局部气泡中的另一艘飞船,所以我们可以像平常一样绘制上方太空视图(只是根据需要进行了裁剪)。 这定义了双人精英版修改版的主要挑战:我们如何将所有这些内容绘制到底部太空视图中,使得一切从玩家2的角度看都是正确的?这正是复杂性所在,因此我将答案分为若干部分: - 我们首先讨论如何绘制玩家1的视图,这让我们有机会回顾单人精英版的工作原理。详见关于分割太空视图 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#splitview) 和绘制玩家1太空视图 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#player1) 的章节。 - 为了绘制玩家2的视图,我们首先在局部气泡中创建太阳、行星、飞船和导弹的副本。这在绘制玩家2太空视图 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#player2) 的章节中描述。 - 获得副本后,我们需要将每个副本从玩家1的参考系(即玩家1位于原点,坐标轴与玩家1飞船对齐)变换到玩家2的参考系(即玩家2位于原点,坐标轴与玩家2飞船对齐)。这是双人精英版的核心部分,在关于玩家2视图背后的几何原理 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#geometry) 和算术运算 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#arithmetic) 的章节中有详细说明。 - 太空狗战中一个相当关键的方面是锁定并射击对手的能力。如上所述,这对玩家来说相当容易,但对玩家来说则更为复杂,因此这在目标计算 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#targets) 章节中解释。 - 最后,事情并非表面看起来那样...当然了。在8位CPU上处理浮点几何时,准确性和速度是持续的挑战,我承认使用了一些“障眼法”让事情看起来比实际更稳定。我在关于在太阳和行星方面的“作弊” (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#sunplanet) 章节中讨论了这些。 还有一些章节涉及更直接的方面,例如响应式控制系统 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#controls),最后还有一个杂项 (https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html#misc) 章节用于其他值得讨论的内容。 让我们从看看如何将太空视图一分为二开始,这是我着手的第一个任务……但在我们深入之前,先快速说明一下我在双人精英版中使用的基于位的标志位。 ## 关于基于位的标志位的说明 双人精英版包含许多新变量,其中很多都是标志位。例如,分屏效果由以下标志控制,我们将在下一节中查看: - splitScreen 控制分屏效果: - 位7置1 = 将太空视图绘制为分屏 - 位7清零 = 全屏 您会注意到此变量使用位7来存储其状态。这种方法在精英游戏中并不常见——贝尔和布拉本的代码倾向于使用值为零或非零的标志,可以通过LDA和BEQ组合进行测试。但在我自己的编程中,我受到杰夫·克兰蒙德(Geoff Crammond)的更多影响,他在《Aviator》、《Revs》和《The Sentinel》的过程中,逐渐使用位7作为标志位(有时还包括位6)。 使用位7的好处是,我们可以在不使用任何寄存器的情况下测试标志的状态,因为BIT指令会将操作数的位7和位6分别设置到N和V标志中。因此,与其像这样测试标志变量是否非零: ```assembly LDA flagVariable \ 将A设为标志变量的值 BNE flagIsSet \ 如果A非零则跳转到flagIsSet ``` 或像这样测试是否为零: ```assembly LDA flagVariable \ 将A设为标志变量的值 BEQ flagIsNotSet \ 如果A为零则跳转到flagIsNotSet ``` 我们可以这样做来测试位7是否置1,而无需使用寄存器: ```assembly BIT flagVariable \ 将标志变量的位7设置到N标志中 BMI flagIsSet \ 如果N标志置1则跳转到flagIsSet ``` 或者我们可以这样做来测试位7是否清零: ```assembly BIT flagVariable \ 将标志变量的位7设置到N标志中 BPL flagIsNotSet \ 如果N标志清零则跳转到flagIsNotSet ``` 类似地,我们可以这样做来测试位6是否置1: ```assembly BIT flagVariable \ 将标志变量的位6设置到V标志中 BVS flagIsSet \ 如果V标志置1则跳转到flagIsSet ``` 或者这样来测试位6是否清零: ```assembly BIT flagVariable \ 将标志变量的位6设置到V标志中 BVC flagIsNotSet \ 如果V标志清零则跳转到flagIsNotSet ``` 不仅如此,我们还可以在不使用寄存器的情况下设置位7,像这样: ```assembly SEC \ 设置C标志 ROR flagVariable \ 将C标志旋转到标志变量的位7中 ``` 我们可以通过类似方式清除它: ```assembly CLC \ 清除C标志 ROR flagVariable \ 将C标志旋转到标志变量的位7中 ``` 或者,如果我们只使用标志变量的位7,并且知道其他位是清零的(特别是位6),我们可以这样做: ```assembly ASL flagVariable \ 将位6(清零)移入标志变量的位7中 ``` 并且由于我们在运行主游戏代码的6502第二处理器上,使用了65C02 CPU的额外指令,我们只需一条指令即可清除两个标志,像这样: ```assembly STZ flagVariable \ 清除标志变量的位6和位7 ``` 您还可以通过右移操作使用这些标志变量存储先前的标志值(例如,使位6继承位7的先前值),但这已经深入到了杰夫·克兰蒙德的领域。对于双人精英版,我只将位6和位7作为独立标志使用,因此您将在整个代码修改中看到这类标志逻辑。 ## 分割太空视图 与双人精英版中的其他一些挑战相比,将太空视图分成两部分相对简单。有两个新变量控制分屏过程,其工作原理如下: - splitScreen 控制分屏效果: - 位7置1 = 将太空视图绘制为分屏 - 位7清零 = 全屏 - drawPlayerView 确定在分屏中绘制哪个玩家的视图: - 位7置1 = 绘制玩家1的视图(上方) - 位7清零 = 绘制玩家2的视图(下方) 有了这两个标志变量,我们可以更新任何绘图计算,以便在屏幕的正确半部分进行绘制。我们只需要在splitScreen的位7置1的情况下更改绘图例程,此时我们需要根据drawPlayerView的值,更新我们正在绘制的像素的y坐标,以指向正确的太空视图。 为了简化此操作,我们可以使用原始源代码中的配置变量Y,该变量被设置为太空视图高度的一半(因此对于BBC Micro,Y设置为96像素,因为完整的太空视图高192像素);我将称此变量为#Y,以避免与6502的Y寄存器混淆。因此,在以下屏幕截图中,每个太空视图的高度为#Y像素,仪表板的顶部位于y坐标#Y*2处: [屏幕分割示意图]

相似文章

BBC Tetris 伴侣

Hacker News Top

一位爱好者描述了一个正在进行的项目,为 BBC Bridge Companion(一款 1985 年基于 Z80 的游戏机)编写 Tetris 游戏,包括硬件规格和背景。

模拟器调试:Area 5150 的 Lake Effect

Lobsters Hottest

本文详细介绍了在MartyPC模拟器上调试Area5150演示中“Lake”效应的过程,解释了需要特定标题hack的原因,以及通过总线嗅探和动态时钟实现周期精确CGA模拟的后续修复方法。

Five monitors on a Commodore 128 [video]

Hacker News Top

A retrocomputing experiment driving five independent monitors from a Commodore 128 by splitting RGBI signals with a custom circuit board, along with similar tests on IBM CGA/EGA systems.