Arduino Retro Gaming med en OLED-skärm

Någonsin undrat hur mycket arbete det tar att skriva egna retrospel? Hur lätt är Pong att koda för Arduino?

Någonsin undrat hur mycket arbete det tar att skriva egna retrospel?  Hur lätt är Pong att koda för Arduino?
Annons

Någonsin undrat hur mycket arbete det tar att skriva egna retrospel? Hur lätt är Pong att koda för Arduino? Följ med mig när jag visar dig hur man bygger en Arduino-driven mini retro spelkonsol, och hur man kodar Pong från början. Här är slutresultatet:

Byggplan

Detta är en ganska enkel krets. En potentiometer (potten) kommer att styra spelet, och en OLED-skärm kommer att drivas av Arduino. Detta kommer att produceras på en brödbräda, men du kanske vill göra det till en permanent krets och installera den i ett fall. Vi har skrivit om att återskapa Pong Hur man återskapar det klassiska Pong-spelet med Arduino Hur man återskapar det klassiska Pong-spelet Använda Arduino Pong var det första videospel som nått massmarknaden. För första gången i historien togs begreppet "videospel" in i familjen hem, tack vare Atari 2600 -... Läs mer innan, men idag kommer jag att visa dig hur man skriver koden från början, och bryta ner varje del.

Vad du behöver

Retro Arduino Setup

Här är vad du behöver:

  • 1 x Arduino (någon modell)
  • 1 x 10k Potentiometer
  • 1 x 0, 96 "I2C OLED Display
  • 1 x brödbräda
  • Assorterad hane> Hankopplingskablar

Diymall 0.96 "Inch I2c IIC Seriell 128x64 Oled LCD LED Vit Skärm Modul för Arduino 51 Msp420 Stim32 SCR Diymall 0.96" Inch I2c IIC Seriell 128x64 Oled LCD LED Vit Skärm Modul för Arduino 51 Msp420 Stim32 SCR Köp Nu På Amazon $ 9.99

Alla Arduino ska fungera, så titta på vår köpguide Arduino Köpguide: Vilket styrelse ska du få? Arduino Köpguide: Vilket styrelse ska du få? Det finns så många olika typer av Arduino brädor där ute, du skulle bli förlåtad för att vara förvirrad. Vilket ska du köpa för ditt projekt? Låt oss hjälpa till med den här Arduino-köpguiden! Läs mer om du inte är säker på vilken modell du vill köpa.

Dessa OLED-skärmar är väldigt coola. De kan vanligtvis köpas i vit, blå, gul eller en blandning av de tre. De existerar i fullfärg, men dessa lägger en helt annan nivå till komplexiteten och kostnaden för projektet.

Kretskortet

Det här är en ganska enkel krets. Om du inte har mycket erfarenhet av Arduino, kolla in dessa nybörjareprojekt. 10 Stora Arduino-projekt för nybörjare. 10 Stora Arduino-projekt för nybörjare. Att slutföra ett Arduino-projekt ger dig en känsla av tillfredsställelse som ingen annan. De flesta nybörjare är inte säkra på var man ska börja, och även nybörjareprojekt kan verka ganska skrämmande. Läs mer först.

Här är det:

Pongbrödbräda

Titta på krukans framsida, koppla vänster stift till + 5V och höger stift till jord . Anslut mittstiftet till analoga stift 0 (A0).

OLED-skärmen är ansluten med hjälp av I2C-protokollet. Anslut VCC och GND till Arduino + 5V och jord . Anslut SCL till analoga fem ( A5 ). Anslut SDA till analog 4 ( A4 ). Anledningen till att detta är anslutet till analoga stiften är enkelt; Dessa stiften innehåller de kretsar som krävs för I2C-protokollet. Se till att dessa är korrekt anslutna och inte korsade. De exakta stiften varierar beroende på modell, men A4 och A5 används på Nano och Uno. Kontrollera dokumentationen för trådbiblioteket för din modell om du inte använder en Arduino eller Nano.

Pottest

Ladda upp denna testkod (se till att du väljer rätt kartong och port från Verktyg > Styrelse och Verktyg > Portmenyer):

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(analogRead(A0)); // print the value from the pot delay(500); } 

