数字逻辑课上如何制作FPGA游戏?

数字逻辑课上如何制作FPGA游戏?以超级马里奥为例子讲述FPGA有些制作。

1、FPGA游戏目标

没有CPU,单纯用 FPGA 的verilog硬件语言来实现一个游戏,而这个游戏还得符合老师要求,由于没有 CPU,游戏逻辑不能太复杂,画面也不能太复杂?想存储图片但是Rom与Ram又那么小,但是要够好玩,要有彩色画面,最好有动画,这不是既像马儿跑又想马儿不吃草么?

2、最早期的游戏是怎么自作的?

最早FC时代的《 超级马里奥 》剧情并不是很长,一共也就 8 个场景线性布置关卡,但 8 个关卡在当时看来也算得上是精心制作了,可它真的只有 64 KB(下面的这张图可能都不止164kb),当时卡带游戏也没有cpu,没有多余的存储空间我们看看是怎么制作的?
超级马里奥
最早早的 2D 游戏由贴图 + 代码 + 音效组成,代码其实一点都不占地儿,说白了就是一堆英文 + 数字 + 标点符号的文本文件。
但是在八个场景,怎么实现的呢?
会出现的素材提前准备好,然后运行游戏的时候把素材拼接起来就行,这一个个素材叫 Tile ,整个素材包叫做 Tile Map 。
这样一来,上面展示的那长长的第一关场景就不用提前搭建好再存储了,而是你在玩的时候重复利用提前准备好的东西。
FC 版本的《 超级马里奥兄弟 》看似有这么多关卡,其实出现过的东西就这么多
超级玛丽
在红白机时代的游戏,只要素材读取好了,运行的时候用代码告诉计算机这块素材应该出现在哪个位置就行了。。。代码占地儿小啊,写起来不心疼!
超级玛丽
如此一来只需要在生成草垛的时候借云朵过来,加一个颜色的信息,就行了。
只不过这些芯片能合成的音色种类有限,往往听上去都大同小异,但也颇有风味,被称作 Chiptune —— 芯片音乐或者是 8-bit 音乐,现在还有些音乐人在玩儿这个风格。

音乐小格式 + 专用芯片,FC 时代的老游戏音乐往往不长,就是几个旋律来回循环,这么一来游戏音效占地也就不大了。

3、FPGA制作超级马里奥

3.1FC游戏的架构

架构

3.2VGA同步单元

要想进行视频输出,最底层的模块就是 VGA 同步单元,负责向显示器输出同步信号和像素信息。
VGA描述
VGA 的扫描方式和 CRT 显示器一样,有一条扫描线从左到右,从上到下地扫描每一个像素点,显示器则在收到同步信号之后点亮对应的像素点到指定的颜色。VGA 控制器需要发出 HS 信号和 VS 信号对显示器进行同步,然而,并不是每一个扫描点都可以显示像素,上面图右下角展示了这一部分,HS与VS信号也是在板子说明书上给出了(看下图)
VGA
在上上图可以看到在水平方向的左右,各有一段 “Porch” 时间,这个时间在一行扫描完成之后 CRT 显示器稳定电压的时间,因为 CRT 是通过控制电压来控制电子偏转的,电压的下降(也就是回到最左边)和稳定需要一点时间,在电压不稳定的情况下,不能输出像素信息;类似的,最上面和最下面也各有一段 “Porch” 时间,具体的时间和时钟周期由时钟频率和帧率还有分辨率共同决定,下面给出 640 x 480 @ 60Hz 的时钟信息:
在这里插入图片描述
到这里,只需要按照表格信息编写 Verilog 程序就好了。同时,这个模块需要输出当前扫描的像素点的 x 坐标和 y 坐标。

3.3图像

Nexcy3 的 VGA 接口对 RGB 每个分量分别提供 3 位输出,也就是 9 比特颜色位深(懒得计算到底是多少具体给了多少位输出自己看吧)如果要将像素信息放到显存中则需要 的位数会很大很显然这是远远不够的。所以我们需要根据当前 VGA 的像素坐标实时计算出像素点的颜色信息,事实上,在内存相当吃紧的年代,FC 也是这么做的,而 FC 有专门的 PPU (Picture Processing Unit) 进行相当复杂的像素操作,虽然不能直接实现一个 PPU,但是其中不少设计思想还是可以借鉴的。

