带有oled显示屏的arduino复古游戏

你有没有想过写自己的复古游戏需要多少工作?庞为Arduino编写代码有多简单?...

有没有想过要花多少功夫才能写出自己的复古游戏?Pong为Arduino编写代码有多容易?加入我,我告诉你如何建立一个Arduino供电的迷你复古游戏控制台,以及如何从头开始编码乒乓球。最终结果如下:

arduino-retro-gaming

建造计划

这是一个相当简单的电路。电位计(pot)将控制游戏,而OLED显示器将由Arduino驱动。这将产生在一个试验板,但你可能希望这是一个永久性的电路,并安装在一个案件。我们以前写过关于重新创建Pong的内容,但是今天我将向您展示如何从头开始编写代码,并分解每个部分。

你需要什么

Retro Arduino Setup

以下是您需要的:

  • 1 x Arduino(任何型号)
  • 1 x 10k电位计
  • 1 x 0.96英寸I2C OLED显示屏
  • 1 x试验板
  • 各种公接头>公接头导线

DIYmall 0.96" OLED Module 0.96 inch I2C IIC Serial 128X64 OLED Display Module SSD1306 Driver for Arduino 51 MSP420 STIM32 SCR Raspberry PI (1pc X White) BUY NOW ON AMAZON

任何Arduino都可以,如果您不确定要购买哪款车型,请参阅我们的购买指南。

这些OLED显示器非常酷。他们通常可以购买白色,蓝色,黄色,或三者的混合物。他们确实存在于全彩,但这些增加了一个完整的另一个层次的复杂性和这个项目的成本。

电路

这是一个相当简单的电路。如果你对Arduino没有太多的经验,请先看看这些初学者的项目。

在这里:

Pong Breadboard

看着锅前,将左销连接到+5V,右销接地。将中间引脚连接到模拟引脚0(A0)。

OLED显示器使用I2C协议连接。将VCC和GND连接至Arduino+5V和接地。将SCL连接到模拟5(A5)。将SDA连接到模拟4(A4)。连接到模拟管脚的原因很简单;这些管脚包含I2C协议所需的电路。确保这些连接正确,没有交叉。具体引脚将因型号而异,但A4和A5用于纳米和Uno。如果您不使用Arduino或Nano,请查看模型的Wire库文档。

盆栽试验

上载此测试代码(确保从Tools>board和Tools>port菜单中选择正确的板和端口):