Öppna nu seriell bildskärm ( Top Right > Serial Monitor ) och vrid krukan. Du bör se det värde som visas på seriell bildskärm. Helt moturs bör vara noll och helt medurs ska vara 1023 :

Pong seriell bildskärm

Du kommer att justera detta senare, men för nu är det bra. Om inget händer, eller värdet ändras utan att du gör någonting, koppla loss och dubbelkontrollera kretsen.

OLED-test

OLED-grafik

OLED-skärmen är lite mer komplex att konfigurera. Du måste installera två bibliotek för att driva displayen först. Ladda ner Adafruit_SSD1306 och Adafruit-GFX-bibliotek från Github. Kopiera filerna till bibliotekets mapp. Detta varierar beroende på ditt operativsystem:

  • Mac OS: / Användare / Användarnamn / Dokument / Arduino / Bibliotek
  • Linux: / home / Användarnamn / Sketchbook
  • Windows: / Användare / Arduino / Bibliotek

Ladda nu upp en testskissa. Gå till Arkiv > Exempel > Adafruit SSD1306 > ssd1306_128x64_i2c . Detta borde ge dig en stor skiss som innehåller mycket grafik:

OLED-grafik

Om inget händer efter uppladdning, koppla ifrån och dubbelkontrollera dina anslutningar. Om exemplen inte finns i menyerna kan du behöva starta om din Arduino IDE.

Koden

Nu är det dags för koden. Jag kommer att förklara varje steg, så hoppa till slutet om du bara vill få den att springa. Det här är en hel del kod, så om du inte känner dig säker, kolla in de 10 gratis resurserna Lär dig att koda: 10 gratis och fantastiska online-resurser för att fånga dina färdigheter Lär dig att koda: 10 gratis och fantastiska online-resurser för att fånga din Färdighetskodning. Ett ämne som undviks av många. Det finns ett överflöd av gratis resurser och verktyg, som alla är tillgängliga online. Visst kan du ta några kurser på ämnet på en närliggande ... Läs mer för att lära sig att koda.

Börja med att inkludera nödvändiga bibliotek:

 #include #include #include #include 

SPI och WIRE är två Arduino-bibliotek för hantering av I2C-kommunikation. Adafruit_GFX och Adafruit_SSD1306 är de bibliotek du installerat tidigare.

Konfigurera sedan skärmen:

 Adafruit_SSD1306 display(4); 

Installera sedan alla variabler som behövs för att köra spelet:

 int resolution[2] = {128, 64}, ball[2] = {20, (resolution[1] / 2)}; const 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; 

Dessa lagrar all information som krävs för att köra spelet. Några av dessa lagrar platsen för bollen, skärmens storlek, platsen för spelaren och så vidare. Lägg märke till hur några av dessa är const menar att de är konstanta och kommer aldrig att förändras. Detta gör att Arduino-kompilatorn snabbar upp saker och ting.

Skärmens upplösning och bollplats lagras i arrays . Arrays är samlingar av liknande saker, och för bollen, lagra koordinaterna ( X och Y ). Åtkomst till element i arrayer är enkelt (inkludera inte denna kod i din fil):

 resolution[1]; 

När arrays startar vid noll kommer detta att returnera det andra elementet i upplösningsuppsättningen ( 64 ). Uppdatera element är ännu enklare (igen, inkludera inte denna kod):

 ball[1] = 15; 

Innehållsfördröjning (), konfigurera displayen:

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

Den första raden berättar för Adafruit-biblioteket vilka dimensioner och kommunikationsprotokoll din bildskärm använder (i detta fall 128 x 64 och I2C ). Den andra raden ( display.display () ) berättar att skärmen visar vad som lagras i bufferten (vilket är ingenting).

Skapa två metoder som heter drawBall och 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); } 

