有沒有想過要花多少功夫才能寫出自己的復古遊戲?Pong為Arduino編寫代碼有多容易?加入我,我告訴你如何建立一個Arduino供電的迷你復古遊戲控制檯,以及如何從頭開始編碼乒乓球。最終結果如下:
這是一個相當簡單的電路。電位計(pot)將控制遊戲,而OLED顯示器將由Arduino驅動。這將產生在一個試驗板,但你可能希望這是一個永久性的電路,並安裝在一個案件。我們以前寫過關於重新創建Pong的內容,但是今天我將向您展示如何從頭開始編寫代碼,並分解每個部分。
以下是您需要的:
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沒有太多的經驗,請先看看這些初學者的項目。
在這裡:
看著鍋前,將左銷連接到+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:
稍後你會調整這個,但現在還可以。如果什麼也沒發生,或者數值沒有做任何改變,斷開並重新檢查電路。
OLED顯示器的配置稍微複雜一些。首先需要安裝兩個庫來驅動顯示器。從Github下載Adafruit\usssd1306和Adafruit GFX庫。將文件複製到庫文件夾中。這取決於您的操作系統:
現在上傳一個測試草圖。轉到“文件”>;“示例”>;“Adafruit SSD1306”>;“SSD1306\U 128x64\U i2c”。這將為您提供包含大量圖形的大型草圖:
如果上傳後什麼也沒發生,請斷開連接並重新檢查連接。如果菜單中沒有示例,則可能需要重新啟動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);}以下是你的結局:
一旦您對代碼有信心,就可以進行許多修改:
現在來看一下這些復古遊戲Pi零項目。
你用這個密碼給乒乓球編碼了嗎?你做了什麼修改?讓我知道在下面的評論,我想看看一些圖片!
...的任天堂交換機即將問世。正如我們所報道的,據傳一款帶有OLED顯示屏的開關將在2021年底釋出。 ...
...(在數字時代之前),但坐式遊戲機也很流行。基本上是帶有玻璃表面的桌子和一個朝上的顯示器,它們的每側都有一個操縱桿,供兩人操作。 ...
所以,你有一個Arduino。你已經學會了一些基礎知識,也許你已經按照初學者指南開始了。接下來呢? ...
Arduino是一個開源的電子產品原型平臺,它是世界上最流行的平臺之一——除了Raspberry Pi之外。銷量超過300萬臺(更多的是以第三方克隆裝置的形式):是什麼讓它這麼好,你能用它做什麼? ...
... 例如,一臺帶有外設的Commodore64可以賣到70美元以上,這比一臺便宜的平板電腦還貴。英國的ZX81頻譜(樹莓Pi的精神先驅)、世嘉Megadrive以及20世紀80年代末和90年代初的其他裝置也是...
...但他們仍然可以找到復古遊戲系統。你甚至可能擁有一臺帶有CRT顯示器的街機,或者僅僅擁有一臺你仍在使用的舊CRT電視。 ...
...:這個裝置可以讓你控制你的Pi和遊戲 PiTFT顯示屏:一塊320x240畫素的2.8英寸TFT電阻觸控式螢幕 PowerBoost 1000充電器 鋰聚合物電池 橡膠按鈕 音訊放大器:Adafru...
... 可拆卸4000毫安時電池 640X480解析度顯示器 內建揚聲器 3.5毫米耳機插孔 ...