1. 项目概述在Arduino上复刻经典T-Rex跑酷游戏相信很多朋友在Chrome浏览器断网时都玩过那个经典的T-Rex跑酷游戏——一只像素风的小恐龙需要跳过仙人掌和飞鸟。这个游戏虽然简单但其背后包含了游戏循环、碰撞检测、玩家输入响应等核心的游戏开发逻辑。今天我们不写网页也不做手机App而是把这个游戏“搬”到一块小小的Arduino开发板上用最基础的硬件——一个16x2的字符LCD屏和两个按键来重现这份经典。对于嵌入式开发或物联网的初学者来说直接上手硬件项目往往伴随着一些“劝退”因素需要购买各种元器件、焊接电路时可能烧坏芯片、代码烧录后现象不对却难以排查是硬件问题还是软件问题。这些门槛让很多有趣的想法止步于理论。而本项目的核心价值就在于利用Tinkercad这款免费的在线电路仿真工具完美解决了上述痛点。你不需要任何实体硬件只需一台能上网的电脑就能完成从电路设计、代码编写到功能仿真的全流程。这就像在进入真实赛车场前先在模拟器上把驾驶技术练熟一样安全、高效且零成本。通过这个项目你将掌握如何用Arduino驱动LCD显示自定义字符、如何用有限的外设两个按键设计游戏交互、如何构建一个稳定的游戏主循环。更重要的是你会理解如何将一个软件游戏逻辑适配到资源极其有限的微控制器MCU环境中。这不仅是复刻一个游戏更是一次典型的嵌入式系统开发实战非常适合对硬件编程、游戏逻辑或物联网应用感兴趣的朋友作为入门练手项目。2. 核心硬件选型与电路设计解析2.1 硬件组件清单与功能剖析要实现这个项目我们需要几样核心硬件。在Tinkercad中我们可以直接从元件库拖拽使用了解每个元件的作用是正确连接电路的前提。Arduino UNO R3本项目的大脑。它是一块基于ATmega328P微控制器的开发板负责运行我们编写的游戏代码处理逻辑并控制所有外围设备。选择UNO是因为其普及度高资料丰富引脚布局规整非常适合教学和原型开发。16x2 LCD字符显示屏本项目的“屏幕”。16x2意味着它能显示2行每行16个字符。它内部有控制器可以显示字母、数字和少量自定义符号。我们将用它来显示恐龙、障碍物、分数和菜单。需要注意的是这是一种字符型LCD而非像素点阵屏这意味着我们无法自由控制每一个像素点而是要以“字符”为单位进行显示这直接影响了我们游戏画面的设计方式。按键Pushbutton x 2本游戏的“手柄”。我们将用两个按键分别实现“跳跃”和“开始/选择”功能。在电路中按键通常需要配合上拉或下拉电阻来确保在未按下时单片机读取到的引脚电平是稳定确定的高电平或低电平防止因引脚悬空导致的误触发。1kΩ电阻用于限制LCD背光LED的电流。LED直接接5V可能会因电流过大而烧毁串联一个电阻起到保护作用。根据欧姆定律R (Vcc - V_led) / I_led假设LED压降约2V期望电流10mA则R (5V - 2V) / 0.01A 300Ω。使用1kΩ是更保守和通用的选择此时电流约为3mA背光稍暗但足够可视且更安全。连接线在Tinkercad中就是虚拟的导线用于连接各元件构成电流通路。注意在真实的硬件项目中你还需要一个电位器来调节LCD的对比度VO引脚以便获得清晰的显示。在Tinkercad仿真中LCD对比度是默认合适的所以教程中直接将VO接地简化了电路。但在实物制作时强烈建议使用一个10kΩ电位器连接VO引脚以便调整。2.2 电路连接原理与细节避坑电路连接是硬件项目的基础错误的连接轻则功能失常重则损坏元件。下面我们详细拆解每个连接背后的原理。LCD与Arduino的连接4位数据模式这是本项目的核心连接。16x2 LCD通常有16个引脚但我们不需要全部使用。为了节省Arduino宝贵的I/O口我们采用“4位数据模式”而非“8位数据模式”。这意味着我们只使用数据总线的高4位DB4-DB7来传输数据每个字节的数据分两次高4位、低4位发送。虽然速度稍慢但节省了4个I/O口对于本项目绰绰有余。电源引脚VCC接Arduino 5VGND接Arduino GND。这是必须的。对比度引脚VO引脚在仿真中直接接GND获得最大对比度。实物中接电位器的中间抽头。控制引脚RS (Register Select)寄存器选择引脚。告诉LCD接下来发送的是指令RS0还是数据RS1。例如设置光标位置是指令显示一个字符是数据。RW (Read/Write)读写选择引脚。接地GND表示我们始终向LCD写入数据而不从LCD读取状态。这简化了操作但代价是我们无法通过读取“忙标志”来等待LCD完成当前操作必须在代码中插入足够的延时。E (Enable)使能引脚。这是一个脉冲信号。当数据在DB4-DB7上准备好后给E引脚一个从高到低的跳变脉冲LCD才会锁存并处理这些数据。数据引脚DB4, DB5, DB6, DB7 分别接至Arduino的某个数字引脚例如引脚4,5,6,7用于传输数据。背光引脚A阳极接5VK阴极通过一个1kΩ限流电阻接GND。按键的连接内部上拉模式按键的连接有学问。一个按键有两端当按下时两端导通。我们需要将其一端接GND另一端接Arduino的某个数字引脚例如引脚2和3。关键在于我们需要在Arduino代码中将该引脚的模式设置为INPUT_PULLUP输入上拉模式。Arduino芯片内部有一个上拉电阻当设置为该模式后在按键未按下时该引脚通过内部电阻连接到5V我们读取到的是高电平1当按键按下时引脚直接与GND接通我们读取到的是低电平0。这样就省去了外接一个物理上拉电阻的麻烦。避坑点如果你在代码中错误地将引脚模式设置为INPUT而不是INPUT_PULLUP并且外部也没有接上拉电阻那么引脚在悬空状态下读取的电平是不确定的可能随机跳动会导致按键“失灵”或“自动触发”。完整的Tinkercad接线图示例在Tinkercad中你可以按照以下对应关系进行连接LCD RS - Arduino 引脚 12LCD E - Arduino 引脚 11LCD D4 - Arduino 引脚 5LCD D5 - Arduino 引脚 4LCD D6 - Arduino 引脚 3LCD D7 - Arduino 引脚 2LCD VCC - Arduino 5VLCD GND - Arduino GNDLCD VO - GND (仿真中)LCD A - 5VLCD K - 通过1kΩ电阻 - GND按键1跳跃一端 - GND另一端 - Arduino 引脚 8按键2开始一端 - GND另一端 - Arduino 引脚 93. 游戏软件逻辑与代码深度实现3.1 核心数据结构与自定义字符设计在字符LCD上做游戏最大的挑战是图形表现力有限。我们不能画任意形状只能使用LCD控制器内置的字符库ASCII码以及最多8个自定义字符CGRAM。因此我们的游戏画面必须用“字符方块”来构建。1. 恐龙与障碍物的自定义字符我们需要设计几个关键的图案恐龙站立/奔跑姿态可能需要2-3个自定义字符来组合成一个小恐龙的样子比如一个字符是恐龙头和上半身另一个是下半身。通过交替显示可以形成奔跑的动画效果。仙人掌障碍物设计1-2个字符来表示不同高度的仙人掌。地面可以用下划线“_”或等号“”的ASCII字符表示。在Arduino的LiquidCrystal库中使用createChar(num, byte_array)函数来创建自定义字符。num是0-7的索引号byte_array是一个8字节的数组每个字节的8个位0或1定义了字符5x8点阵中一行的像素1亮0灭。例如一个简单的站立恐龙上半身可能看起来像这样二进制表示1代表点亮byte dinoUpper[8] { B00100, B00110, B00111, B10110, B11111, B01010, B00100, B00000 };设计这些图案需要一些耐心你可以在纸上画好5x8的格子填黑后再转换成二进制数组。2. 游戏状态与变量定义我们需要用一系列变量来记录游戏的动态状态这是游戏逻辑的“记忆体”。// 游戏状态机 enum GameState { MENU, PLAYING, GAME_OVER }; GameState currentState MENU; // 恐龙位置LCD行、列坐标通常行0/1列0-15 int dinoRow 0; // 假设在地面行 int dinoCol 2; // 起始列 bool isJumping false; int jumpHeight 0; int jumpCounter 0; // 障碍物 int obstacleCol 15; // 从屏幕最右侧进入 int obstacleType 0; // 0: 矮仙人掌 1: 高仙人掌 int obstacleGap 8; // 障碍物之间的间隔列数 // 游戏速度与分数 long gameSpeed 400; // 主循环延迟控制游戏速度越小越快 int score 0; int highScore 0; // 按键定义 const int jumpButtonPin 8; const int startButtonPin 9;GameState枚举定义了游戏的几个阶段这是编写清晰状态机逻辑的基础。游戏速度gameSpeed是一个关键变量它通过控制主循环中delay()的时间来调节恐龙和障碍物的移动速度随着分数增加可以逐渐减小这个值让游戏变难。3.2 主循环架构与关键函数实现Arduino程序的核心是setup()和loop()。setup()负责一次性初始化loop()则是不停循环执行的主逻辑。1.setup()函数初始化一切void setup() { // 1. 初始化LCD指定引脚连接关系 lcd.begin(16, 2); // 2. 创建自定义字符并载入LCD的CGRAM lcd.createChar(0, dinoUpper); // 索引0对应自定义字符0 lcd.createChar(1, dinoLower); lcd.createChar(2, cactusSmall); // ... 创建其他字符 // 3. 设置按键引脚为输入上拉模式 pinMode(jumpButtonPin, INPUT_PULLUP); pinMode(startButtonPin, INPUT_PULLUP); // 4. 初始化随机种子用于随机生成障碍物类型 randomSeed(analogRead(A0)); // 读取一个未连接的模拟引脚噪声作为种子 // 5. 显示初始菜单 showMenu(); }实操心得randomSeed(analogRead(A0))是一个常用技巧。Arduino上电后如果未连接任何信号模拟引脚A0会读取到环境电磁噪声这个值是不确定的可以用作不错的随机数种子这样每次游戏重启后障碍物的出现模式就不会完全一样了。2.loop()函数游戏状态机调度loop()函数就像一个总调度中心根据currentState的值决定执行哪一段逻辑。void loop() { switch (currentState) { case MENU: handleMenu(); break; case PLAYING: runGame(); break; case GAME_OVER: handleGameOver(); break; } // 一个很小的延时防止循环过快 delay(10); }3.handleMenu()与handleGameOver()菜单与结束界面这两个函数负责处理非游戏进行时的交互。它们通常包括在LCD上显示提示信息如“PRESS TO START”、“SCORE: XX”。持续检测开始按键是否被按下。当按下开始键时重置游戏变量恐龙位置、障碍物位置、分数等并将currentState设置为PLAYING。在游戏结束界面还需要比较本次分数与历史最高分并更新最高分。4.runGame()函数游戏核心逻辑这是整个项目最复杂的部分它在一个循环周期内需要完成以下所有事情void runGame() { // 1. 处理输入检测跳跃按键 if (digitalRead(jumpButtonPin) LOW !isJumping dinoRow GROUND_ROW) { isJumping true; jumpCounter 0; } // 2. 更新游戏对象状态 updateDino(); // 根据是否跳跃更新恐龙垂直位置 updateObstacle(); // 向左移动障碍物并判断是否移出屏幕、是否需要生成新障碍物 updateScore(); // 根据时间或越过障碍物增加分数并可能提高游戏速度 // 3. 碰撞检测 if (checkCollision()) { currentState GAME_OVER; return; // 发生碰撞立即结束本轮游戏循环进入结束状态 } // 4. 渲染画面 renderGame(); // 清屏然后在正确的位置绘制恐龙、障碍物、地面和分数 // 5. 控制游戏节奏 delay(gameSpeed); }输入处理使用digitalRead()读取按键电平。注意由于我们使用了内部上拉按键按下时读到的是LOW。为了防止“长按”导致连续跳跃我们设置了isJumping标志位只有在落地状态下按下按键才触发新跳跃。状态更新updateDino()实现跳跃的物理模拟。跳跃可以简化为一个抛物线过程上升阶段减速到达顶点后下降阶段加速。我们可以用一个变量jumpCounter计数根据其值计算恐龙在垂直方向行的偏移量。updateObstacle()每一帧将障碍物的列坐标减1使其向左移动。当障碍物移出屏幕列坐标小于0就将其重置到屏幕最右侧列15并随机决定下一个障碍物的类型高或矮。碰撞检测这是游戏逻辑的精华。我们需要判断代表恐龙的字符块与代表障碍物的字符块在屏幕上的位置是否发生了重叠。由于LCD坐标是离散的行列整数检测相对简单if (dinoCol obstacleCol dinoRow obstacleRow) { /* 碰撞 */ }。但实际情况可能更复杂因为恐龙和障碍物可能不止占一个字符位。渲染画面使用lcd.clear()或lcd.setCursor()配合lcd.print()来绘制每一帧。为了减少闪烁可以只重绘发生变化的部分而不是每帧清屏全量重绘。3.3 在Tinkercad中编写与上传代码Tinkercad集成了Arduino代码编辑器支持块式编程Blocks和文本编程Text。为了获得最大的灵活性和学习效果我们必须使用文本模式。在Tinkercad中搭建好电路后点击右上角的“代码”按钮。默认是“块”视图。点击下拉菜单选择“文本”。系统会提示你将丢失块式代码点击“继续”。你将看到一个包含setup()和loop()空函数的编辑器。将我们编写好的完整代码粘贴进去。代码中的LiquidCrystal库是Tinkercad环境内置的无需额外安装。点击“开始仿真”按钮。如果代码有语法错误下方控制台会报错。根据错误信息修改代码。仿真成功启动后你可以点击虚拟的按键来控制游戏在LCD上观察游戏运行情况。重要注意事项Tinkercad的仿真速度可能与真实硬件有差异尤其是涉及delay()函数时。如果你在仿真中感觉游戏速度非常慢或非常快可以调整gameSpeed和跳跃等逻辑中的延时参数。仿真的意义在于验证逻辑正确性最终在真实硬件上运行时可能还需要进行微调。4. 从仿真到实物制作要点与深度调试4.1 实物制作清单与焊接装配指南在Tinkercad仿真成功验证了所有逻辑后你就可以着手制作实体版本了。你需要准备以下实物Arduino UNO开发板 x116x2 LCD屏带I2C接口的版本更佳接线更简单 x1轻触按键 x21kΩ电阻 x1用于LCD背光10kΩ电位器 x1用于调节LCD对比度强烈推荐面包板 x1 或 洞洞板 x1公对公、公对母杜邦线 若干USB数据线为Arduino供电和编程 x1装配步骤建议规划布局在面包板上先摆放主要元件Arduino, LCD预留连线空间。将LCD的1脚通常有标记对准面包板一侧方便数引脚。先电源后信号首先连接所有元件的电源5V和地GND确保供电回路正确。这是硬件调试的铁律可以避免很多诡异问题。连接LCD按照仿真中的接线图连接LCD的控制线和数据线。如果使用I2C LCD模块则接线极大简化只需连接VCC、GND、SDAA4、SCLA5四根线但代码中需要包含Wire.h和LiquidCrystal_I2C.h库并修改对象初始化方式。连接按键和电位器按键按前述方法连接。电位器三个引脚两侧分别接5V和GND中间引脚接LCD的VO引脚。检查连线对照电路图逐一检查每根线是否连接正确、牢固。特别注意不要将5V和GND短路。4.2 常见问题排查与深度调试技巧即使仿真成功实物制作也常会遇到问题。以下是一个系统性的排查清单现象可能原因排查步骤与解决方案LCD无任何显示1. 电源未接通或接反。2. 对比度电位器调节不当。3. 背光未亮在光线暗处观察。1. 用万用表测量LCD VCC和GND间电压是否为5V。2. 缓慢旋转电位器调节对比度。3. 检查背光LED引脚接线和限流电阻。LCD显示乱码或黑色方块1. 初始化代码不正确或时序问题。2. 数据线接触不良或接错。3. 电位器对比度未调好。1. 确认lcd.begin()行列参数正确16,2。2. 检查DB4-DB7四根数据线是否与代码定义一致。3. 重新仔细调节对比度。按键无反应或一直触发1. 引脚模式未设置为INPUT_PULLUP。2. 按键接线错误一端未接GND。3. 代码中读取的电平逻辑弄反。1. 检查setup()中pinMode(pin, INPUT_PULLUP)。2. 用万用表通断档检查按键按下时是否导通。3. 记住上拉模式下未按是高电平(1)按下是低电平(0)。游戏运行卡顿或不流畅1. 主循环delay(gameSpeed)时间太长。2. 渲染函数renderGame()效率低每帧清屏全量重绘。3. 碰撞检测等函数逻辑过于复杂。1. 逐步减小gameSpeed值测试。2. 优化渲染只更新变化的位置如用变量记录上一帧位置先擦除旧图再画新图。3. 简化计算避免在循环中使用浮点数或复杂数学函数。恐龙或障碍物显示残缺1. 自定义字符数组数据定义错误。2. 创建字符createChar的索引与显示时使用的索引不匹配。3. CGRAM空间被覆盖最多8个。1. 检查自定义字符的字节数组每个字节应为8位可用在线工具生成。2. 确保lcd.createChar(0, charData)和lcd.write(byte(0))索引对应。3. 确保使用的自定义字符总数不超过8个。仿真正常实物异常1. 实物引脚连接与代码定义不符。2. 硬件接触不良或损坏。3. 电源供电不足特别是使用电脑USB口时。1.最常用方法编写一个最简单的“Hello World”测试程序单独测试LCD和按键是否正常工作隔离问题。2. 换用手机充电器等独立5V电源为Arduino供电测试。3. 检查所有杜邦线与插孔的接触是否紧密。高级调试技巧串口打印在代码中添加Serial.begin(9600);和Serial.print()语句将关键变量如恐龙坐标、障碍物坐标、按键状态、游戏状态的值打印到串口监视器。这是洞察程序内部运行状态的“透视镜”对于排查逻辑错误极其有效。例如在updateDino()函数里打印dinoRow和isJumping可以清楚地看到跳跃过程是否正常。5. 项目优化与扩展思路一个基础版本完成后你可以从以下几个方向进行优化和扩展这会让你的项目从“能运行”变得“更专业、更有趣”。5.1 性能与体验优化消除屏幕闪烁lcd.clear()操作耗时且会导致全屏闪烁。优化方法是采用“局部更新”// 在全局变量中记录上一帧恐龙和障碍物的位置 int prevDinoCol, prevDinoRow, prevObsCol; void renderGame() { // 1. 擦除上一帧的恐龙在旧位置打印空格 lcd.setCursor(prevDinoCol, prevDinoRow); lcd.print( ); // 2. 擦除上一帧的障碍物 lcd.setCursor(prevObsCol, GROUND_ROW); lcd.print( ); // 3. 在新位置绘制当前帧的恐龙和障碍物 lcd.setCursor(dinoCol, dinoRow); lcd.write(byte(0)); // 绘制恐龙字符 lcd.setCursor(obstacleCol, GROUND_ROW); lcd.write(byte(2)); // 绘制障碍物字符 // 4. 更新“上一帧位置”记录 prevDinoCol dinoCol; prevDinoRow dinoRow; prevObsCol obstacleCol; }增加游戏元素多种障碍物不止仙人掌可以增加飞鸟出现在屏幕上方一行这需要修改碰撞检测逻辑判断恐龙在跳跃时是否撞到飞鸟。道具系统设计一个“加速”或“无敌”道具字符恐龙吃到后短时间内游戏速度变慢或无视碰撞。音效反馈增加一个压电蜂鸣器或无源蜂鸣器模块。在跳跃、得分、碰撞时用tone()函数播放简单的音效体验立刻提升一个档次。改进交互连跳限制防止玩家在空中连续按键增加游戏平衡性。按下时长控制跳跃高度检测按键按下的时间按得越久跳得越高但有一个上限。这需要用到millis()函数来计时而不是简单的delay。5.2 硬件扩展与进阶玩法更换显示设备如果你觉得字符LCD表现力太弱可以升级到OLED显示屏如0.96寸 I2C SSD1306。这种屏幕是像素级的可以绘制更细腻的恐龙和背景。你需要学习Adafruit_GFX和Adafruit_SSD1306库。更换输入设备用摇杆模块替代按键可以更精确地控制虽然本游戏只需要二段跳但摇杆可以作为其他扩展游戏的基础。或者使用红外接收头用电视遥控器来玩游戏。增加网络功能使用ESP8266或ESP32这类带Wi-Fi的开发板替代Arduino UNO。你可以将游戏最高分上传到云端服务器制作一个简单的全球排行榜。这需要你学习基本的网络请求HTTP GET/POST知识。制作独立游戏机使用Arduino Pro Mini这种更小的主板搭配一个锂电池供电模块将整个电路焊接在一块洞洞板上装进3D打印的外壳里就变成了一个掌上复古游戏机。这个项目麻雀虽小五脏俱全。它串联起了嵌入式开发的完整链条需求分析、方案设计、硬件选型、电路搭建、逻辑编程、仿真测试、实物制作、调试排错。当你看到自己编写的代码在真实的硬件上跑起来那个像素小恐龙随着你的按键在屏幕上跳跃时那种从零到一创造的成就感是纯软件编程难以比拟的。希望这个详细的教程能成为你硬件开发之旅上一块坚实的垫脚石。