帶有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
  • 閱讀 ( 59 )
  • 分類:DIY

你可能感興趣的文章

新的任天堂交換機:我們期待看到的6個改進

...的任天堂交換機即將問世。正如我們所報道的,據傳一款帶有OLED顯示屏的開關將在2021年底釋出。 ...

  • 發佈於 2021-03-10 20:56
  • 閲讀 ( 60 )

9個奇妙的diy遊戲站,你可以在短時間內建立

...(在數字時代之前),但坐式遊戲機也很流行。基本上是帶有玻璃表面的桌子和一個朝上的顯示器,它們的每側都有一個操縱桿,供兩人操作。 ...

  • 發佈於 2021-03-12 18:59
  • 閲讀 ( 54 )

忘了snes經典:5復古控制檯,你可以模仿樹莓皮

...經典的重新發布是多麼成功,它可能會超越遊戲機,進入復古家用電腦的世界。 ...

  • 發佈於 2021-03-12 19:02
  • 閲讀 ( 53 )

6個顯示器從arduino輸出資料

所以,你有一個Arduino。你已經學會了一些基礎知識,也許你已經按照初學者指南開始了。接下來呢? ...

  • 發佈於 2021-03-12 19:42
  • 閲讀 ( 56 )

arduino入門:初學者指南

Arduino是一個開源的電子產品原型平臺,它是世界上最流行的平臺之一——除了Raspberry Pi之外。銷量超過300萬臺(更多的是以第三方克隆裝置的形式):是什麼讓它這麼好,你能用它做什麼? ...

  • 發佈於 2021-03-14 15:19
  • 閲讀 ( 55 )

找不到一個迷你?試試這些替代品!

...槽新增自己的遊戲。控制元件是手持裝置的典型配置,但帶有仿效原始鍵盤的灰色按鈕。 ...

  • 發佈於 2021-03-16 12:04
  • 閲讀 ( 50 )

在銷售復古遊戲機時獲得最大收益

... 例如,一臺帶有外設的Commodore64可以賣到70美元以上,這比一臺便宜的平板電腦還貴。英國的ZX81頻譜(樹莓Pi的精神先驅)、世嘉Megadrive以及20世紀80年代末和90年代初的其他裝置也是...

  • 發佈於 2021-03-17 01:56
  • 閲讀 ( 52 )

如何安全方便地清洗電腦螢幕

...但他們仍然可以找到復古遊戲系統。你甚至可能擁有一臺帶有CRT顯示器的街機,或者僅僅擁有一臺你仍在使用的舊CRT電視。 ...

  • 發佈於 2021-03-18 20:13
  • 閲讀 ( 74 )

如何建立一個樹莓皮遊戲男孩和哪裡買一個工具包

...:這個裝置可以讓你控制你的Pi和遊戲 PiTFT顯示屏:一塊320x240畫素的2.8英寸TFT電阻觸控式螢幕 PowerBoost 1000充電器 鋰聚合物電池 橡膠按鈕 音訊放大器:Adafru...

  • 發佈於 2021-03-19 13:29
  • 閲讀 ( 59 )

行動式retrostone 2模擬了許多復古遊戲機

... 可拆卸4000毫安時電池 640X480解析度顯示器 內建揚聲器 3.5毫米耳機插孔 ...

  • 發佈於 2021-03-19 22:44
  • 閲讀 ( 38 )