Dessa tar x- och y- koordinaterna för bollen och ritar den på skärmen med hjälp av drawCircle- metoden från visningsbiblioteken. Detta använder den konstanta BALL_SIZE definierade tidigare. Försök ändra detta och se vad som händer. Denna drawCircle-metod accepterar en pixelfärg - BLACK eller WHITE . Eftersom det här är en monokromatisk bildskärm (en färg), motsvarar vitt en pixel på och svart svänger pixeln av.

Skapa nu en metod som heter moveAi :

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

Denna metod hanterar att flytta artificiell intelligens eller AI- spelare. Det här är en ganska enkel dator motståndare - Om bollen ligger ovanför paddeln, rör dig uppåt. Den är under padden, flytta ner. Ganska enkelt, men det fungerar bra. Symbolerna för ökning och minskning används ( ++ aiPos och -aiPos ) för att lägga till eller subtrahera en från aiPositionen. Du kan lägga till eller subtrahera ett större antal för att AI ska röra sig snabbare och därför vara svårare att slå. Så här skulle du göra det:

 aiPos += 2; 

Och:

 aiPos -= 2; 

Plus Equals and Minus Equals- tecknen är stenografi för att lägga till eller subtrahera två från / till det aktuella värdet av aiPos. Här är ett annat sätt att göra det:

 aiPos = aiPos + 2; 

och

 aiPos = aiPos - 1; 

Lägg märke till hur den här metoden först raderar padden och dra sedan den igen. Detta måste göras så här. Om den nya positionen av paddeln drogs skulle det finnas två överlappande paddlar på skärmen.

DrawNet- metoden använder två slingor för att rita nätet:

 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); } } 

Detta använder WALL_WIDTH- variablerna för att ställa in storleken.

Skapa metoder som heter drawPixels och erasePixels . Precis som bollmetoderna är den enda skillnaden mellan dessa två färgerna på pixlarna:

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

Återigen använder båda dessa metoder två för loopar för att rita en grupp pixlar. I stället för att behöva rita varje pixel med hjälp av bibliotekets drawPixel- metod, ritar lusarna en grupp pixlar baserat på de givna dimensionerna.

DrawScore- metoden använder textfunktionerna i biblioteket för att skriva spelarens och AI-poäng till skärmen. Dessa lagras i playerScore och aiScore :

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

Den här metoden har också en eraseScore- motsvarighet, som ställer in pixlarna till svart eller av.

De sista fyra metoderna är mycket lika. De ritar och raderar spelaren och AI paddlarna:

 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); } 

Lägg märke till hur de kallar erasePixel- metoden skapa tidigare. Dessa metoder ritar och tar bort lämplig paddla.

Det finns lite mer logik i huvudslingan. Här är hela koden:

 #include #include #include #include Adafruit_SSD1306 display(4); int resolution[2] = {128, 64}, ball[2] = {20, (resolution[1] / 2)}; const 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] = 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) (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] = ball[1] && (playerPos - 12) (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 = analogRead(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 dimensions) { // draw group of pixels for (int x = 0; x< dimensions; ++x) { for (int y = 0; y< dimensions; ++y) { display.drawPixel((posX + x), (posY + y), WHITE); } } } void erasePixel(int posX, int posY, int dimensions) { // erase group of pixels for (int x = 0; x< dimensions; ++x) { for (int y = 0; y< dimensions; ++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); } 

Här är vad du hamnar med:

OLED Pong

När du är säker på koden finns det många modifieringar du kan göra:

  • Lägg till en meny för svårighetsgrader (ändra AI och bollhastighet).
  • Lägg till lite slumpmässig rörelse till bollen eller AI.
  • Lägg till en annan gryta för två spelare.
  • Lägg till en pausknapp.

Ta en titt på dessa retrospel Pi Zero-projekt 5 Retro Gaming Projekt med Raspberry Pi Zero 5 Retro Gaming Projekt med Raspberry Pi Zero Raspberry Pi Zero har tagit DIY och Homebrew World med storm, vilket gör det möjligt att revidera gamla projekt och inspirerande nykomlingar, i synnerhet i retrospelsfansens feverade sinnen. Läs mer .

Har du kodat Pong med den här koden? Vilka ändringar gjorde du? Låt mig veta i kommentarerna nedan, skulle jag gärna vilja se några bilder!

In this article