3.3.1ROM与RAM

确定了存储方式之后,就可以编写 ROM 和 RAM 代码,这个你们课堂上也学过如何写,只要挪用即可, Vivado 也有自带的模板代码,ROM你们也可以直接声明使用。要将数据在综合时读取到寄存器中的话,要用到 $readmemh 这个系统指令,具体使用自己可以探究。

3.3.2Background图

一般而言,游戏机会有两个图层,一个是背景图层,用来显示大块的图片,但不能自由移动,在内存中只存储下面的信息:

第八位 第七位 第六位 5:3 2:0
启用 上下翻转 左右翻转 Tile行 Tile列

这里 Tile 指的就是在背景 ROM 中存储的图像块,长宽固定。那么我们怎么知道当前的块要显示在什么地方呢?这由内存信息的地址决定,按行存储,直到铺满画面为止。这样就可以节省大量的空间,因为不用存储坐标信息了

3.3.3活动图

另一个图层就是活动图层,一般用来显示需要灵活移动的图像块,比如游戏的主角与金币什么的,这时候我们就要存储具体的坐标信息了,占用的位数会更多
同上,我就不具体写了,一般要32位存储信息。
读取 ROM 的方式其实就是通过 Tile 行列信息和坐标信息,计算出在 ROM 中的地址,实现图像数据的存取。计算的方式很简单:如果当前坐标还没到达图像块的显示范围,就不读取;到达范围之后,读取的地址就是当前的 x, y 坐标减去图像块的 x, y 坐标,再加上 Tile 行列对应的偏移即可。

3.3.4图层融合

两个图层需要在渲染的时候融合起来,每个图层的引擎需要负责输出当前的像素是否为透明的信息,然后由上层模块通过这个信息和遮挡关系计算出最终显示的像素信息,这样需要我们确定一个透明色,当引擎遇到这个颜色的时候就认为当前像素是透明的。

3.4游戏逻辑

3.4.1人物马里奥

这个游戏其实只用到一个上键,每当我们按键的时候,需要将马里奥的状态设置成“上升”,并切换人物动画,然后给定一个初始计数器值,每当计数器到达零,就让马里奥上升一个像素,然后给一个大一点的计数器值,这样就实现了有重力的上升,直到计数器值达到最大设定值为止,此时把状态设置成“下降”,马里奥开始掉落,同样给一个初始计数器值,不过这个计数器值是慢慢变小的,就是实现了加速下落的效果。马里奥模块需要告诉上层模块当前的位置,方便碰撞检测。

3.4.2障碍物

首先确定障碍物信息如何记录,需要存储的信息有:障碍物左端的 X 坐标,障碍物上端的结束 Y 位置,下端的开始 Y 位置,全部是以背景图像块作为索引。游戏中障碍去的数量其实是恒定的,按照一定周期向左移动,当检测到一个水管到达了画面最左端,就重置信息到最右边,并随机给两端开始结束赋值。游戏引擎根据水管信息向背景内存写入图像信息。

3.4.3其他的金币得分分数统计等等效果自己可以考虑加进去

实现滚动

前面提到,背景图层是整整齐齐按照图像块的方式排列的,那么要怎么滚动呢?方法是设置一个 变量来对整个画面进行向左偏移,只需要将其加在传给背景引擎的 x 值上就能达到目的。这个变量的最大值应该是 Tile 的宽度减 1,当这个值达到最大的时候,我们需要更新所有障碍物的 X 坐标,使其减 1,这样就可以造成障碍物连续向左滚动的效果了!

4、游戏数据的读写控制

1,简单粗暴的利用时钟
2,使用数据总线来控制

5、参考代码

我在xlinx官网上选了一个2048游戏的代码给你们参考,选的开发板型号不一样(为了防止你们copy代码)经过测试无bug的,VGA等接口有区别,因此你们别copy,在大作业你们也别copy2048游戏,这个因为已经有完整的代码给你们参考了,所以作业中不能出现2048的游戏,也希望跳出超级马里奥的思维,发挥你们想象力,取得好的成绩。
我写的匆忙,大体上的实现逻辑,细节上可能有很多不足的地方,希望你们不拘小节,耗自为汁。

猜你喜欢

转载自blog.csdn.net/weixin_39940536/article/details/110732485