Arduino Retro Gaming Com um Display OLED

Já imaginou quanto trabalho é necessário para escrever seus próprios jogos retrô? Quão fácil é Pong codificar para o Arduino?

Já imaginou quanto trabalho é necessário para escrever seus próprios jogos retrô?  Quão fácil é Pong codificar para o Arduino?
Propaganda

Já imaginou quanto trabalho é necessário para escrever seus próprios jogos retrô? Quão fácil é Pong codificar para o Arduino? Junte-se a mim enquanto eu mostro a você como construir um console de jogos retro mini-Arduino, e como programar o Pong a partir do zero. Aqui está o resultado final:

Plano de construção

Este é um circuito bastante simples. Um potenciômetro (pote) controlará o jogo, e um display OLED será acionado pelo Arduino. Isso será produzido em uma placa de ensaio, mas você pode querer fazer um circuito permanente e instalá-lo em um gabinete. Nós escrevemos sobre recriar Pong Como recriar o jogo Pong clássico usando Arduino Como recriar o jogo Pong clássico usando Arduino Pong foi o primeiro videogame que chegou ao mercado de massa. Pela primeira vez na história, o conceito de um "videogame" foi trazido para a casa da família, graças ao Atari 2600 -... Leia Mais antes, no entanto, hoje eu vou estar mostrando como escrever o código a partir do zero, e quebrando cada parte.

O que você precisa

Configuração Retro Arduino

Aqui está o que você precisa:

  • 1 x Arduino (qualquer modelo)
  • 1 x potenciômetro de 10k
  • 1 x 0, 96 ″ I2C OLED Display
  • 1 x tábua de pão
  • Assorted male> male hookup wires

Diymall 0, 96 "Inch I2c IIC Serial 128x64 LCD Oled LED Módulo Display Branco para Arduino 51 Msp420 Stim32 SCR Diymall 0, 96" Inch I2c IIC Serial 128x64 LCD Oled LED Módulo Display Branco para Arduino 51 Msp420 Stim32 SCR Comprar agora Em Amazon $ 9.99

Qualquer Arduino deve funcionar, então olhe para o nosso guia de compra Arduino Guia de compra: Qual placa você deve obter? Guia de compra do Arduino: Qual placa você deve obter? Existem tantos tipos diferentes de placas Arduino por aí, você seria perdoado por estar confuso. Qual você deve comprar para o seu projeto? Deixe-nos ajudar, com este guia de compra do Arduino! Leia mais se você não tiver certeza de qual modelo comprar.

Esses displays OLED são muito legais. Eles geralmente podem ser comprados em branco, azul, amarelo ou uma mistura dos três. Eles existem em cores, no entanto, eles adicionam um outro nível à complexidade e ao custo deste projeto.

O circuito

Este é um circuito bastante simples. Se você não tem muita experiência com o Arduino, confira esses projetos para iniciantes. 10 Grandes projetos para iniciantes do Arduino 10 Grandes projetos para iniciantes do Arduino A conclusão de um projeto do Arduino dá a você uma sensação de satisfação como nenhuma outra. A maioria dos iniciantes não sabe ao certo por onde começar, e até mesmo os projetos para iniciantes podem parecer assustadores. Leia mais primeiro.

Aqui está:

Pong Breadboard

Olhando para a frente do pote, conecte o pino esquerdo a + 5V e o pino direito ao terra . Ligue o pino do meio ao pino analógico 0 (A0).

O display OLED é conectado usando o protocolo I2C. Conecte VCC e GND ao Arduino + 5V e terra . Conecte o SCL ao analógico cinco ( A5 ). Conecte o SDA ao analógico 4 ( A4 ). A razão pela qual isso está conectado aos pinos analógicos é simples; esses pinos contêm o circuito necessário para o protocolo I2C. Assegure-se de que estes estejam conectados corretamente, e não cruzados. Os pinos exatos variam de acordo com o modelo, mas A4 e A5 são usados ​​no Nano e no Uno. Verifique a documentação da biblioteca Wire para o seu modelo, se você não estiver usando um Arduino ou Nano.

Teste de pote

Carregue este código de teste (certifique-se de selecionar a placa e a porta corretas nos menus Ferramentas > Placa e Ferramentas > Porta ):

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

Agora abra o monitor serial ( superior direito > Monitor serial ) e gire o pote. Você deve ver o valor exibido no monitor serial. Totalmente no sentido anti-horário deve ser zero, e totalmente no sentido horário deve ser 1023 :

Monitor serial Pong

Você vai ajustar isso mais tarde, mas por enquanto tudo bem. Se nada acontecer, ou o valor mudar sem você fazer nada, desconecte e verifique novamente o circuito.

Teste OLED

Gráficos OLED

O display OLED é um pouco mais complexo para configurar. Você precisa instalar duas bibliotecas para direcionar a exibição primeiro. Faça o download das bibliotecas Adafruit_SSD1306 e Adafruit-GFX do Github. Copie os arquivos para a pasta de bibliotecas. Isso varia dependendo do seu sistema operacional:

  • Mac OS: / Usuários / Nome de usuário / Documentos / Arduino / bibliotecas
  • Linux: / home / Nome de usuário / Sketchbook
  • Windows: / Users / Arduino / libraries

Agora faça o upload de um esboço de teste. Vá para Arquivo > Exemplos > Adafruit SSD1306 > ssd1306_128x64_i2c . Isso deve fornecer um esboço grande contendo muitos gráficos:

Gráficos OLED

Se nada acontecer após o upload, desconecte e verifique suas conexões. Se os exemplos não estiverem nos menus, talvez seja necessário reiniciar seu Arduino IDE.

O código

