拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于ESP32与Arduino IDE复刻经典小恐龙游戏:从硬件选型到游戏引擎实现

1. 项目缘起当经典游戏遇上开源硬件相信不少朋友都见过那个经典的“断网小恐龙”——当Chrome浏览器检测到网络连接断开时那只像素风的霸王龙就会在屏幕上奔跑跳跃躲避仙人掌和翼龙。这个游戏以其极简的设计和令人上瘾的玩法成为了一个互联网时代的文化符号。最近我手头正好有一块Magicbit开发板这是一款基于ESP32的微型创客开发板集成了显示屏、按键、传感器等多种外设非常适合用来做嵌入式原型开发。一个念头突然冒出来能不能用这块小小的开发板把这个经典的小恐龙游戏复刻出来做成一个可以握在手里玩的独立设备这个想法听起来简单但实际操作起来却是一个典型的嵌入式系统综合项目。它涉及到图形界面的渲染、游戏逻辑的编写、用户输入的响应以及硬件资源的调度。对于刚接触Arduino和ESP32的朋友来说这是一个绝佳的练手项目能让你一次性把GPIO控制、屏幕驱动、状态机编程、性能优化等多个知识点串起来。而对于有经验的开发者如何在一块资源有限的微控制器上流畅地运行一个动态游戏也是一个有趣的挑战。今天我就来分享一下我是如何用Magicbit和Arduino IDE一步步把这个想法变成现实的。2. 硬件选型与开发环境搭建2.1 为什么是Magicbit在开始敲代码之前我们先聊聊硬件。市面上基于ESP32的开发板很多比如NodeMCU、Wemos D1 Mini等为什么我选择了Magicbit原因主要有三点。第一是集成度高开箱即用。Magicbit最大的特点就是“All in One”。它板载了一块1.3英寸的IPS彩色显示屏ST7789驱动、一个蜂鸣器、两个物理按键、一个光线传感器、一个红外接收头甚至还有锂电池充电管理电路。这意味着我们想做这个小恐龙游戏几乎不需要焊接任何额外的元件。屏幕用来显示游戏画面两个按键分别对应“跳跃”和“下蹲”或者一个跳跃另一个作为开始/重启蜂鸣器可以播放简单的音效光线传感器未来甚至可以用来调节屏幕亮度。这种高度集成性极大地简化了硬件连接让我们可以专注于软件逻辑。第二是性能足够。Magicbit的核心是ESP32这是一款双核、主频高达240MHz的微控制器内置520KB SRAM和4MB Flash。对于渲染一个128x160分辨率、色彩深度为16位的游戏画面以及运行相对复杂的碰撞检测和物理模拟逻辑这个性能是绰绰有余的。相比之下如果使用传统的Arduino UnoATmega328P16MHz2KB SRAM光是驱动彩色屏幕和存储图像数据就可能捉襟见肘。第三是社区与生态。Magicbit有相对完善的文档和社区支持其Arduino库对板载外设的封装做得不错降低了入门门槛。同时ESP32庞大的生态意味着你在遇到问题时有海量的资料和案例可以参考。2.2 Arduino IDE的“正确打开方式”确定了硬件接下来就是搭建开发环境。从热搜词可以看出大家最关心的就是Arduino IDE。这里我强烈建议无论你是新手还是老手都直接使用最新的Arduino IDE 2.x版本目前最新稳定版是2.3.10。与经典的1.8.x版本相比2.x版本在代码自动补全、语法高亮、串口监视器等方面有了质的飞跃开发体验好很多。第一步安装Arduino IDE 2.x。直接从Arduino官网下载对应操作系统的安装包。安装过程很简单一路下一步即可。安装完成后打开你会看到一个更现代、更清爽的界面。第二步添加ESP32开发板支持。Arduino IDE默认只支持AVR架构的Arduino板如Uno, Nano。要让它能编译程序给ESP32也就是Magicbit使用我们需要添加额外的开发板管理器网址。打开Arduino IDE点击菜单栏的文件-首选项。在“附加开发板管理器网址”一栏中填入以下网址如果已有其他网址用逗号隔开https://espressif.github.io/arduino-esp32/package_esp32_index.json点击“好”保存。第三步安装ESP32开发板包。点击侧边栏的“开发板管理器”图标一个电路板模样的小图标。在顶部的搜索框中输入“esp32”。你应该会看到由“Espressif Systems”发布的“esp32”包。点击“安装”按钮。这个过程会下载几百MB的文件需要一些时间请保持网络通畅。第四步选择正确的开发板型号。安装完成后点击工具栏的“选择开发板”下拉菜单。由于Magicbit是基于ESP32-S3芯片的我们需要找到对应的选项。在列表中找到“ESP32S3 Dev Module”。如果你不确定Magicbit的官方文档通常会明确指出板型。选择它。第五步安装Magicbit专用库可选但推荐。为了更方便地使用板载外设Magicbit提供了一个集成库。在Arduino IDE中点击工具-管理库...打开库管理器。搜索“Magicbit”你应该能找到由Magicbit团队发布的库点击安装。这个库封装了屏幕、按键、传感器等操作的函数让我们的代码更简洁。不过为了更深入地理解底层原理本文的示例将主要使用通用的TFT_eSPI库来驱动屏幕这样学到的知识可以迁移到其他ESP32屏幕的项目中。至此你的开发环境就准备好了。如果遇到“arduino ide打不开一直转”的问题通常是旧版本残留冲突或杀毒软件拦截所致。尝试彻底卸载旧版本清理C:\Users\[你的用户名]\AppData\Local\Arduino15Windows或~/Library/Arduino15Mac目录下的缓存再重新安装新版一般都能解决。3. 游戏核心架构设计与实现3.1 图形引擎TFT_eSPI库的配置与使用要在屏幕上画出小恐龙、仙人掌和云朵我们需要一个高效的图形库。对于ESP32TFT_eSPI库是当之无愧的首选。它针对各种SPI接口的屏幕进行了高度优化绘制速度极快。首先通过库管理器安装TFT_eSPI库。安装后最关键的一步是配置用户设置文件。这个库通过一个配置文件来适配不同的屏幕和主板很多新手卡在这里就是因为没配对这个文件。找到Arduino库的安装目录。通常位于你的Arduino安装目录下的libraries文件夹内或者是在你的文档目录下的Arduino/libraries。进入TFT_eSPI库文件夹你会看到一个名为User_Setup.h的文件。不要直接修改它库的更新会覆盖这个文件。在TFT_eSPI库文件夹内找到一个名为User_Setup_Select.h的文件。用文本编辑器打开它。这个文件里有很多行被注释掉的#include语句每一行对应一种屏幕或开发板的配置。我们需要根据Magicbit的屏幕型号ST7789, 135x240像素但通常旋转后使用来启用对应的配置。查找与ST7789相关的行。例如你可能会找到一行类似//#include User_Setups/Setup25_ST7789.h的注释。取消这一行的注释删除行首的//并确保其他#include行都被注释掉。保存文件。接下来我们还需要检查或修改被引用的具体设置文件如Setup25_ST7789.h确认引脚定义与Magicbit匹配。Magicbit的屏幕通常使用固定的SPI引脚你需要查阅Magicbit的引脚定义图。常见的配置如下具体以官方文档为准#define TFT_CS 5 // 片选引脚 #define TFT_DC 21 // 数据/命令引脚 #define TFT_RST 18 // 复位引脚如果屏幕不需要硬件复位可以设为-1 #define TFT_MOSI 23 // SPI数据输出主出从入 #define TFT_SCLK 19 // SPI时钟在Setup25_ST7789.h中修改这些#define的值使其与Magicbit的硬件连接一致。完成这些配置后在代码中引入头文件#include TFT_eSPI.h并创建一个全局对象TFT_eSPI tft TFT_eSPI();就可以调用tft.drawPixel(),tft.fillRect(),tft.print()等函数进行绘图了。3.2 游戏状态机逻辑的骨架游戏本质上是一个状态机。对于小恐龙游戏我们可以定义几个核心状态MENU开始菜单、PLAYING游戏中、GAME_OVER游戏结束。状态机清晰地划分了不同阶段该做什么避免了代码逻辑混乱。enum GameState { STATE_MENU, STATE_PLAYING, STATE_GAME_OVER }; GameState currentState STATE_MENU;在loop()函数中我们根据currentState的值执行不同的代码块STATE_MENU在屏幕中央显示游戏标题如“DINO RUN!”和提示信息“Press A to Start”。同时检测按键A是否被按下如果按下就将状态切换到STATE_PLAYING并初始化游戏变量恐龙位置、速度、障碍物数组清空等。STATE_PLAYING这是游戏的核心循环。在这个状态下每一帧都需要依次执行处理输入检测跳跃/下蹲按键、更新游戏逻辑恐龙重力与跳跃、障碍物生成与移动、碰撞检测、渲染画面。STATE_GAME_OVER当碰撞检测触发后进入此状态。在屏幕上显示“GAME OVER”和最终得分并提示“Press A to Restart”。检测按键A按下后重置游戏状态回STATE_PLAYING。使用状态机使得代码结构非常清晰后期想要添加“暂停”状态也会非常容易。3.3 精灵、障碍物与场景对象管理游戏中的每个元素都可以看作一个对象我们需要管理它们的属性位置、速度、类型和行为移动、绘制。恐龙对象最简单的我们可以用一个结构体来表示。struct Dino { int x, y; // 左上角坐标 int width, height; // 碰撞框大小 int velocityY; // Y轴速度用于跳跃 bool isJumping; bool isDucking; // 是否处于下蹲状态 };跳跃的物理模拟很简单按下跳跃键时给velocityY一个向上的初速度负值。在每一帧的更新中velocityY受到“重力”影响而增加正值然后dino.y velocityY。当dino.y大于或等于地面高度时重置y坐标和velocityY并将isJumping设为false。下蹲状态则可以通过切换height和显示的精灵图来实现。障碍物对象仙人掌和翼龙可以共用一种结构体用type字段区分。struct Obstacle { int x, y; int width, height; int type; // 0: 小仙人掌1: 大仙人掌2: 翼龙 bool passed; // 是否已被越过用于计分 };我们需要一个Obstacle obstacles[MAX_OBSTACLES]数组来管理当前屏幕上的障碍物。障碍物的生成可以采用随机间隔当最后一个障碍物移动到一定距离后就在屏幕右侧x SCREEN_WIDTH生成一个新的并随机其类型和高度对于翼龙。在每一帧遍历数组将每个障碍物的x坐标减去一个固定的速度值实现向左移动。当障碍物移出屏幕左侧x width 0就从数组中移除或标记为可复用。场景渲染包括地面和云朵。地面可以画一条简单的直线或者为了更有动感绘制一组连续移动的“地板砖”贴图。云朵可以作为装饰性背景元素以比障碍物更慢的速度移动增加场景的层次感。3.4 碰撞检测游戏的胜负手碰撞检测决定了游戏何时结束。对于这种像素风格的2D游戏使用**轴对齐包围盒AABB**算法是最简单高效的。即判断两个矩形在X轴和Y轴上是否都有重叠。bool checkCollision(const Dino dino, const Obstacle obs) { return (dino.x obs.x obs.width dino.x dino.width obs.x dino.y obs.y obs.height dino.y dino.height obs.y); }在每一帧更新完所有对象的位置后遍历障碍物数组用恐龙当前的碰撞框与每一个障碍物的碰撞框进行上述检测。如果返回true则游戏结束切换到STATE_GAME_OVER。这里有一个细节需要注意恐龙的精灵图可能比其碰撞框大比如有尾巴尖、头发等非主体部分。为了游戏体验更公平我们通常会让碰撞框比显示的图像稍小一些这被称为“碰撞盒优化”。你可以通过调整dino.width和dino.height或者使用一个独立的碰撞矩形来实现。4. 性能优化与细节打磨4.1 帧率控制与双缓冲在嵌入式设备上不加控制的快速循环会导致屏幕闪烁和功耗增加。我们需要引入帧率控制。一个简单的方法是使用millis()函数。unsigned long lastFrameTime 0; const int FRAME_DELAY 33; // 目标帧间隔约30FPS (1000/30 ≈ 33) void loop() { unsigned long currentTime millis(); if (currentTime - lastFrameTime FRAME_DELAY) { lastFrameTime currentTime; // 执行一帧的游戏逻辑更新和渲染 updateGame(); renderGame(); } // 可以在这里处理一些非严格依赖帧率的逻辑比如按键消抖检测 }FRAME_DELAY控制了游戏运行的速度。33ms对应约30帧/秒这是一个比较流畅的速率。你也可以根据游戏难度动态调整这个值比如随着分数增加逐渐减小FRAME_DELAY来加快游戏速度。更进一步的优化是使用双缓冲。直接往屏幕上绘制单缓冲时如果绘制复杂场景时间较长用户会看到绘制过程产生闪烁。双缓冲的原理是在内存中开辟一块和屏幕一样大的“画布”缓冲区所有绘图操作先在这块内存画布上完成然后一次性将整块画布数据发送到屏幕。TFT_eSPI库通过tft.startWrite()和tft.endWrite()函数隐式支持了这种机制或者你也可以使用tft.setSwapBytes(true)配合tft.pushImage()函数来实现高效的全屏更新。对于小恐龙游戏如果每帧都是全屏刷新使用双缓冲能显著提升视觉流畅度。4.2 资源管理将图像存入Flash我们的游戏需要一些简单的精灵图比如恐龙的奔跑、跳跃、下蹲动画帧以及仙人掌、翼龙的图片。直接在代码里用drawPixel画太麻烦我们可以事先做好小位图。制作位图使用像LCD Image Converter或在线工具将小图片转换成C语言数组格式。图片尺寸要小颜色可以用16位RGB565格式以减少内存占用。存储位置将这些大的常量数组存储在Flash中而不是RAM里。在ArduinoESP32上使用PROGMEM关键字。const uint16_t dinoRunFrame1[] PROGMEM { /* ... 颜色数据 ... */ };读取与绘制TFT_eSPI库的pushImage函数支持从PROGMEM读取数据。tft.pushImage(x, y, width, height, (uint16_t*)dinoRunFrame1);注意需要强制类型转换。通过将多帧动画的数组放入一个二维数组或结构体数组中配合一个帧索引计数器就能实现恐龙奔跑的动画效果。4.3 输入处理与用户体验Magicbit有两个物理按键。我们需要稳定的按键检测避免抖动和重复触发。一个简单的状态机软件消抖算法如下bool readButton(int pin) { static int buttonState[MAX_BUTTONS]; static unsigned long lastDebounceTime[MAX_BUTTONS]; const unsigned long debounceDelay 50; int reading digitalRead(pin); if (reading ! buttonState[pin]) { lastDebounceTime[pin] millis(); } if ((millis() - lastDebounceTime[pin]) debounceDelay) { if (reading ! buttonState[pin]) { buttonState[pin] reading; if (buttonState[pin] LOW) { // 假设按下为LOW return true; } } } return false; }在游戏中我们可以将按键A映射为“跳跃/开始”按键B映射为“下蹲”。在STATE_PLAYING状态下检测到跳跃按键按下时如果恐龙不处于跳跃状态(!dino.isJumping)则触发跳跃逻辑。下蹲按键则是一个持续状态按下时dino.isDucking true并切换为下蹲精灵图松开时恢复。为了提升体验可以加入简单的音效。使用tone(pin, frequency, duration)函数驱动板载蜂鸣器在跳跃、得分、碰撞时发出不同频率的短音。注意不要长时间播放以免干扰主循环。5. 从原型到产品进阶思考与扩展完成基础版本后这个小项目还有巨大的扩展空间。这里分享几个我实践过或思考过的方向。1. 分数系统与难度曲线最简单的分数是计时每过一定时间如1000毫秒加1分。更有趣的是每当恐龙成功越过一个障碍物即障碍物的x width dino.x且未被标记为已越过就加一次分。难度曲线可以通过多种方式实现速度递增每得100分全局的障碍物移动速度obstacleSpeed增加一点。生成间隔缩短随着分数增加生成新障碍物的最小间隔时间减小。障碍物组合后期可以同时生成上下两个障碍物仙人掌翼龙增加通过难度。2. 利用板载传感器Magicbit板载了光线传感器。我们可以用它来实现自动亮度调节。在loop()中读取传感器值映射到屏幕背光亮度通过PWM控制背光引脚这样在光线暗的环境下屏幕不会刺眼。这虽然是个小功能但能让你的作品更像一个“产品”。3. 低功耗优化这是一个手持设备功耗值得考虑。在STATE_MENU状态下如果没有操作一段时间后可以进入低功耗模式关闭屏幕背光、降低ESP32的CPU频率。当任一按键被按下时再唤醒系统。ESP32的深度睡眠模式需要配合外部唤醒如EXT0实现起来稍复杂但对于由电池供电的设备来说能极大延长续航。4. 代码结构与可维护性当功能越来越多把所有代码堆在ino文件里会变得难以管理。好的做法是进行模块化拆分Game.ino主状态机和setup/loop函数。Display.h/cpp所有与屏幕绘制相关的函数封装。Input.h/cpp按键和传感器读取的封装。Objects.h/cpp恐龙、障碍物等游戏对象的定义和方法。Assets.h存储所有的精灵图、字体等PROGMEM数据。 这样结构清晰也方便与他人协作或日后回顾。这个“Magicbit小恐龙”项目从想法到实现几乎涵盖了嵌入式趣味开发的所有核心环节。它不像点亮一个LED那样简单也不至于复杂到让人望而却步。过程中你会遇到屏幕驱动不亮、图像闪烁、碰撞检测不准、游戏节奏不对劲等各种问题而解决每一个问题的过程就是最有效的学习。最终当你拿着这个自己编写程序、自己焊接虽然Magicbit几乎不用焊的小设备玩着自己复刻的经典游戏时那种成就感是无可替代的。希望这篇详细的分享能给你带来启发动手做一个属于你自己的掌上小恐龙吧。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门