Arduino Retro Gaming met een OLED-scherm

Ooit afgevraagd hoeveel werk het kost om uw eigen retro-games te schrijven? Hoe eenvoudig is Pong om te coderen voor de Arduino?

Ooit afgevraagd hoeveel werk het kost om uw eigen retro-games te schrijven?  Hoe eenvoudig is Pong om te coderen voor de Arduino?
Advertentie

Ooit afgevraagd hoeveel werk het kost om uw eigen retro-games te schrijven? Hoe eenvoudig is Pong om te coderen voor de Arduino? Doe met me mee, terwijl ik je laat zien hoe je een Arduino mini-gameconsole met afstandsbediening bouwt, en hoe je Pong vanuit het niets kunt coderen. Dit is het eindresultaat:

Bouw plan

Dit is een vrij eenvoudig circuit. Een potmeter (pot) bestuurt het spel en een OLED-display wordt aangedreven door de Arduino. Dit wordt geproduceerd op een breadboard, maar misschien wilt u dit een permanent circuit maken en het in een hoes installeren. We hebben geschreven over het opnieuw creëren van Pong Hoe het klassieke pongspel te recreëren met behulp van Arduino Hoe het klassieke pongspel te recreëren met behulp van Arduino Pong was de allereerste videogame die de massamarkt bereikte. Voor het eerst in de geschiedenis werd het concept van een "videogame" in het ouderlijk huis gebracht, dankzij de Atari 2600 -... Lees meer voor, maar vandaag laat ik je zien hoe je de code helemaal opnieuw kunt schrijven, en elk onderdeel afbreken.

Wat je nodig hebt

Retro Arduino-installatie

Dit is wat je nodig hebt:

  • 1 x Arduino (elk model)
  • 1 x 10k Potentiometer
  • 1 x 0, 96 "I2C OLED-scherm
  • 1 x breadboard
  • Geassorteerde mannelijke> mannelijke aansluitdraden

Diymall 0, 96 "Inch I2c IIC Serial 128x64 Oled LCD LED witte displaymodule voor Arduino 51 Msp420 Stim32 SCR Diymall 0, 96" Inch I2c IIC Serial 128x64 Oled LCD-LED witte displaymodule voor Arduino 51 Msp420 Stim32 SCR Nu kopen bij Amazon $ 9, 99

Elke Arduino zou moeten werken, dus kijk eens naar onze koopgids. Arduino Koopwijzer: welk bord moet je kopen? Arduino Koopwijzer: welk bord moet je kopen? Er zijn zoveel verschillende soorten Arduino-boards, dat je zou worden vergeven dat je in de war was. Welke moet je kopen voor je project? Laat ons helpen, met deze Arduino koopgids! Meer lezen als u niet zeker weet welk model u moet kopen.

Deze OLED-schermen zijn erg cool. Ze kunnen meestal worden gekocht in wit, blauw, geel of een mengsel van de drie. Ze bestaan ​​in kleur, maar deze voegen een heel ander niveau toe aan de complexiteit en kosten van dit project.

Het circuit

Dit is een vrij eenvoudig circuit. Als je niet veel ervaring hebt met Arduino, bekijk dan deze beginnersprojecten. 10 Great Arduino Projects for Beginners 10 Great Arduino Projects for Beginners Het voltooien van een Arduino-project geeft je een gevoel van voldoening als geen ander. De meeste beginners weten echter niet waar ze moeten beginnen, en zelfs projecten voor beginners kunnen behoorlijk intimiderend lijken. Lees meer eerst.

Hier is het:

Pong-breadboard

Kijkend naar de voorkant van de pot, verbindt u de linkerpin met + 5V en de rechterpin met aarde . Verbind de middelste pin met analoge pin 0 (A0).

Het OLED-display is aangesloten via het I2C-protocol. Verbind VCC en GND met de Arduino + 5V en aarde . Verbind SCL met analoog vijf ( A5 ). Verbind SDA met analoog 4 ( A4 ). De reden dat dit is verbonden met de analoge pinnen is eenvoudig; deze pinnen bevatten de circuits die vereist zijn voor het I2C-protocol. Zorg ervoor dat deze correct zijn aangesloten en niet zijn overgestoken. De exacte pinnen verschillen per model, maar A4 en A5 worden gebruikt op de Nano en Uno. Raadpleeg de documentatie bij de draadbibliotheek voor uw model als u geen Arduino of Nano gebruikt.

