Arduino Retro Gaming con una pantalla OLED

¿Alguna vez se preguntó cuánto trabajo se necesita para escribir sus propios juegos retro? ¿Qué tan fácil es Pong para codificar el Arduino?

¿Alguna vez se preguntó cuánto trabajo se necesita para escribir sus propios juegos retro?  ¿Qué tan fácil es Pong para codificar el Arduino?
Anuncio

¿Alguna vez se preguntó cuánto trabajo se necesita para escribir sus propios juegos retro? ¿Qué tan fácil es Pong para codificar el Arduino? Únete a mí y te mostraré cómo construir una consola de juegos retro de Arduino y cómo codificar Pong desde cero. Aquí está el resultado final:

Plan de compilación

Este es un circuito bastante simple. Un potenciómetro (pot) controlará el juego, y una pantalla OLED será manejada por el Arduino. Esto se producirá en una placa de prueba, sin embargo, es posible que desee hacer de este un circuito permanente e instalarlo en una caja. Hemos escrito sobre la recreación de Pong Cómo recrear el juego clásico de Pong con Arduino Cómo recrear el juego clásico de Pong con Arduino Pong fue el primer videojuego que llegó al mercado masivo. Por primera vez en la historia, el concepto de un "videojuego" fue llevado a la casa familiar, gracias al Atari 2600 -... Lea más antes, sin embargo, hoy le mostraré cómo escribir el código desde cero, y desglosando cada parte.

Que necesitas

Configuración retro Arduino

Esto es lo que necesitas:

  • 1 x Arduino (cualquier modelo)
  • Potenciómetro de 1 x 10k
  • Pantalla OLED de 1 x 0.96 "I2C
  • 1 x Tablero
  • Surtido de cables macho y macho de conexión

Diymall 0.96 "pulgadas I2c IIC Serie 128x64 Oled LCD LED Módulo de pantalla blanca para Arduino 51 Msp420 Stim32 SCR Diymall 0.96" pulgadas I2c IIC Serie 128x64 Oled LCD LED Módulo de pantalla blanca para Arduino 51 Msp420 Stim32 SCR Comprar ahora En Amazon $ 9.99

Cualquier Arduino debería funcionar, así que mira nuestra guía de compras Guía de compra de Arduino: ¿Qué placa deberías conseguir? Guía de compra de Arduino: ¿Qué placa debería obtener? Hay tantos tipos diferentes de tableros Arduino que hay, se te perdonaría por estar confundido. ¿Qué debería comprar para su proyecto? ¡Permítanos ayudarlo con esta guía de compra de Arduino! Lea más si no está seguro de qué modelo comprar.

Estas pantallas OLED son geniales. Por lo general, se pueden comprar en blanco, azul, amarillo o una mezcla de los tres. Existen a todo color, sin embargo, estos agregan un nivel completamente diferente a la complejidad y el costo de este proyecto.

El circuito

Este es un circuito bastante simple. Si no tienes mucha experiencia con Arduino, echa un vistazo a estos proyectos para principiantes 10 Grandes proyectos Arduino para principiantes 10 Grandes proyectos Arduino para principiantes Completar un proyecto Arduino te da una sensación de satisfacción como ninguna otra. La mayoría de los principiantes no están seguros de por dónde empezar, e incluso los proyectos para principiantes pueden parecer bastante desalentadores. Lea más primero.

Aquí está:

Pong Breadboard

Mirando la parte frontal de la olla, conecte el pin izquierdo a + 5V y el pin derecho a tierra . Conecte el pin central al pin analógico 0 (A0).

La pantalla OLED se conecta utilizando el protocolo I2C. Conecte VCC y GND a Arduino + 5V y tierra . Conecte SCL al analógico cinco ( A5 ). Conecte SDA al análogo 4 ( A4 ). La razón por la que está conectado a los pines analógicos es simple; estos pines contienen los circuitos necesarios para el protocolo I2C. Asegúrese de que estén conectados correctamente y que no estén cruzados. Los pines exactos variarán según el modelo, pero A4 y A5 se usan en Nano y Uno. Consulte la documentación de la biblioteca Wire para su modelo si no está usando un Arduino o Nano.