Agora é hora do código. Eu estarei explicando cada passo, então pule para o final se você quer apenas executá-lo. Esta é uma boa quantidade de código, por isso, se você não se sentir confiante, confira estes 10 recursos gratuitos Aprenda a Code: 10 grátis e fantásticos recursos on-line para aprimorar suas habilidades Aprenda a codificar: 10 grátis e fantásticos recursos on-line para aprimorar seu Codificação de habilidades. Um tópico que é evitado por muitos. Há uma abundância de recursos e ferramentas gratuitos, todos disponíveis online. Claro que você poderia fazer alguns cursos sobre o tópico em uma próxima ... Leia mais para aprender a codificar.

Comece incluindo as bibliotecas necessárias:

 #include #include #include #include 

SPI e WIRE são duas bibliotecas do Arduino para lidar com a comunicação I2C. Adafruit_GFX e Adafruit_SSD1306 são as bibliotecas que você instalou anteriormente.

Em seguida, configure a exibição:

 Adafruit_SSD1306 display(4); 

Em seguida, configure todas as variáveis ​​necessárias para executar o jogo:

 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; 

Estes armazenam todos os dados necessários para executar o jogo. Algumas delas armazenam a localização da bola, o tamanho da tela, a localização do jogador e assim por diante. Observe como alguns deles são constantes, significando que são constantes e nunca serão alterados. Isso permite que o compilador do Arduino acelere as coisas.

A resolução da tela e a localização da bola são armazenadas em matrizes . Arrays são coleções de coisas semelhantes e, para a bola, armazenam as coordenadas ( X e Y ). Acessar elementos em matrizes é fácil (não inclua este código no seu arquivo):

 resolution[1]; 

Como as matrizes começam em zero, isso retornará o segundo elemento na matriz de resolução ( 64 ). A atualização de elementos é ainda mais fácil (novamente, não inclua este código):

 ball[1] = 15; 

Dentro void setup (), configure o display:

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

A primeira linha informa à biblioteca Adafruit quais dimensões e protocolo de comunicação seu monitor está usando (nesse caso, 128 x 64 e I2C ). A segunda linha ( display.display () ) diz à tela para mostrar o que está armazenado no buffer (que não é nada).

Crie dois métodos chamados drawBall e 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); } 

Eles pegam as coordenadas xey da bola e desenham na tela usando o método drawCircle das bibliotecas de exibição. Isso usa a constante BALL_SIZE definida anteriormente. Tente mudar isso e veja o que acontece. Este método drawCircle aceita uma cor de pixel - PRETO ou BRANCO . Como este é um mostrador monocromático (uma cor), o branco equivale a um pixel e o preto desliga o pixel.

Agora crie um método chamado moveAi :

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

Esse método manipula a movimentação da inteligência artificial ou do player AI . Este é um oponente de computador bastante simples - se a bola estiver acima do remo, suba. É abaixo do remo, desça. Muito simples, mas funciona bem. Os símbolos de incremento e decremento são usados ​​( ++ aiPos e –aiPos ) para adicionar ou subtrair um do aiPosition. Você pode adicionar ou subtrair um número maior para fazer com que a IA se movimente mais rapidamente e, portanto, seja mais difícil de superar. Veja como você faria isso:

 aiPos += 2; 

E:

 aiPos -= 2; 

Os sinais Plus Equals e Minus Equals são abreviados para adicionar ou subtrair dois de / para o valor atual de aiPos. Aqui está outra maneira de fazer isso:

 aiPos = aiPos + 2; 

e

 aiPos = aiPos - 1; 

Observe como esse método primeiro apaga o remo e desenha novamente. Isso tem que ser feito assim. Se a nova posição do remo fosse desenhada, haveria duas pás sobrepostas na tela.

O método drawNet usa dois loops para desenhar a rede:

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

Isso usa as variáveis WALL_WIDTH para definir seu tamanho.

Crie métodos chamados drawPixels e erasePixels . Assim como os métodos de bola, a única diferença entre esses dois é a cor dos 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); } } } 

Novamente, ambos os métodos usam dois loops for para desenhar um grupo de pixels. Em vez de precisar desenhar cada pixel usando o método drawPixel das bibliotecas, os loops desenham um grupo de pixels com base nas dimensões fornecidas.

O método drawScore usa os recursos de texto da biblioteca para gravar o player e a pontuação do AI na tela. Estes são armazenados no playerScore e no 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 também tem uma contraparte eraseScore, que define os pixels para preto ou desligado.

Os quatro métodos finais são muito semelhantes. Eles desenham e apagam o jogador e as pás da 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 como eles chamam o método erasePixel create earlier. Esses métodos desenham e apagam o remo apropriado.

Há um pouco mais de lógica no loop principal. Aqui está o código inteiro:

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

Aqui está o que você acaba com:

Pong OLED

Quando estiver confiante com o código, existem inúmeras modificações que você pode fazer:

  • Adicione um menu para níveis de dificuldade (altere AI e velocidade da bola).
  • Adicione algum movimento aleatório à bola ou à IA.
  • Adicione outro pote para dois jogadores.
  • Adicione um botão de pausa.

Agora, dê uma olhada nestes projetos de jogos retro de Pi Zero 5 Projetos de Jogos Retrô com o Raspberry Pi Zero 5 Projetos de Jogos Retrô com o Raspberry Pi Zero O Raspberry Pi Zero levou o mundo DIY e homebrew ao mundo, tornando possível revisar projetos antigos e novatos inspiradores, especialmente nas mentes febris dos fãs de jogos retrô. Consulte Mais informação .

Você já codificou o Pong usando este código? Quais modificações você fez? Deixe-me saber nos comentários abaixo, eu adoraria parecer algumas fotos!

In this article