Potentest

Upload deze testcode (selecteer het juiste forum en de juiste poort in het menu Hulpmiddelen > Bord en Extra > Poort ):

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

Open nu de seriële monitor ( rechtsboven > seriële monitor ) en draai de pot. U zou de waarde op het seriële scherm moeten zien. Volledig tegen de klok in moet nul zijn en volledig met de klok mee zou 1023 moeten zijn:

Pong seriële monitor

Je zult dit later aanpassen, maar voorlopig is het prima. Als er niets gebeurt, of de waarde verandert zonder dat u iets doet, ontkoppel en controleer dan het circuit.

OLED-test

OLED-afbeeldingen

Het OLED-scherm is iets complexer om te configureren. U moet twee bibliotheken installeren om als eerste met het beeldscherm te werken. Download de Adafruit_SSD1306- en Adafruit-GFX-bibliotheken van Github. Kopieer de bestanden naar uw bibliothekenmap. Dit varieert afhankelijk van uw besturingssysteem:

  • Mac OS: / Gebruikers / Gebruikersnaam / Documenten / Arduino / bibliotheken
  • Linux: / home / gebruikersnaam / schetsboek
  • Windows: / Users / Arduino / libraries

Upload nu een testschets. Ga naar Bestand > Voorbeelden > Adafruit SSD1306 > ssd1306_128x64_i2c . Dit zou je een grote schets moeten opleveren met veel afbeeldingen:

OLED-afbeeldingen

Als er niets gebeurt na het uploaden, ontkoppel en controleer je je verbindingen. Als de voorbeelden niet in de menu's staan, moet u wellicht uw Arduino IDE opnieuw opstarten.

De code

Nu is het tijd voor de code. Ik zal elke stap uitleggen, dus ga door tot het einde als je het gewoon wilt laten draaien. Dit is een behoorlijke hoeveelheid code, dus als je geen zelfvertrouwen hebt, bekijk dan deze 10 gratis bronnen. Learn To Code: 10 gratis en fantastische online bronnen om je vaardigheden te verbeteren Leren coderen: 10 gratis en fantastische online bronnen om je te verbeteren Vaardigheden coderen. Een onderwerp dat door velen wordt vermeden. Er is een overvloed aan gratis hulpmiddelen en hulpmiddelen, die allemaal online beschikbaar zijn. Natuurlijk kun je wat cursussen over dit onderwerp volgen in een nabijgelegen ... Lees meer om te leren coderen.

Begin met het opnemen van de benodigde bibliotheken:

 #include #include #include #include 

SPI en WIRE zijn twee Arduino-bibliotheken voor het verwerken van de I2C-communicatie. Adafruit_GFX en Adafruit_SSD1306 zijn de bibliotheken die u eerder hebt geïnstalleerd.

Configureer vervolgens het scherm:

 Adafruit_SSD1306 display(4); 

Stel vervolgens alle variabelen in die nodig zijn om het spel uit te voeren:

 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; 

Deze slaan alle gegevens op die nodig zijn om het spel uit te voeren. Sommige hiervan slaan de locatie van de bal op, de grootte van het scherm, de locatie van de speler, enzovoort. Merk op hoe sommige van deze const zijn, wat betekent dat ze constant zijn en nooit zullen veranderen. Hierdoor kan de Arduino-compiler dingen versnellen.

De schermresolutie en de ballocatie worden opgeslagen in arrays . Arrays zijn verzamelingen met soortgelijke dingen en bewaar voor de bal de coördinaten ( X en Y ). Toegang tot elementen in arrays is eenvoudig (voeg deze code niet toe aan uw bestand):

 resolution[1]; 

Naarmate arrays bij nul beginnen, zal dit het tweede element in de resolutie-array ( 64 ) retourneren. Het bijwerken van elementen is nog eenvoudiger (nogmaals, neem deze code niet op):

 ball[1] = 15; 

Inside void setup (), configureer de display:

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

De eerste regel vertelt de Adafruit-bibliotheek welk dimensies en communicatieprotocol uw display gebruikt (in dit geval 128 x 64 en I2C ). De tweede regel ( display.display () ) vertelt het scherm om te laten zien wat er in de buffer is opgeslagen (wat niets is).