Prueba Pot

Cargue este código de prueba (asegúrese de seleccionar la placa y el puerto correctos desde Herramientas > Tablero y Herramientas > Menús de puerto ):

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

Ahora abra el monitor serie ( Superior derecha > Monitor serie ) y gire el potenciómetro. Debería ver el valor que se muestra en el monitor de serie. Totalmente en sentido contrario a las agujas del reloj debería ser cero, y completamente en sentido horario debería ser 1023 :

Monitor serie Pong

Lo ajustarás más adelante, pero por ahora está bien. Si no ocurre nada, o el valor cambia sin que usted haga nada, desconecte y vuelva a verificar el circuito.

Prueba OLED

Gráficos OLED

La pantalla OLED es un poco más compleja de configurar. Necesita instalar dos bibliotecas para controlar la pantalla primero. Descargue las bibliotecas Adafruit_SSD1306 y Adafruit-GFX de Github. Copie los archivos en su carpeta de bibliotecas. Esto varía según su sistema operativo:

  • Mac OS: / Usuarios / Nombre de usuario / Documentos / Arduino / libraries
  • Linux: / home / Nombre de usuario / Sketchbook
  • Windows: / Usuarios / Arduino / libraries

Ahora cargue un boceto de prueba. Vaya a Archivo > Ejemplos > Adafruit SSD1306 > ssd1306_128x64_i2c . Esto debería darle un boceto grande que contiene muchos gráficos:

Gráficos OLED

Si no sucede nada después de la carga, desconecte y vuelva a verificar sus conexiones. Si los ejemplos no están en los menús, puede que necesite reiniciar su Arduino IDE.

El código

Ahora es el momento para el código. Voy a explicar cada paso, así que salte hasta el final si solo quieres que funcione. Esta es una buena cantidad de código, así que si no se siente seguro, consulte estos 10 recursos gratuitos. Aprenda a codificar: 10 recursos en línea gratuitos y fantásticos para perfeccionar sus habilidades Aprenda a codificar: 10 recursos en línea gratuitos y fantásticos para perfeccionar Codificación de habilidades. Un tema que es evitado por muchos. Hay una gran cantidad de herramientas y recursos gratuitos, todos los cuales están disponibles en línea. Claro que podrías tomar algunos cursos sobre el tema en un cercano ... Leer más para aprender a codificar.

Comience por incluir las bibliotecas necesarias:

 #include #include #include #include 

SPI y WIRE son dos bibliotecas Arduino para manejar la comunicación I2C. Adafruit_GFX y Adafruit_SSD1306 son las bibliotecas que instaló anteriormente.

Luego, configure la pantalla:

 Adafruit_SSD1306 display(4); 

Luego configura todas las variables necesarias para ejecutar el juego:

 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; 

Estos almacenan todos los datos necesarios para ejecutar el juego. Algunos almacenan la ubicación de la pelota, el tamaño de la pantalla, la ubicación del jugador, etc. Observe cómo algunos de estos son const, lo que significa que son constantes y nunca cambiarán. Esto permite que el compilador Arduino acelere un poco las cosas.

La resolución de la pantalla y la ubicación de la bola se almacenan en matrices . Las matrices son colecciones de cosas similares, y para la bola, almacena las coordenadas ( X e Y ). Acceder a elementos en matrices es fácil (no incluya este código en su archivo):

 resolution[1]; 

Como las matrices comienzan en cero, esto devolverá el segundo elemento en la matriz de resolución ( 64 ). La actualización de elementos es aún más sencilla (nuevamente, no incluyas este código):

 ball[1] = 15; 

Dentro de la configuración de vacío (), configure la pantalla:

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

