用Arduino Nano和SSD1306 OLED构建推盒游戏
创作时间:
作者:
@小白创作中心
用Arduino Nano和SSD1306 OLED构建推盒游戏
引用
1
来源
1.
https://www.cnblogs.com/linkstu/p/18788743
用Arduino Nano和SSD1306 OLED显示屏制作一个经典的推盒游戏,通过四个按钮控制玩家移动,实现简单的游戏互动。本文将详细介绍硬件连接方法和完整的Arduino代码,帮助你快速上手这个有趣的项目。
项目简介
Push Box Game基于Arduino Nano和SSD1306 OLED显示屏,主要包括以下几个部分:
- Arduino Nano: 作为游戏的控制单元,处理用户输入和游戏逻辑。
- SSD1306 OLED显示屏: 用于显示游戏界面,包括玩家、盒子、目标位置和边界。
- 按钮: 四个按钮用于控制玩家的上下左右移动。
连接方法
- SSD1306 OLED显示屏通过I2C接口与Arduino Nano相连。
- 四个按钮连接到Arduino Nano的数字引脚上,每个按钮都需要一个上拉电阻,以确保稳定的信号读取。
项目功能
该程序的主要功能和特点如下:
- 初始化设置:设置串行通信,初始化OLED显示屏并显示游戏标题,将按钮引脚初始化为输入上拉模式,调用initGame()函数来设置游戏的初始状态。
- 游戏循环:主游戏循环,持续读取按键状态,更新游戏状态,并在游戏进行期间重新绘制游戏地图。
- 初始化游戏:设置玩家的初始位置,随机生成盒子和目标的位置。
- 绘制游戏地图:清除显示并重新绘制游戏地图,包括边界、玩家、盒子和目标位置。
- 读取按钮:检测按钮状态,如果按钮被按下,调用movePlayer()函数来移动播放器。
- 移动播放器:根据按键输入更新玩家的位置,如果玩家移动到一个盒子的位置,尝试推动盒子,检查游戏是否获胜。
- 检查获胜条件:如果所有方块都在目标位置,则显示获胜消息并重新开始游戏。
- 更新游戏:可以在这里添加额外的游戏逻辑,如得分或关卡更改。
预防措施
- 确保所有连接正确,特别是I2C连接和按钮的上拉电阻。
- 在实际部署前测试每个组件,以确保它们正常工作。
- 该程序使用随机数来初始化盒子和目标的位置,这可能导致一些游戏配置无法解决。您可能需要添加额外的逻辑来确保生成的布局是可解决的。
完整代码
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
// 初始化OLED显示屏
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// 游戏地图的尺寸(以方块为单位)
#define MAP_WIDTH 16 // 地图宽度,方块数
#define MAP_HEIGHT 8 // 地图高度,方块数
// 玩家、箱子和目标的位置
int playerX = 1;
int playerY = 1;
int boxX[2] = {2, 4};
int boxY[2] = {1, 3};
int targetX[2] = {3, 5}; // 两个目标位置
int targetY[2] = {1, 3};
bool gameRunning = true;
// 按钮引脚
const int buttonUp = 2;
const int buttonDown = 3;
const int buttonLeft = 4;
const int buttonRight = 5;
void setup() {
Serial.begin(9600);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // Address 0x3C for 128x64
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.println("推箱子游戏");
display.display();
delay(2000); // 显示初始化效果2秒
initGame();
// 初始化按钮引脚为输入上拉模式
pinMode(buttonUp, INPUT_PULLUP);
pinMode(buttonDown, INPUT_PULLUP);
pinMode(buttonLeft, INPUT_PULLUP);
pinMode(buttonRight, INPUT_PULLUP);
}
void loop() {
if (gameRunning) {
readButtons();
updateGame();
drawMap();
delay(100);
}
}
void initGame() {
// 初始化游戏状态
playerX = 1;
playerY = 1;
boxX[0] = 2;
boxY[0] = 1;
boxX[1] = 4;
boxY[1] = 3;
gameRunning = true;
}
// 绘制游戏地图
void drawMap() {
display.clearDisplay();
// 绘制游戏边界框的线条
display.drawLine(0, 0, MAP_WIDTH * 5 - 1, 0, BLACK); // 上边框
display.drawLine(0, 5 * MAP_HEIGHT - 1, MAP_WIDTH * 5 - 1, 5 * MAP_HEIGHT - 1, WHITE); // 下边框
display.drawLine(0, 0, 0, 5 * MAP_HEIGHT - 1, WHITE); // 左边框
display.drawLine(5 * MAP_WIDTH - 1, 0, 5 * MAP_WIDTH - 1, 5 * MAP_HEIGHT - 1, WHITE); // 右边框
// 绘制玩家位置
display.drawRect(playerX * 5, playerY * 5, 5, 5, WHITE); // 玩家方块尺寸:5x5
// 绘制箱子位置
for (int i = 0; i < 2; i++) {
display.fillRect(boxX[i] * 5, boxY[i] * 5, 5, 5, WHITE); // 箱子方块尺寸:5x5
}
// 绘制目标位置,使用黄色表示
for (int i = 0; i < 2; i++) {
display.drawRect(targetX[i] * 5, targetY[i] * 5, 5, 5, WHITE); // 目标方块尺寸:5x5,颜色:黄色(0xFFFF)
}
display.display();
}
// 按钮读取和消抖逻辑
void readButtons() {
static unsigned long lastDebounceTime = 0;
static unsigned long debounceDelay = 50;
unsigned long currentMillis = millis();
if (digitalRead(buttonUp) == LOW && (currentMillis - lastDebounceTime) > debounceDelay) {
lastDebounceTime = currentMillis;
movePlayer(0, -1);
}
if (digitalRead(buttonDown) == LOW && (currentMillis - lastDebounceTime) > debounceDelay) {
lastDebounceTime = currentMillis;
movePlayer(0, 1);
}
if (digitalRead(buttonLeft) == LOW && (currentMillis - lastDebounceTime) > debounceDelay) {
lastDebounceTime = currentMillis;
movePlayer(-1, 0);
}
if (digitalRead(buttonRight) == LOW && (currentMillis - lastDebounceTime) > debounceDelay) {
lastDebounceTime = currentMillis;
movePlayer(1, 0);
}
}
void movePlayer(int dx, int dy) {
// 玩家移动逻辑
int newX = playerX + dx;
int newY = playerY + dy;
// 检查新位置是否在地图范围内
if (newX >= 0 && newX < MAP_WIDTH && newY >= 0 && newY < MAP_HEIGHT) {
// 检查新位置是否不是墙壁
if (!isWall(newX, newY)) {
// 检查新位置是否是箱子
for (int i = 0; i < 2; i++) {
if (newX == boxX[i] && newY == boxY[i]) {
// 尝试推动箱子
int boxNewX = boxX[i] + dx;
int boxNewY = boxY[i] + dy;
if (!isWall(boxNewX, boxNewY) && !isBox(boxNewX, boxNewY)) {
boxX[i] = boxNewX;
boxY[i] = boxNewY;
playerX = newX;
playerY = newY;
checkWinCondition();
return;
}
}
}
// 移动玩家
playerX = newX;
playerY = newY;
}
}
}
bool isWall(int x, int y) {
// 定义墙壁位置
return (x == 0 || x == MAP_WIDTH - 1) || (y == 0 || y == MAP_HEIGHT - 1);
}
bool isBox(int x, int y) {
// 检查位置是否有箱子
for (int i = 0; i < 2; i++) {
if (x == boxX[i] && y == boxY[i]) {
return true;
}
}
return false;
}
void checkWinCondition() {
// 检查所有箱子是否都在目标位置
bool allBoxesAtTarget = true;
for (int i = 0; i < 2; i++) {
if (boxX[i] != targetX[i] || boxY[i] != targetY[i]) {
allBoxesAtTarget = false;
break;
}
}
if (allBoxesAtTarget) {
gameRunning = false;
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.println("Congratulations!!");
display.println("You win!");
display.display();
delay(300);
initGame(); // 重新开始游戏
}
}
void updateGame() {
// 游戏逻辑更新
}
热门推荐
DIY灯笼+创新灯谜:让中秋传统文化焕发新活力
从捡石头到宝石达人:实用鉴定技巧与市场行情全攻略
南眉籽:一颗种子的多重养生密码
南眉籽:糖尿病和高血压患者的天然食材之选
南眉籽:科学实证的养生食材
青岛老城区:文化传承的新篇章
大理自然景观精华一日游攻略🔥
从莆田到恩施的火车行程:途径站点全解析及相关旅行建议
心理学揭示:这三个信号表明他可能是你的正缘
脑梗塞治疗指南:5种权威治疗方案详解
那些始终忘不掉的人,都是生命中的重要相遇
福建15大特色美食:从福州鱼丸到泉州肉粽
八字缺桃花运?专家解析改善之道
福州厦门高铁游:两天玩转两座城
中国古代楼阁:建筑艺术与文人情怀的完美融合
从实用到审美:中国楼阁建筑的演变与文化内涵
响堂山风景区:千年石窟与现代旅游的完美融合
手机积分兑换攻略:轻松兑换好礼,享受更多福利!
咸鸭蛋虽好但要适量,这样吃更健康
减肥期间如何健康食用咸鸭蛋:搭配方法与摄入量建议
瘦牛肉成糖尿病患者理想食材,四大要点教你健康食用
天津亲子游必打卡:天津之眼、津门故里、南湾公园全攻略
研究证实:糖尿病患者适量食用牛肉有益血糖控制
糖尿病患者吃牛肉:含铬助控糖,这样食用最安全
糖尿病患者吃牛肉:选对部位、控制量,这样吃最健康
青岛构建“4+2+4”海洋产业体系,打造现代海洋经济发展高地
千元色盲眼镜值得买吗?效果有限需理性消费
色盲眼镜:从原理到选购,一文读懂这项助视科技
蓝莓籽食用全攻略:营养价值、食用方法与选购要点
《小巷人家》王安宇关晓彤CP感爆棚!