这篇废话帖子专门为老ZX Spectrum电脑用C语言开发一个游戏,来看看帅哥吧:

1982年开始生产,一直生产到1992年。该机的技术特点:8位Z80处理器,16-128kb内存和其他扩展,例如AY-3-8910声音芯片。
作为Yandex Retro Games Battle 2019竞赛的一部分,我为这台机器编写了一个游戏叫做Interceptor 2020。由于我没有时间学习Z80的汇编语言,所以我决定用C来开发它。作为工具链,我选择了一套现成的工具链—— z88dk,其中包含 C 编译器和许多辅助库,可加快 Spectrum 应用程序的执行速度。它还支持许多其他 Z80 机器,例如 MSX、德州仪器计算器。
接下来,我将描述我对计算机体系结构、z88dk 工具链的浅薄了解,并展示我如何设法实现 OOP 方法和使用设计模式。
安装功能
z88dk 的安装应该根据存储库中的手册进行,但是,对于 Ubuntu 用户,我想注意一个功能:如果您已经从 deb 包安装了 Z80 编译器,那么您应该删除它们,因为 z88dk 默认情况下将从 bin 文件夹访问它们;由于工具链编译器版本不兼容,您很可能无法编译任何内容。 /p>
你好世界
编写 Hello World 非常简单:
#include
void main()
{
printf("Hello World");
}
编译 Tap 文件更加容易:
zcc +zx -lndos -create-app -o helloworld helloworld.c
要运行,请使用任何支持 Tap 文件的 ZX Spectrum 模拟器,例如在线模拟器:
http://jsspeccy.zxdemo.org/
全屏在图片上绘图
tl;dr 图片以图块形式绘制,图块大小为 8×8 像素,图块本身内置于 Spectrum 字体中,然后将图片打印为索引中的一条线。
精灵和图块输出库 sp1 使用 UDG 输出图块。图片被转换为一组单独的 UDG(图块),然后使用索引在屏幕上组装。应该记住,UDG 用于显示文本,如果您的图片包含非常大的图块集(例如,超过 128 个图块),那么您将不得不超出该集的边界并删除默认的 Spectrum字体。为了解决这个限制,我使用了 128 – 的基数。 255,通过简化图像同时保留原始字体。关于简化下面的图片。
要绘制全屏图像,您需要使用三个实用程序来武装自己:
瘸子
img2spec
png2c-z88dk
真正的 ZX 男人、真正的复古战士有一种方法,那就是使用 Spectrum 调色板打开图形编辑器,了解图像输出的特征,手动准备并使用 png2c-z88dk 或 png2scr 上传。< /p>
更简单的方法–拍摄一张32位图像,在Gimp中将颜色数量切换为3-4,稍微编辑一下,然后将其导入到img2spec中,以免手动处理颜色限制,导出png并使用png2c-将其转换为C数组z88dk。
您应该记住,为了成功导出,每个图块不能包含超过两种颜色。
因此,您将收到一个包含唯一图块数量的 h 文件,如果超过 ~128 个,则在 Gimp 中简化图像(增加重复性)并在新图块上执行导出过程.
导出后,您可以从字面上加载图块中的“字体”,并将图块索引中的“文本”打印到屏幕上。下面是渲染“类”的示例:
// грузим шрифт в память
unsigned char *pt = fullscreenImage->tiles;
for (i = 0; i < fullscreenImage->tilesLength; i++, pt += 8) {
sp1_TileEntry(fullscreenImage->tilesBase + i, pt);
}
// ставим курсор в 0,0
sp1_SetPrintPos(&ps0, 0, 0);
// печатаем строку
sp1_PrintString(&ps0, fullscreenImage->ptiles);
在屏幕上绘制精灵
接下来我将描述一种在屏幕上绘制 16×16 像素精灵的方法。我没有开始动画和改变颜色,因为……正如我所假设的,在这个阶段我已经耗尽了内存,这是微不足道的。因此,游戏仅包含透明的单色精灵。
我们在Gimp中绘制一个单色png图像16×16,然后使用png2sp1sprite将其转换为asm汇编文件,在C代码中我们从汇编文件中声明数组,并在汇编阶段添加该文件。< /p>
在声明精灵资源阶段之后,必须将其添加到屏幕上所需的位置,下面是游戏对象“类”的代码示例:
struct sp1_ss *bubble_sprite = sp1_CreateSpr(SP1_DRAW_MASK2LB, SP1_TYPE_2BYTE, 3, 0, 0);
sp1_AddColSpr(bubble_sprite, SP1_DRAW_MASK2, SP1_TYPE_2BYTE, col2-col1, 0);
sp1_AddColSpr(bubble_sprite, SP1_DRAW_MASK2RB, SP1_TYPE_2BYTE, 0, 0);
sp1_IterateSprChar(bubble_sprite, initialiseColour);
从函数名称就可以大致明白–的含义。为精灵分配内存,添加两个 8×8 列,为精灵添加颜色。
每帧中都会指示精灵的位置:
sp1_MoveSprPix(gameObject->gameObjectSprite, Renderer_fullScreenRect, gameObject->sprite_col, gameObject->x, gameObject->y);
模拟 OOP
C 中没有 OOP 语法,如果你真的想要的话该怎么办?您需要连接您的思想并受到以下想法的启发:不存在 OOP 设备之类的东西;一切最终都属于一种机器架构,其中根本不存在对象的概念以及与之相关的其他抽象。 /p>
这个事实让我在很长一段时间里都无法理解为什么需要 OOP,如果最终一切都变成了机器代码,为什么有必要使用它。
但是,在从事产品开发之后,我发现了这种编程范式的好处,当然主要是开发灵活性、代码保护机制、采用正确的方法、减少熵、简化团队工作。所有上述好处都源于三大支柱——多态、封装、继承。
还值得注意的是解决与应用程序架构相关的问题的简化,因为 80% 的架构问题是在上个世纪由计算机科学家解决的,并在设计模式的文献中进行了描述。接下来,我将描述向 C 添加类似 OOP 的语法的方法。
以C结构体作为存储类实例数据的基础更为方便。当然,您可以使用字节缓冲区,为类、方法创建自己的结构,但为什么要重新发明轮子呢?毕竟,我们已经在重新发明语法。
类数据
游戏对象“类”数据字段示例:
struct GameObjectStruct {
struct sp1_ss *gameObjectSprite;
unsigned char *sprite_col;
unsigned char x;
unsigned char y;
unsigned char referenceCount;
unsigned char beforeHideX;
unsigned char beforeHideY;
};
typedef struct GameObjectStruct GameObject;
将我们的类保存为“GameObject.h”,在正确的位置执行#include“GameObject.h”并使用它。
类方法
考虑到 Objective-C 语言开发人员的经验,类方法的签名将是全局范围内的函数,第一个参数始终是数据结构,后面是方法参数。下面是“类”GameObject 的“方法”示例:
void GameObject_hide(GameObject *gameObject) {
gameObject->beforeHideX = gameObject->x;
gameObject->beforeHideY = gameObject->y;
gameObject->y = 200;
}
方法调用如下所示:
GameObject_hide(gameObject);
构造函数和析构函数的实现方式相同。可以将构造函数实现为分配器和字段初始值设定项,但我更喜欢单独控制对象分配和初始化。
使用内存
使用 new 和 delete 宏中包含的 malloc 和 free 来手动管理表单内存以匹配 C++:
#define new(X) (X*)malloc(sizeof(X))
#define delete(X) free(X)
对于同时被多个类使用的对象,半手动内存管理是基于引用计数实现的,类似于旧的 Objective-C Runtime ARC 机制:
void GameObject_retain(GameObject *gameObject) {
gameObject->referenceCount++;
}
void GameObject_release(GameObject *gameObject) {
gameObject->referenceCount--;
if (gameObject->referenceCount < 1) { sp1_MoveSprAbs(gameObject->gameObjectSprite, &Renderer_fullScreenRect, NULL, 0, 34, 0, 0);
sp1_DeleteSpr(gameObject->gameObjectSprite);
delete(gameObject);
}
}
因此,每个类必须使用retain声明共享对象的使用,通过release释放所有权。现代版本的 ARC 使用自动保留/释放调用。
声音!
Spectrum 有一个能够再现 1 位音乐的高音扬声器;当时的作曲家能够同时再现多达 4 个声道。
Spectrum 128k包含一个独立的声音芯片AY-3-8910,可以播放跟踪器音乐。
提供了一个用于在 z88dk 中使用高音扬声器的库
还有什么需要学习
我有兴趣熟悉 Spectrum、使用 z88dk 实现游戏并学习很多有趣的东西。我还有很多东西需要学习,例如 Z80 汇编器,因为它使我能够充分利用 Spectrum 的功能、使用内存库以及使用 AY-3-8910 声音芯片。希望明年还能参加比赛!
链接
https://rgb.yandex
https://vk.com/sinc_lair
https://www.z88dk.org/forum/
源代码
https://gitlab.com/demensdeum/ zx-projects/tree/master/interceptor2020