Maak twee methoden genaamd drawBall en 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); } 

Deze nemen de x- en y- coördinaten van de bal en tekenen deze op het scherm met de methode drawCircle uit de weergavebibliotheken. Hierbij wordt de eerder gedefinieerde constante BALL_SIZE gebruikt. Probeer dit te veranderen en kijk wat er gebeurt. Deze drawCircle-methode accepteert een pixelkleur - ZWART of WIT . Omdat dit een monochroom display (één kleur) is, komt wit overeen met een pixel die aan staat en zwart zet de pixel uit.

Maak nu een methode genaamd moveAi :

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

Met deze methode kun je de kunstmatige intelligentie of AI- speler verplaatsen. Dit is een vrij eenvoudige computertegenstander. Als de bal zich boven de peddel bevindt, ga dan omhoog. Het is onder de peddel, ga naar beneden. Heel eenvoudig, maar het werkt goed. De symbolen voor ophogen en afnemen worden gebruikt ( ++ aiPos en -aiPos ) om een ​​toe te voegen of af te trekken van de aiPosition. Je zou een groter getal kunnen optellen of aftrekken om de AI sneller te laten bewegen en daarom moeilijker te verslaan zijn. Hier is hoe je dat zou doen:

 aiPos += 2; 

En:

 aiPos -= 2; 

De tekens Plus Equals en Minus-equals zijn kort voor het optellen of aftrekken van twee van / tot de huidige waarde van aiPos. Hier is een andere manier om dat te doen:

 aiPos = aiPos + 2; 

en

 aiPos = aiPos - 1; 

Merk op hoe deze methode eerst de paddle wist en deze vervolgens opnieuw tekent. Dit moet zo worden gedaan. Als de nieuwe positie van de paddle werd getekend, zouden er twee overlappende paddles op het scherm verschijnen.

De drawNet- methode gebruikt twee lussen om het net te tekenen:

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

Hiermee worden de variabelen WALL_WIDTH gebruikt om de grootte in te stellen.

Maak methoden met de naam drawPixels en erasePixels . Net als de ballen, is het enige verschil tussen deze twee de kleur van de pixels:

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

Nogmaals, beide methoden gebruiken twee lussen om een ​​groep pixels te tekenen. In plaats van elke pixel te moeten tekenen met behulp van de drawPixel- methode van bibliotheken, tekenen de lussen een groep pixels op basis van de gegeven dimensies.

De drawScore- methode gebruikt de tekstfuncties van de bibliotheek om de speler en de AI-score naar het scherm te schrijven. Deze worden opgeslagen in playerScore en aiScore :

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

Deze methode heeft ook een eraseScore- tegenhanger, die de pixels in zwart zet of uitzet.

De laatste vier methoden lijken erg op elkaar. Ze tekenen en wissen de speler en de AI-paddles:

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

Merk op hoe ze de erasePixel- methode eerder hebben aangemaakt. Deze methoden tekenen en wissen de juiste paddle.

Er zit iets meer logica in de hoofdlus. Hier is de hele code:

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

Dit is waar je uiteindelijk mee eindigt:

OLED Pong

Zodra u zeker bent van de code, zijn er tal van wijzigingen die u kunt aanbrengen:

  • Voeg een menu toe voor moeilijkheidsniveaus (verander AI en balsnelheid).
  • Voeg een willekeurige beweging toe aan de bal of AI.
  • Voeg nog een pot toe voor twee spelers.
  • Voeg een pauzeknop toe.

Bekijk nu deze retro gaming Pi Zero-projecten 5 Retro Gaming-projecten met de Raspberry Pi Zero 5 Retro Gaming-projecten met de Raspberry Pi Zero De Raspberry Pi Zero heeft de DIY en homebrew wereld veroverd, waardoor het mogelijk was om oude projecten te herzien en inspirerende nieuwkomers, vooral in de koortsige geesten van retro-gameliefhebbers. Lees verder .

Heb je Pong gecodeerd met deze code? Welke wijzigingen heb je aangebracht? Laat het me weten in de reacties hieronder, ik wil graag wat foto's lijken!

In this article