void setup() { // put your setup code here, to run once: Serial.begin(9600); // setup serial}void loop() { // put your main code here, to run repeatedly: Serial.println(****ogRead(A0)); // print the value from the pot delay(500);}

现在打开串行监视器(右上角>串行监视器),然后转动锅盖。您应该看到串行监视器上显示的值。完全逆时针应为零,且完全顺时针应为1023:

Pong serial monitor

稍后你会调整这个,但现在还可以。如果什么也没发生,或者数值没有做任何改变,断开并重新检查电路。

oled测试

OLED Graphics

OLED显示器的配置稍微复杂一些。首先需要安装两个库来驱动显示器。从Github下载Adafruit\usssd1306和Adafruit GFX库。将文件复制到库文件夹中。这取决于您的操作系统:

  • Mac OS:/Users/Username/Documents/Arduino/libraries
  • Linux:/home/Username/Sketchbook
  • Windows:/Users/Arduino/库

现在上传一个测试草图。转到“文件”>“示例”>“Adafruit SSD1306”>“SSD1306\U 128x64\U i2c”。这将为您提供包含大量图形的大型草图:

OLED Graphics

如果上传后什么也没发生,请断开连接并重新检查连接。如果菜单中没有示例,则可能需要重新启动Arduino IDE。

代码

现在是编写代码的时候了。我会解释每一步,所以跳到最后,如果你只是想让它运行。这是一个相当多的代码,所以如果你不觉得有信心,看看这10免费资源学习代码。

首先包括必要的库:

#include <SPI.h>#include <Wire.h>#include <Adafruit_GFX.h>#include <Adafruit_SSD1306.h>

SPI和WIRE是两个用于处理I2C通信的Arduino库。Ada水果_uGFX和Ada水果_SSD1306是您以前安装的库。

接下来,配置显示器:

Adafruit_SSD1306 display(4);

然后设置运行游戏所需的所有变量:

int resolution[2] = {128, 64}, ball[2] = {20, (resolution[1] / 2)};c***t int PIXEL_SIZE = 8, WALL_WIDTH = 4, PADDLE_WIDTH = 4, BALL_SIZE = 4, SPEED = 3;int playerScore = 0, aiScore = 0, playerPos = 0, aiPos = 0;char ballDirectionHori = 'R', ballDirectionVerti = 'S';boolean inProgress = true;

它们存储运行游戏所需的所有数据。其中一些存储了球的位置、屏幕的大小、球员的位置等等。注意其中一些是常量,这意味着它们是常量,永远不会改变。这让Arduino编译器加快了速度。

屏幕分辨率和球的位置存储在数组中。数组是相似事物的集合,对于球,存储坐标(X和Y)。访问数组中的元素很容易(不要在文件中包含此代码):

resolution[1];

由于数组从零开始,这将返回分辨率数组中的第二个元素(64)。更新元素更容易(同样,不要包含此代码):

ball[1] = 15;

在void setup()中,配置显示:

void setup() { display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.display();}

第一行告诉Adafruit库您的显示器使用的是什么维度和通信协议(在本例中是128 x 64和I2C)。第二行(显示。显示())告诉屏幕显示缓冲区中存储的任何内容(即nothing)。

创建两个名为drablell和eraseBall的方法:

void drawBall(int x, int y) { display.drawCircle(x, y, BALL_SIZE, WHITE);}void eraseBall(int x, int y) { display.drawCircle(x, y, BALL_SIZE, BLACK);}

它们获取球的x和y坐标,并使用显示库中的drawCircle方法在屏幕上绘制球。这将使用前面定义的常量球大小。试着改变这个,看看会发生什么。这个drawCircle方法接受一种像素颜色——黑色或白色。由于这是一个单色显示器(一种颜色),白色相当于一个像素被打开,而黑色则关闭像素。

现在创建一个名为moveAi的方法:

void moveAi() { eraseAiPaddle(aiPos); if (ball[1] > aiPos) { ++aiPos; } else if (ball[1] < aiPos) { --aiPos; } drawAiPaddle(aiPos);}

此方法处理移动人工智能或AI播放器。这是一个相当简单的电脑对手——如果球在桨上方,向上移动。在桨下面,向下移动。很简单,但效果很好。增量和减量符号用于(++aiPos和--aiPos)从aiPosition中添加或减去一个。你可以加或减一个更大的数字,使人工智能移动更快,因此更难击败。下面是您将如何做到这一点:

aiPos += 2;

以及:

aiPos -= 2;

正负等号是从aiPos当前值中添加或减去两个的缩写。还有一种方法可以做到:

aiPos = aiPos + 2;

aiPos = aiPos - 1;

注意这个方法是如何先擦除划片,然后再绘制它的。必须这样做。如果划桨的新位置被划出,屏幕上会有两个重叠的划桨。

drawNet方法使用两个循环绘制网络:

void drawNet() { for (int i = 0; i < (resolution[1] / WALL_WIDTH); ++i) { drawPixel(((resolution[0] / 2) - 1), i * (WALL_WIDTH) + (WALL_WIDTH * i), WALL_WIDTH); }}

这将使用墙的宽度变量来设置其大小。

创建名为drawPixels和erasePixels的方法。与ball方法一样,这两种方法之间的唯一区别是像素的颜色:

void drawPixel(int posX, int posY, int dimensi***) { for (int x = 0; x < dimensi***; ++x) { for (int y = 0; y < dimensi***; ++y) { display.drawPixel((posX + x), (posY + y), WHITE); } }}void erasePixel(int posX, int posY, int dimensi***) { for (int x = 0; x < dimensi***; ++x) { for (int y = 0; y < dimensi***; ++y) { display.drawPixel((posX + x), (posY + y), BLACK); } }}

同样,这两种方法都使用两个for循环来绘制一组像素。循环不必使用库drawPixel方法绘制每个像素,而是基于给定的尺寸绘制一组像素。

drawScore方法使用库的文本特性将播放器和AI分数写入屏幕。这些存储在playerScore和aiScore中:

void drawScore() { display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(45, 0); display.println(playerScore); display.setCursor(75, 0); display.println(aiScore);}

这个方法还有一个对应的eraseScore,它将像素设置为黑色或关闭。

最后四种方法非常相似。他们绘制并擦除玩家和AI桨:

void erasePlayerPaddle(int row) { erasePixel(0, row - (PADDLE_WIDTH * 2), PADDLE_WIDTH); erasePixel(0, row - PADDLE_WIDTH, PADDLE_WIDTH); erasePixel(0, row, PADDLE_WIDTH); erasePixel(0, row + PADDLE_WIDTH, PADDLE_WIDTH); erasePixel(0, row + (PADDLE_WIDTH + 2), PADDLE_WIDTH);}

注意他们如何调用前面创建的erasePixel方法。这些方法绘制并擦除相应的桨。

主循环中有更多的逻辑。这是整个代码:

#include <SPI.h>#include <Wire.h>#include <Adafruit_GFX.h>#include <Adafruit_SSD1306.h>Adafruit_SSD1306 display(4);int resolution[2] = {128, 64}, ball[2] = {20, (resolution[1] / 2)};c***t int PIXEL_SIZE = 8, WALL_WIDTH = 4, PADDLE_WIDTH = 4, BALL_SIZE = 4, SPEED = 3;int playerScore = 0, aiScore = 0, playerPos = 0, aiPos = 0;char ballDirectionHori = 'R', ballDirectionVerti = 'S';boolean inProgress = true;void setup() { display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.display();}void loop() { if (aiScore > 9 || playerScore > 9) { // check game state inProgress = false; } if (inProgress) { eraseScore(); eraseBall(ball[0], ball[1]); if (ballDirectionVerti == 'U') { // move ball up diagonally ball[1] = ball[1] - SPEED; } if (ballDirectionVerti == 'D') { // move ball down diagonally ball[1] = ball[1] + SPEED; } if (ball[1] <= 0) { // bounce the ball off the top ballDirectionVerti = 'D'; } if (ball[1] >= resolution[1]) { // bounce the ball off the bottom ballDirectionVerti = 'U'; } if (ballDirectionHori == 'R') { ball[0] = ball[0] + SPEED; // move ball if (ball[0] >= (resolution[0] - 6)) { // ball is at the AI edge of the screen if ((aiPos + 12) >= ball[1] && (aiPos - 12) <= ball[1]) { // ball hits AI paddle if (ball[1] > (aiPos + 4)) { // deflect ball down ballDirectionVerti = 'D'; } else if (ball[1] < (aiPos - 4)) { // deflect ball up ballDirectionVerti = 'U'; } else { // deflect ball straight ballDirectionVerti = 'S'; } // change ball direction ballDirectionHori = 'L'; } else { // GOAL! ball[0] = 6; // move ball to other side of screen ballDirectionVerti = 'S'; // reset ball to straight travel ball[1] = resolution[1] / 2; // move ball to middle of screen ++playerScore; // increase player score } } } if (ballDirectionHori == 'L') { ball[0] = ball[0] - SPEED; // move ball if (ball[0] <= 6) { // ball is at the player edge of the screen if ((playerPos + 12) >= ball[1] && (playerPos - 12) <= ball[1]) { // ball hits player paddle if (ball[1] > (playerPos + 4)) { // deflect ball down ballDirectionVerti = 'D'; } else if (ball[1] < (playerPos - 4)) { // deflect ball up ballDirectionVerti = 'U'; } else { // deflect ball straight ballDirectionVerti = 'S'; } // change ball direction ballDirectionHori = 'R'; } else { ball[0] = resolution[0] - 6; // move ball to other side of screen ballDirectionVerti = 'S'; // reset ball to straight travel ball[1] = resolution[1] / 2; // move ball to middle of screen ++aiScore; // increase AI score } } } drawBall(ball[0], ball[1]); erasePlayerPaddle(playerPos); playerPos = ****ogRead(A2); // read player potentiometer playerPos = map(playerPos, 0, 1023, 8, 54); // convert value from 0 - 1023 to 8 - 54 drawPlayerPaddle(playerPos); moveAi(); drawNet(); drawScore(); } else { // somebody has won display.clearDisplay(); display.setTextSize(4); display.setTextColor(WHITE); display.setCursor(0, 0); // figure out who if (aiScore > playerScore) { display.println("YOU LOSE!"); } else if (playerScore > aiScore) { display.println("YOU WIN!"); } } display.display();}void moveAi() { // move the AI paddle eraseAiPaddle(aiPos); if (ball[1] > aiPos) { ++aiPos; } else if (ball[1] < aiPos) { --aiPos; } drawAiPaddle(aiPos);}void drawScore() { // draw AI and player scores display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(45, 0); display.println(playerScore); display.setCursor(75, 0); display.println(aiScore);}void eraseScore() { // erase AI and player scores display.setTextSize(2); display.setTextColor(BLACK); display.setCursor(45, 0); display.println(playerScore); display.setCursor(75, 0); display.println(aiScore);}void drawNet() { for (int i = 0; i < (resolution[1] / WALL_WIDTH); ++i) { drawPixel(((resolution[0] / 2) - 1), i * (WALL_WIDTH) + (WALL_WIDTH * i), WALL_WIDTH); }}void drawPixel(int posX, int posY, int dimensi***) { // draw group of pixels for (int x = 0; x < dimensi***; ++x) { for (int y = 0; y < dimensi***; ++y) { display.drawPixel((posX + x), (posY + y), WHITE); } }}void erasePixel(int posX, int posY, int dimensi***) { // erase group of pixels for (int x = 0; x < dimensi***; ++x) { for (int y = 0; y < dimensi***; ++y) { display.drawPixel((posX + x), (posY + y), BLACK); } }}void erasePlayerPaddle(int row) { erasePixel(0, row - (PADDLE_WIDTH * 2), PADDLE_WIDTH); erasePixel(0, row - PADDLE_WIDTH, PADDLE_WIDTH); erasePixel(0, row, PADDLE_WIDTH); erasePixel(0, row + PADDLE_WIDTH, PADDLE_WIDTH); erasePixel(0, row + (PADDLE_WIDTH + 2), PADDLE_WIDTH);}void drawPlayerPaddle(int row) { drawPixel(0, row - (PADDLE_WIDTH * 2), PADDLE_WIDTH); drawPixel(0, row - PADDLE_WIDTH, PADDLE_WIDTH); drawPixel(0, row, PADDLE_WIDTH); drawPixel(0, row + PADDLE_WIDTH, PADDLE_WIDTH); drawPixel(0, row + (PADDLE_WIDTH + 2), PADDLE_WIDTH);}void drawAiPaddle(int row) { int column = resolution[0] - PADDLE_WIDTH; drawPixel(column, row - (PADDLE_WIDTH * 2), PADDLE_WIDTH); drawPixel(column, row - PADDLE_WIDTH, PADDLE_WIDTH); drawPixel(column, row, PADDLE_WIDTH); drawPixel(column, row + PADDLE_WIDTH, PADDLE_WIDTH); drawPixel(column, row + (PADDLE_WIDTH * 2), PADDLE_WIDTH);}void eraseAiPaddle(int row) { int column = resolution[0] - PADDLE_WIDTH; erasePixel(column, row - (PADDLE_WIDTH * 2), PADDLE_WIDTH); erasePixel(column, row - PADDLE_WIDTH, PADDLE_WIDTH); erasePixel(column, row, PADDLE_WIDTH); erasePixel(column, row + PADDLE_WIDTH, PADDLE_WIDTH); erasePixel(column, row + (PADDLE_WIDTH * 2), PADDLE_WIDTH);}void drawBall(int x, int y) { display.drawCircle(x, y, BALL_SIZE, WHITE);}void eraseBall(int x, int y) { display.drawCircle(x, y, BALL_SIZE, BLACK);}

以下是你的结局:

OLED Pong

一旦您对代码有信心,就可以进行许多修改:

  • 为难度等级添加菜单(更改AI和球速)。
  • 为球或AI添加一些随机移动。
  • 给两个人再加一个罐子。
  • 添加暂停按钮。

现在来看一下这些复古游戏Pi零项目。

你用这个密码给乒乓球编码了吗?你做了什么修改?让我知道在下面的评论,我想看看一些图片!

  • 发表于 2021-03-16 17:37
  • 阅读 ( 235 )
  • 分类:IT

你可能感兴趣的文章

6个显示器从arduino输出数据

所以,你有一个Arduino。你已经学会了一些基础知识,也许你已经按照初学者指南开始了。接下来呢? ...

  • 发布于 2021-03-12 19:42
  • 阅读 ( 260 )

arduino入门:初学者指南

... 雷射塔 Midi控制器 带有OLED显示屏的复古游戏 交通灯控制器 ...

  • 发布于 2021-03-14 15:19
  • 阅读 ( 295 )

找不到一个迷你?试试这些替代品!

...部。Sinclair ZX Spectrum Vega Plus型号即将发布,这是一款内置显示屏和USB充电的手持设备。 ...

  • 发布于 2021-03-16 12:04
  • 阅读 ( 198 )

如何建立一个树莓皮游戏男孩和哪里买一个工具包

...:这个设备可以让你控制你的Pi和游戏 PiTFT显示屏:一块320x240像素的2.8英寸TFT电阻触摸屏 PowerBoost 1000充电器 锂聚合物电池 橡胶按钮 音频放大器:Adafruit PAM...

  • 发布于 2021-03-19 13:29
  • 阅读 ( 265 )

便携式retrostone 2模拟了许多复古游戏机

旧的又是新的。至少,在电子游戏领域似乎是这样,复古电子游戏比以往任何时候都更受欢迎。 ...

  • 发布于 2021-03-19 22:44
  • 阅读 ( 151 )

今年你能买到的7款最好的复古游戏机

... 这些机器自带游戏,通常带有两个控制器。通常,也有一种方法复制你自己收藏的复古游戏光盘,进一步扩展游戏库。 ...

  • 发布于 2021-03-22 15:54
  • 阅读 ( 237 )

15个优秀的arduino初学者项目

... 15周末项目:打造一个巨大的led像素显示屏 ...

  • 发布于 2021-03-23 11:30
  • 阅读 ( 358 )

如何把电视变成一个复古游戏系统与树莓皮零

... 如果你有幸拥有一台带有HDMI接口的便携式电视,那么明智的选择是连接pizero的HDMI适配器,并在HDMI接口上绕一圈电缆。 ...

  • 发布于 2021-03-24 05:37
  • 阅读 ( 269 )

5种方法diy黑客你的老任天堂设备到新的东西

... 我们之前已经列出了几个Wiimote黑客,其中包括与Arduino配对驾驶无线电控制的汽车(我们的Arduino入门指南应该有帮助),使用Wiimote作为PC控制器,作为交互式白板,用于手指跟踪,甚至桌面VR。 ...

  • 发布于 2021-03-24 08:31
  • 阅读 ( 214 )

8个令人惊叹的硬件项目,带处理和p5.js

... 像Arduino这样的微控制器和Raspberry Pi这样的单板计算机已经完全改变了电子和原型**的方式。它们都非常适合快速开发,以至于很难决定在项目中使用哪一个。 ...

  • 发布于 2021-03-25 23:49
  • 阅读 ( 271 )
愁肠似盖
愁肠似盖

0 篇文章

相关推荐