首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

用 ESP32 制作《俄罗斯方块》掌上游戏机

用 ESP32 制作《俄罗斯方块》掌上游戏机

1984 年,一位名叫阿列克谢·帕基特诺夫(Alexey Pajitnov) 的工程师,在当时的苏联科学院计算机中心工作。他的工作任务是测试新硬件设备的兼容性,相比单位分配的正式任务,更吸引他的是研究如何使用计算机进行娱乐。为此,帕基特诺夫开始尝试在计算机上开发一些简单的游戏,经过一段时间的尝试后,他从一款拼图游戏上获得了灵感,考虑让不同形状的图形依次下落,在矩形底部堆叠起来,使之排列成完整的一行后消除。在另外两位同伴的协助下,帕基特诺夫在Electronika 60 计算机上完成了这款名为“Tetris”(俄语:Тетрис)的游戏,因为这个型号的计算机没有图形接口,所以游戏只使用空格和实心方块来表示形状(见图 1)。

图 1  第一版《俄罗斯方块》游戏,运行在 Electronika 60 上

根据另一位当事人的回忆,“Tetris”这个单词是阿列克谢自己发明并坚持使用的,来自反映俄罗斯方块图案基本结构的“四”(希腊语:tetra)和阿列克谢自己最喜爱的运动“网球”(tennis)的组合。今天我们更习惯称呼这个游戏为《俄罗斯方块》。

1985 年,《俄罗斯方块》的开发者之一 ——瓦丁·格拉西莫夫在 MS-DOS 下移植了《俄罗斯方块》(见图 2),伴随着 PC 的推广,游戏得以迅速普及。

图 2  1986 年 IBM PC 版本的《俄罗斯方块》

对于“80 后”来说,图 3 所示的《俄罗斯方块》掌上游戏机承载了很多快乐的回忆。

图 3 《俄罗斯方块》掌上游戏机

硬件设计

我这次使用ESP32 搭配 ILI9341TFT LCD 来制作一个《俄罗斯方块》掌上游戏机,选择的硬件如附表所示。

硬件中最为复杂的是 ILI9341 TFT 显示屏,它使用 SPI 接口进行通信。丝印上的 SCL 和 SDA 分别是 CLK 和 MOSI(显示屏可以看作一个单纯的输出设备,因此MISO 是可以省略的);RES 用于显示屏复位,在初始化显示屏时是必需的;DC用于通知显示屏当前传输的是数据(Data)还是命令(Command);CS 是 SPI 接口用于片选的信号,如果当前 SPI 总线上还有其他设备,可以用于选中需要的通信设备;BLK 用于控制显示屏背光是否开启,在这个项目中没有关闭显示屏的需求,因此直接悬空,显示屏会一直保持背光打开的状态。

图 4  ILI9341 TFT 显示屏

确定使用的硬件之后,就可以开始着手设计电路了,我使用立创 EDA 绘制电路图和 PCB,结果如图 5、图 6 所示。可以看到电路很简单,除了前面提到的 TFT 显示屏就是 PS/2 摇杆,后者输出 2 路模拟信号和一个数字信号,分别是x 轴和y 轴坐标、按键信号。

图 5  使用立创 EDA 绘制的电路图

图 6  PCB 图

立创 EDA 提供在线预览工具,PCB 绘制完成后可以直接查看最终效果(见图 7)。

图 7  预览 PCB 效果

软件设计

硬件设计完成后即可进行软件设计,相比硬件,软件设计要复杂得多。代码整体可以看作两部分,一部分是在 screen[Width][Height]数组中计算界面的形态, 代码在Get Next PosRot() 和 Revise Screen() 函数中;另外一部分是通过 ILI9341 TFT 显示屏将前面的数组显示出来,代码在 Draw() 函数中。这样设计的好处是逻辑和硬件分离,便于调试和移植到不同显示接口的设备上。

代码中最重要的数据结构是关于形状的存放。游戏中共有 7 种形状(见图 8),可以看到每种形状都由 4 个方块组成,因此可以用方块的坐标表示形状。

图 8  游戏中的 7 种形状

比如,图 8 所示的这个形状,以记录坐标的方式,可用 {{-1,1},, ,} 来表示。

图 8  形状和对应的坐标

当它旋转后, 变成图9所示的形状,以记录坐标的方式,可表示为 {,, ,}。

图 9  旋转后的形状和对应的坐标

具体到代码中,我用 blocks[ ] 数组记录了每一种形状的全部变换结果,旋转就是指向下一个内容。例如 blocks[ ] 数组中的一个形状定义如下:

{{{{-1,0},, ,},{, ,,},{,,,}, {,,,}},2,1},

其中末尾处的深绿色1表示这个形状的颜色索引,浅绿色2表示这个形状有 2 个形态。{{-1,0},, ,}对应图10所示的形态,其中的 x 表示 (0,0) 这个原点。

图 10  形状的形态 1

{, ,,}对应图 11 所示的形态。

图 11  形状的形态 2

了解了上面的数据结构,就不难理解GetNextPosRot() 函数了。这个函数的功能是放置新生成形状、响应按键动作(比如,左右移动和旋转)和触发下落。游戏的难度不同体现为图形不同的下降速度。游戏根据当前积分的不同使用不同的下降速度,具体代码在 GetNextPosRot() 函数中。

Score2Level() 函数返回当前分数对应的延时,当前分数越高,下落速度越快。

ReviseScreen() 函数处理下落的过程,此外计分功能也在该函数中:在消除行时,根据一次性消除的行数计算分数,具体在 DeleteLine() 函数中。每次获得的分数 = 消除层数的平方。

Draw() 函数将前面 screen[ ] 数组中的界面形态展现在 ILI9341 TFT 显示屏上。为了驱动这个显示屏,我选用了 Adafruit_GFX 显示库;注意这个库对 ESP32 有兼容性问题,编译时会出现错误,可以修改\Adafruit_BusIO\Adafruit_SPIDevice.cpp 这个文件解决。

为了便于使用,我在 IL9341 的库中增加了一个填充图像的函数。

此外,《俄罗斯方块》还要具有显示“下一个图形”的功能。为此,我在 Draw()函数中实现了对应的绘制“下一个图形”的功能。这里有特别需要注意的地方,比如, 我们定 义了一个 image[20][20] 图像,但是想将它里面 10×20 的内容显示出来,是不可以直接使用 tft.fillImage() 的,会出现混乱,因为这个函数会默认将显示Buffer 认为是 20×20 大小的。解决方法是创建另外的 Buffer,先把数据存进去,再使用 fillImage() 函数绘制。

装配电路、烧写程序后,你就可以开启游戏之旅了!

正版《俄罗斯方块》发售了 1.25 亿份,受到 50 多个国家和地区的玩家喜爱,有超过50 种语言的版本,运行在街机、家用游戏机、掌上游戏机、PC、手机等几十种游戏平台上。历经近 40 年,小方块的魅力经久不衰,《俄罗斯方块》游戏可以称得上是传奇。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20221117A091Z200?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

相关快讯

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券