La primera línea le dice a la biblioteca de Adafruit qué dimensiones y protocolo de comunicaciones está usando su pantalla (en este caso, 128 x 64 e I2C ). La segunda línea ( display.display () ) le dice a la pantalla que muestre lo que está almacenado en el búfer (que no es nada).

Crea dos métodos llamados drawBall y 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); } 

Estos toman las coordenadas xey de la bola y la dibujan en la pantalla usando el método drawCircle de las bibliotecas de visualización. Esto usa la constante BALL_SIZE definida anteriormente. Intenta cambiar esto y mira lo que sucede. Este método drawCircle acepta un color de píxel: NEGRO o BLANCO . Como se trata de una pantalla monocromática (un color), el blanco equivale a la activación de un píxel y el negro desactiva el píxel.

Ahora crea un método llamado moveAi :

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

Este método maneja mover el Inteligencia Artificial o jugador AI . Este es un oponente informático bastante simple: si la bola está sobre la paleta, suba. Está debajo de la paleta, mueve hacia abajo. Muy simple, pero funciona bien. Los símbolos de incremento y decremento se usan ( ++ aiPos y -aiPos ) para sumar o restar uno de aiPosition. Puede agregar o restar un número mayor para hacer que la IA se mueva más rápido y, por lo tanto, sea más difícil de superar. Así es como lo harías:

 aiPos += 2; 

Y:

 aiPos -= 2; 

Los signos Más Igual y Menos iguales son una abreviatura para sumar o restar dos del / al valor actual de aiPos. Aquí hay otra manera de hacerlo:

 aiPos = aiPos + 2; 

y

 aiPos = aiPos - 1; 

Observe cómo este método primero borra la paleta y luego la dibuja nuevamente. Esto tiene que hacerse así. Si se dibujara la nueva posición de la paleta, habría dos paletas superpuestas en la pantalla.

El método drawNet usa dos bucles para dibujar la red:

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

Utiliza las variables WALL_WIDTH para establecer su tamaño.

Crea métodos llamados drawPixels y erasePixels . Al igual que los métodos de bolas, la única diferencia entre estos dos es el color de los píxeles:

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

De nuevo, ambos métodos utilizan dos bucles for para dibujar un grupo de píxeles. En lugar de tener que dibujar cada píxel utilizando el método drawPixel de las bibliotecas, los bucles dibujan un grupo de píxeles en función de las dimensiones dadas.

El método drawScore utiliza las funciones de texto de la biblioteca para escribir el jugador y la puntuación AI en la pantalla. Estos se almacenan en playerScore y aiScore :

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

Este método también tiene una contraparte eraseScore, que establece los píxeles en negro o apagado.

Los últimos cuatro métodos son muy similares. Dibujan y borran el jugador y las paletas de 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); } 

Observe cómo llaman al método borrarPixel crear antes. Estos métodos dibujan y borran la paleta adecuada.

Hay un poco más de lógica en el ciclo principal. Aquí está el código completo:

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

Esto es con lo que terminas:

Pong OLED

Una vez que tenga confianza con el código, puede realizar numerosas modificaciones:

  • Agregue un menú para los niveles de dificultad (cambio de AI y velocidad de la bola).
  • Agregue un movimiento aleatorio a la bola o AI.
  • Agrega otro bote para dos jugadores.
  • Agrega un botón de pausa.

Ahora eche un vistazo a estos proyectos retro de Pi Zero 5 Retro Gaming Projects con Raspberry Pi Zero 5 Retro Gaming Projects con Raspberry Pi Zero La Raspberry Pi Zero ha tomado por asalto el mundo del bricolaje y el homebrew, lo que permite revisar proyectos antiguos e inspirar a los recién llegados, especialmente en las mentes enfebrecidas de los fanáticos de los videojuegos retro. Lee mas .

¿Ha codificado Pong usando este código? ¿Qué modificaciones hiciste? Déjame saber en los comentarios a continuación, ¡me encantaría que aparecieran algunas fotos!

In this article