Vous êtes-vous déjà demandé combien de temps il vous fallait pour écrire vos propres jeux rétro? À quel point Pong est-il facile à coder pour l'Arduino? Joignez-vous à moi pour vous montrer comment construire une mini console de jeux rétro alimentée par Arduino, et comment coder Pong à partir de zéro. Voici le résultat final:
Construire un plan
C'est un circuit assez simple. Un potentiomètre (pot) contrôlera le jeu, et un écran OLED sera piloté par l'Arduino. Ce sera produit sur une planche à pain, mais vous pouvez en faire un circuit permanent et l'installer dans un étui. Nous avons écrit à propos de recréer Pong Comment recréer le jeu Classic Pong en utilisant Arduino Comment recréer le jeu Classic Pong en utilisant Arduino Pong a été le premier jeu vidéo qui a atteint le marché de masse. Pour la première fois dans l'histoire, le concept d'un «jeu vidéo» a été introduit dans la maison familiale, grâce à l'Atari 2600 -... Lire la suite avant, mais aujourd'hui je vais vous montrer comment écrire le code à partir de zéro, et décomposer chaque partie.
De quoi as-tu besoin
Voici ce dont vous avez besoin:
- 1 x Arduino (n'importe quel modèle)
- Potentiomètre 1 x 10k
- 1 x 0.96 "affichage OLED I2C
- 1 x planche à pain
- Fils mâles et femelles assortis
Diymall 0.96 "Inch I2c IIC Série 128x64 Oled LCD LED Module d'affichage blanc pour Arduino 51 Msp420 Stim32 SCR Diymall 0.96" Inch I2c IIC Série 128x64 Oled LCD LED Module d'affichage blanc pour Arduino 51 Msp420 Stim32 SCR Acheter maintenant Sur Amazon $ 9.99
N'importe quel Arduino devrait fonctionner, alors regardez notre guide d'achat Arduino Guide d'achat: Quel conseil devriez-vous obtenir? Guide d'achat Arduino: Quel conseil devriez-vous obtenir? Il y a tellement de différents types de cartes Arduino là-bas, vous seriez pardonné d'être confus. Lequel devriez-vous acheter pour votre projet? Laissez-nous vous aider, avec ce guide d'achat Arduino! Lire la suite si vous ne savez pas quel modèle acheter.
Ces écrans OLED sont très cool. Ils peuvent généralement être achetés en blanc, bleu, jaune ou un mélange des trois. Ils existent en couleur, mais ils ajoutent un tout autre niveau à la complexité et au coût de ce projet.
Le circuit
C'est un circuit assez simple. Si vous n'avez pas beaucoup d'expérience avec Arduino, consultez ces projets débutants 10 Grands projets Arduino pour les débutants 10 Grands projets Arduino pour les débutants Compléter un projet Arduino vous donne un sentiment de satisfaction pas comme les autres. La plupart des débutants ne savent pas par où commencer, et même les projets de débutants peuvent sembler décourageants. Lire la suite en premier.
C'est ici:
En regardant l'avant du pot, connectez la broche gauche à + 5V et la broche droite à la terre . Connectez la broche du milieu à la broche analogique 0 (A0).
L'affichage OLED est connecté en utilisant le protocole I2C. Connectez VCC et GND à l'Arduino + 5V et à la masse . Connectez SCL à l' analogique cinq ( A5 ). Connectez le SDA à l' analogique 4 ( A4 ). La raison pour laquelle cela est connecté aux broches analogiques est simple; ces broches contiennent les circuits requis pour le protocole I2C. Assurez-vous qu'ils sont correctement connectés et qu'ils ne sont pas traversés. Les broches exactes varient selon le modèle, mais A4 et A5 sont utilisés sur les modèles Nano et Uno. Vérifiez la documentation de la bibliothèque de fils pour votre modèle si vous n'utilisez pas un Arduino ou un Nano.
Test de pot
Téléchargez ce code de test (assurez-vous de sélectionner la carte et le port corrects dans les menus Outils > Carte et Outils > 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(analogRead(A0)); // print the value from the pot delay(500); }
Maintenant, ouvrez le moniteur série (en haut à droite > Serial Monitor ) et tournez le potentiomètre. Vous devriez voir la valeur affichée sur le moniteur série. En sens inverse des aiguilles d'une montre, il doit être complètement à zéro, et complètement dans le sens des aiguilles d'une montre, il doit être 1023 :
Vous ajusterez ceci plus tard, mais pour l'instant c'est bien. Si rien ne se passe ou si la valeur change sans que vous fassiez quoi que ce soit, déconnectez et vérifiez le circuit.
Test OLED
L'affichage OLED est légèrement plus complexe à configurer. Vous devez d'abord installer deux bibliothèques pour piloter l'affichage. Téléchargez les bibliothèques Adafruit_SSD1306 et Adafruit-GFX de Github. Copiez les fichiers dans votre dossier de bibliothèques. Cela varie en fonction de votre système d'exploitation:
- Mac OS: / Utilisateurs / Nom d'utilisateur / Documents / Arduino / libraries
- Linux: / home / Nom d'utilisateur / Sketchbook
- Windows: / Utilisateurs / Arduino / bibliothèques
Téléchargez maintenant une esquisse de test. Allez dans Fichier > Exemples > Adafruit SSD1306 > ssd1306_128x64_i2c . Cela devrait vous donner une grande esquisse contenant beaucoup de graphiques:
Si rien ne se produit après le téléchargement, déconnectez-vous et vérifiez vos connexions. Si les exemples ne figurent pas dans les menus, vous devrez peut-être redémarrer votre IDE Arduino.
Le code
Maintenant il est temps pour le code. Je vais expliquer chaque étape, alors passez à la fin si vous voulez juste que ça marche. C'est une bonne quantité de code, donc si vous ne vous sentez pas confiant, consultez ces 10 ressources gratuites Apprenez à coder: 10 ressources en ligne gratuites et fantastiques pour perfectionner vos compétences Apprenez à coder: 10 ressources en ligne gratuites et fantastiques Codage des compétences. Un sujet évité par beaucoup. Il existe une abondance de ressources et d'outils gratuits, tous disponibles en ligne. Bien sûr, vous pourriez prendre quelques cours sur le sujet à proximité ... Lire la suite pour apprendre à coder.
Commencez par inclure les bibliothèques nécessaires:
#include #include #include #include
SPI et WIRE sont deux bibliothèques Arduino pour gérer la communication I2C. Adafruit_GFX et Adafruit_SSD1306 sont les bibliothèques que vous avez installées précédemment.
Ensuite, configurez l'affichage:
Adafruit_SSD1306 display(4);
Ensuite, configurez toutes les variables nécessaires pour lancer le jeu:
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;
Ceux-ci stockent toutes les données nécessaires pour lancer le jeu. Certains d'entre eux stockent l'emplacement de la balle, la taille de l'écran, l'emplacement du joueur et ainsi de suite. Remarquez comment certaines d'entre elles sont const, ce qui signifie qu'elles sont constantes et ne changeront jamais. Cela permet au compilateur Arduino d'accélérer les choses.
La résolution de l'écran et l'emplacement de la balle sont stockés dans des tableaux . Les tableaux sont des collections de choses similaires, et pour la balle, stocker les coordonnées ( X et Y ). Accéder aux éléments dans les tableaux est facile (n'incluez pas ce code dans votre fichier):
resolution[1];
Lorsque les tableaux commencent à zéro, cela retourne le deuxième élément dans le tableau de résolution ( 64 ). La mise à jour des éléments est encore plus facile (encore une fois, n'incluez pas ce code):
ball[1] = 15;
À l'intérieur de void setup (), configurez l'affichage:
void setup() { display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.display(); }
La première ligne indique à la bibliothèque Adafruit les dimensions et le protocole de communication utilisés par votre écran (dans ce cas, 128 x 64 et I2C ). La deuxième ligne ( display.display () ) indique à l'écran d'afficher tout ce qui est stocké dans le tampon (ce qui n'est rien).
Créez deux méthodes appelées drawBall et 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); }
Ceux-ci prennent les coordonnées x et y de la boule et la dessinent sur l'écran en utilisant la méthode drawCircle des bibliothèques d'affichage. Cela utilise la constante BALL_SIZE définie précédemment. Essayez de changer cela et voyez ce qui se passe. Cette méthode drawCircle accepte une couleur de pixel - BLACK ou WHITE . Comme il s'agit d'un affichage monochrome (une couleur), le blanc équivaut à un pixel activé et le noir éteint le pixel.
Maintenant, créez une méthode appelée moveAi :
void moveAi() { eraseAiPaddle(aiPos); if (ball[1]>aiPos) { ++aiPos; } else if (ball[1]< aiPos) { --aiPos; } drawAiPaddle(aiPos); }
Cette méthode gère le déplacement de l' intelligence artificielle ou du joueur IA . C'est un adversaire informatique assez simple - Si la balle est au-dessus de la pagaie, remontez. C'est au dessous de la pagaie, descendez. Assez simple, mais ça marche bien. Les symboles d'incrémentation et de décrémentation sont utilisés ( ++ aiPos et -aiPos ) pour ajouter ou soustraire un de l'aiPosition. Vous pouvez ajouter ou soustraire un plus grand nombre pour que l'IA se déplace plus vite, et soit donc plus difficile à battre. Voici comment vous feriez cela:
aiPos += 2;
Et:
aiPos -= 2;
Les signes Plus égal à et Moins égal à sont des raccourcis pour ajouter ou soustraire deux de / à la valeur actuelle de aiPos. Voici une autre façon de faire cela:
aiPos = aiPos + 2;
et
aiPos = aiPos - 1;
Remarquez comment cette méthode efface d'abord la palette, puis la dessine à nouveau. Cela doit être fait comme ça. Si la nouvelle position de la palette était dessinée, il y aurait deux palettes superposées sur l'écran.
La méthode drawNet utilise deux boucles pour dessiner le net:
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); } }
Cela utilise les variables WALL_WIDTH pour définir sa taille.
Créez des méthodes appelées drawPixels et erasePixels . Tout comme les méthodes à billes, la seule différence entre les deux est la couleur des 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); } } }
Encore une fois, ces deux méthodes utilisent deux boucles pour dessiner un groupe de pixels. Plutôt que d'avoir à dessiner chaque pixel en utilisant la méthode drawPixel des bibliothèques, les boucles dessinent un groupe de pixels basé sur les dimensions données.
La méthode drawScore utilise les fonctions de texte de la bibliothèque pour écrire le score du joueur et de l'IA sur l'écran. Ceux-ci sont stockés dans playerScore et aiScore :
void drawScore() { display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(45, 0); display.println(playerScore); display.setCursor(75, 0); display.println(aiScore); }
Cette méthode a également une contrepartie eraseScore, qui définit les pixels en noir ou off.
Les quatre dernières méthodes sont très similaires. Ils dessinent et effacent les pagaies du joueur et de l'IA:
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); }
Notez comment ils appellent la méthode erasePixel créer plus tôt. Ces méthodes dessinent et effacent la palette appropriée.
Il y a un peu plus de logique dans la boucle principale. Voici le code entier:
#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); }
Voici ce que vous finissez avec:
Une fois que vous avez confiance dans le code, vous pouvez effectuer de nombreuses modifications:
- Ajouter un menu pour les niveaux de difficulté (changer l'IA et la vitesse de la balle).
- Ajouter un mouvement aléatoire à la balle ou AI.
- Ajouter un autre pot pour deux joueurs.
- Ajouter un bouton de pause.
Jetez un coup d'œil à ces projets rétro Pi Zero 5 Retro Gaming Projects au Raspberry Pi Zero 5 Retro Gaming Projects au Raspberry Pi Zero Le Raspberry Pi Zero a pris d'assaut le monde DIY et homebrew, permettant de revoir de vieux projets et inspirer les nouveaux venus, en particulier dans l'esprit enfiévré des fans de jeux rétro. Lire la suite .
Avez-vous codé Pong en utilisant ce code? Quelles modifications avez-vous faites? Faites-moi savoir dans les commentaires ci-dessous, j'aimerais regarder quelques photos!