Manufaturação industrial
Internet das coisas industrial | Materiais industriais | Manutenção e reparo de equipamentos | Programação industrial |
home  MfgRobots >> Manufaturação industrial >  >> Manufacturing Technology >> Processo de manufatura

Jogo Breakout Touch com Arduino: Arcade Clássico em LCD TFT 2,4″

Componentes e Materiais

Componente Quantidade Observações
Arduino UNO 1
AZ-Delivery 2.4 TFT LCD Touch Display Arduino Shield 1

Aplicativos e Serviços Online

Sobre o Projeto

Esta é uma pequena versão do clássico jogo breakout para Arduino UNO e tela TFT LCD (240x320 pixels), driver ILI9341 com comunicação paralela de 8 bits.

O Jogo

Este Breakout tem múltiplas telas com diferentes linhas e colunas de tijolos configuráveis, até oito fileiras, com cada duas fileiras de uma cor diferente, que podem ser programadas ligadas ou desligadas com diferentes padrões. Usando uma única bola, o jogador deve derrubar o máximo de tijolos possível usando as paredes e/ou a raquete abaixo para ricochetear a bola contra os tijolos e eliminá-los. Se a raquete do jogador errar o rebote da bola, perde uma vida.

Cada fileira de tijolos vale pontos diferentes:

uint8_t pointsForRow[] = {7, 7, 5, 5, 3, 3, 1, 1};

Cada nível pode configurar o tamanho da raquete e da bola. A velocidade da bola aumenta a cada acerto; você pode configurar a velocidade inicial para cada tela. Dependendo do ponto da raquete que acerta a bola, a velocidade horizontal também muda.

Você pode definir novas telas com diferentes padrões de paredes:

Jogo Breakout Touch com Arduino: Arcade Clássico em LCD TFT 2,4″
Tela 1: Padrão de parede personalizado
Jogo Breakout Touch com Arduino: Arcade Clássico em LCD TFT 2,4″
Tela 2: Outro padrão de parede
Jogo Breakout Touch com Arduino: Arcade Clássico em LCD TFT 2,4″
Tela 3: Padrão tipo grade
Jogo Breakout Touch com Arduino: Arcade Clássico em LCD TFT 2,4″
Tela 4: Padrão quadriculado
Jogo Breakout Touch com Arduino: Arcade Clássico em LCD TFT 2,4″
Tela 5: Padrão complexo
Jogo Breakout Touch com Arduino: Arcade Clássico em LCD TFT 2,4″
Tela 6: Padrão diagonal

Como Jogar

Segure o dispositivo com as mãos e use os polegares sobre a tela para mover a raquete para esquerda ou direita.

Jogo Breakout Touch com Arduino: Arcade Clássico em LCD TFT 2,4″
Posição das mãos para jogar com polegares

Definir uma Nova Tela

Esta estrutura é usada para definir uma nova tela:

typedef struct game_type {
  int ballsize;
  int playerwidth;
  int playerheight;
  int exponent;
  int top;
  int rows;
  int columns;
  int brickGap;
  int lives;
  int wall[GAMES_NUMBER];
  int initVelx;
  int initVely;
} game_type;

E adicione a nova tela ao conjunto:

game_type games[GAMES_NUMBER] =
// ballsize, playerwidth, playerheight, exponent, top, rows, columns, brickGap, lives, wall[8], initVelx, initVely
{
  { 10, 60, 8, 6, 40 , 8, 8, 3, 3, {0x18, 0x66, 0xFF, 0xDB, 0xFF, 0x7E, 0x24, 0x3C} , 28, -28},
  { 10, 50, 8, 6, 40 , 8, 8, 3, 3, {0xFF, 0x99, 0xFF, 0xE7, 0xBD, 0xDB, 0xE7, 0xFF} , 28, -28},
  { 10, 50, 8, 6, 40 , 8, 8, 3, 3, {0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55} , 28, -28},
  { 8, 50, 8, 6, 40 , 8, 8, 3, 3, {0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF} , 34, -34},
  { 10, 40, 8, 6, 40 , 8, 8, 3, 3, {0xFF, 0xAA, 0xAA, 0xFF, 0xFF, 0xAA, 0xAA, 0xFF} , 28, -28},
  { 10, 40, 8, 6, 40 , 8, 8, 3, 3, {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA} , 28, -28},
  { 12, 64, 8, 6, 60 , 4, 2, 3, 4, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} , 20, -20},
  { 12, 60, 8, 6, 60 , 5, 3, 3, 4, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} , 22, -22},
  { 10, 56, 8, 6, 30 , 6, 4, 3, 4, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} , 24, -24},
  { 10, 52, 8, 6, 30 , 7, 5, 3, 4, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} , 26, -26},
  { 8, 48, 8, 6, 30 , 8, 6, 3, 3, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} , 28, -28},
  { 8, 44, 8, 6, 30 , 8, 7, 3, 3, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} , 30, -30},
  { 8, 40, 8, 6, 30 , 8, 8, 3, 3, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} , 32, -32},
  { 8, 36, 8, 6, 40 , 8, 8, 3, 3, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} , 34, -34},
  { 8, 36, 8, 6, 40 , 8, 8, 3, 3, {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA} , 34, -34}
};

Padrão de Parede

O padrão de parede é definido como array de bits 8x8.

Exemplo:

{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}

Corresponde a este array de bits:

1,0,1,0,1,0,1,0
1,0,1,0,1,0,1,0
1,0,1,0,1,0,1,0
1,0,1,0,1,0,1,0
1,0,1,0,1,0,1,0
1,0,1,0,1,0,1,0
1,0,1,0,1,0,1,0
1,0,1,0,1,0,1,0

Produzirá esta parede (note que é espelhada):

Jogo Breakout Touch com Arduino: Arcade Clássico em LCD TFT 2,4″
Padrão de parede gerado pelo array 0xAA (espelhado horizontalmente)

Hardware

Este projeto usa um display AZ-Delivery 2.4 polegadas TFT LCD com touchscreen resistivo 4 fios e leitor de cartão SD integrado.

Jogo Breakout Touch com Arduino: Arcade Clássico em LCD TFT 2,4″
Shield AZ-Delivery 2.4" TFT LCD Touch Display

Mais sobre este shield no meu artigo "Road testing an AZ-Delivery 2, 4 TFT LCD Touch Display".

Montagem do Shield

Você só precisa encaixar o shield sobre o Arduino.

Jogo Breakout Touch com Arduino: Arcade Clássico em LCD TFT 2,4″
Shield encaixado no Arduino Uno
Jogo Breakout Touch com Arduino: Arcade Clássico em LCD TFT 2,4″
Vista lateral do shield montado

Bibliotecas

#include <Adafruit_GFX.h> // Biblioteca gráfica principal
#include <Adafruit_TFTLCD.h>
#include <TouchScreen.h>

Calibrando a Tela Touch

Você deve calibrar o display para que a informação de posição esteja correta quando tocar a tela. A biblioteca MCUFriend_kbv fornece um exemplo chamado "TouchScreen_Calibr_native". O exemplo envia os resultados para a porta serial. Inicie o monitor serial da Arduino IDE para copiar o código gerado pelo exemplo.

Siga as instruções no display touch, pressione e segure os marcadores de posição exibidos, que são destacados em branco. Após fazer todas as marcas de posição, a calibração do display é mostrada no display touch e pela porta serial.

Para este projeto você precisa dos dados da "calibração portrait".

TouchScreen.h GFX Calibration
Making all control and bus pins INPUT_PULLUP
Typical 30k Analog pullup with corresponding pin
would read low when digital is written LOW
e.g. reads ~25 for 300R X direction
e.g. reads ~30 for 500R Y direction
Testing : (A2, D8) = 26
Testing : (A3, D9) = 28
ID = 0x9341
cx=153 cy=103 cz=534 X, Y, Pressure
cx=150 cy=475 cz=406 X, Y, Pressure
cx=155 cy=868 cz=231 X, Y, Pressure
cx=517 cy=103 cz=561 X, Y, Pressure
cx=535 cy=855 cz=364 X, Y, Pressure
cx=884 cy=88 cz=650 X, Y, Pressure
cx=908 cy=478 cz=557 X, Y, Pressure
cx=902 cy=864 cz=488 X, Y, Pressure
*** COPY-PASTE from Serial Terminal:
const int XP=8,XM=A2,YP=A3,YM=9; //240x320 ID=0x9341
const int TS_LEFT=118,TS_RT=931,TS_TOP=72,TS_BOT=887;
PORTRAIT CALIBRATION 240 x 320
x = map(p.x, LEFT=118, RT=931, 0, 240)
y = map(p.y, TOP=72, BOT=887, 0, 320)
LANDSCAPE CALIBRATION 320 x 240
x = map(p.y, LEFT=72, RT=887, 0, 320)
y = map(p.x, TOP=931, BOT=118, 0, 240)

Animação

Para mover a imagem pela tela ao longo do tempo, usa-se velocidade estática e aplica-se à posição de uma imagem a cada passo de tempo.

pos += vel * dt;

Evitando Aritmética de Ponto Flutuante

A resolução ILI9341 é 240 x 320, então precisamos de dois inteiros de 9 bits para referenciar um pixel na tela. Usando inteiros de 16 bits, sobram 6 bits para representar a parte decimal.

nnnn nnnn nndd dddd

Chamamos este número, 6, de expoente binário. E podemos usar esses 6 bits para ter uma parte decimal variando de 0.000 a 0.63. Assim, podemos usar matemática inteira evitando aritmética de ponto flutuante.

Para obter a parte inteira do número, fazemos um shift aritmético à direita.

number >> exponent

state.ballx += state.velx;
state.bally += state.vely;

// verifica colisões da bola e saída
checkBallCollisions(game, &state, state.ballx >> game->exponent, state.bally >> game->exponent);
checkBallExit(game, &state, state.ballx >> game->exponent, state.bally >> game->exponent);

Modo Demo

Descomente a Diretiva Define e a raquete seguirá a bola como visto nos vídeos de demonstração:

#define DEMO_MODE

Divirta-se!

Crie novos padrões e níveis e compartilhe!

Código

Arduino Breakout (Arduino)

/* Arduino Touch TFT Breakout
   Classic breakout game
   Parts needed:
   Ardunio UNO
   AZ-Delivery 2.4 TFT LCD Touch Display Arduino Shield or compatible
   This example code is in the public domain.
   Modified 07 11 2020
   By Enrique Albertos */
   
// #define DEMO_MODE
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_TFTLCD.h>
#include <TouchScreen.h>

#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
#define PRIMARY_COLOR 0x4A11
#define PRIMARY_LIGHT_COLOR 0x7A17
#define PRIMARY_DARK_COLOR 0x4016
#define PRIMARY_TEXT_COLOR 0x7FFF

#define LCD_CS A3 // Chip Select goes to Analog 3
#define LCD_CD A2 // Command/Data goes to Analog 2
#define LCD_WR A1 // LCD Write goes to Analog 1
#define LCD_RD A0 // LCD Read goes to Analog 0
#define LCD_RESET A4 // Can alternately just connect to Arduino's reset pin

Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);

#define LOWFLASH (defined(__AVR_ATmega328P__) && defined(MCUFRIEND_KBV_H_))

// Touch screen pressure threshold
#define MINPRESSURE 40
#define MAXPRESSURE 1000

// Touch screen calibration
const int16_t XP = 8, XM = A2, YP = A3, YM = 9; //240x320 ID=0x9341
const int16_t TS_LEFT = 122, TS_RT = 929, TS_TOP = 77, TS_BOT = 884;
const TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);

#define SCORE_SIZE 30
char scoreFormat[] = "%04d";

typedef struct gameSize_type {
  int16_t x, y, width, height;
} gameSize_type;

gameSize_type gameSize;
uint16_t backgroundColor = BLACK;
int level;

const uint8_t BIT_MASK[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
uint8_t pointsForRow[] = {7, 7, 5, 5, 3, 3 , 1, 1};

#define GAMES_NUMBER 16

typedef struct game_type {
  int ballsize;
  int playerwidth;
  int playerheight;
  int exponent;
  int top;
  int rows;
  int columns;
  int brickGap;
  int lives;
  int wall[GAMES_NUMBER];
  int initVelx;
  int initVely;
} game_type;

game_type games[GAMES_NUMBER] = 
// ballsize, playerwidth, playerheight, exponent, top, rows, columns, brickGap, lives, wall[8], initVelx, initVely
{
  { 10, 60, 8, 6, 40 , 8, 8, 3, 3, {0x18, 0x66, 0xFF, 0xDB, 0xFF, 0x7E, 0x24, 0x3C} , 28, -28},
  { 10, 50, 8, 6, 40 , 8, 8, 3, 3, {0xFF, 0x99, 0xFF, 0xE7, 0xBD, 0xDB, 0xE7, 0xFF} , 28, -28},
  { 10, 50, 8, 6, 40 , 8, 8, 3, 3, {0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55} , 28, -28},
  { 8, 50, 8, 6, 40 , 8, 8, 3, 3, {0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF} , 34, -34},
  { 10, 40, 8, 6, 40 , 8, 8, 3, 3, {0xFF, 0xAA, 0xAA, 0xFF, 0xFF, 0xAA, 0xAA, 0xFF} , 28, -28},
  { 10, 40, 8, 6, 40 , 8, 8, 3, 3, {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA} , 28, -28},
  { 12, 64, 8, 6, 60 , 4, 2, 3, 4, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} , 20, -20},
  { 12, 60, 8, 6, 60 , 5, 3, 3, 4, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} , 22, -22},
  { 10, 56, 8, 6, 30 , 6, 4, 3, 4, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} , 24, -24},
  { 10, 52, 8, 6, 30 , 7, 5, 3, 4, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} , 26, -26},
  { 8, 48, 8, 6, 30 , 8, 6, 3, 3, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} , 28, -28},
  { 8, 44, 8, 6, 30 , 8, 7, 3, 3, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} , 30, -30},
  { 8, 40, 8, 6, 30 , 8, 8, 3, 3, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} , 32, -32},
  { 8, 36, 8, 6, 40 , 8, 8, 3, 3, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} , 34, -34},
  { 8, 36, 8, 6, 40 , 8, 8, 3, 3, {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA} , 34, -34}
};

game_type* game;

typedef struct game_state_type {
  uint16_t ballx;
  uint16_t bally;
  uint16_t ballxold;
  uint16_t ballyold;
  int velx;
  int vely;
  int playerx;
  int playerxold;
  int wallState[8];
  int score;
  int remainingLives;
  int top;
  int bottom;
  int walltop;
  int wallbottom;
  int brickheight;
  int brickwidth;
} game_state_type;

game_state_type state;

// ARDUINO SETUP
void setup() {
  initTft(tft);
  gameSize = {0, 0, tft.width(), tft.height()};
  newGame(&games[0], &state, tft);
}

// ARDUINO LOOP
int selection = -1;
void loop(void) {
  selection = readUiSelection(game, &state, selection);
  drawPlayer(game, &state);
  
  // armazena posição antiga para remover pixels antigos
  state.playerxold = state.playerx;
  
  // calcula nova posição da bola x1 = x0 + vx * dt
  // verifica velocidade máxima
  if (abs(state.vely) > ((1 << game->exponent) - 1)) {
    state.vely = ((1 << game->exponent) - 1) * ((state.vely > 0) - (state.vely < 0));
  }
  if (abs(state.velx) > ((1 << game->exponent) - 1)) {
    state.velx = ((1 << game->exponent) - 1) * ((state.velx > 0) - (state.velx < 0));
  }
  
  state.ballx += state.velx;
  state.bally += state.vely;
  
  // verifica colisões da bola e saída
  checkBallCollisions(game, &state, state.ballx >> game->exponent, state.bally >> game->exponent);
  checkBallExit(game, &state, state.ballx >> game->exponent, state.bally >> game->exponent);
  
  // desenha bola na nova posição
  drawBall(state.ballx >> game->exponent, state.bally >> game->exponent, 
           state.ballxold >> game->exponent, state.ballyold >> game->exponent, game->ballsize);
  
  // armazena posição antiga para remover pixels antigos
  state.ballxold = state.ballx;
  state.ballyold = state.bally;
  
  // incrementa velocidade
  state.velx = (20 + (state.score >> 3)) * ((state.velx > 0) - (state.velx < 0));
  state.vely = (20 + (state.score >> 3)) * ((state.vely > 0) - (state.vely < 0));
  
  // se sem tijolos, vai para próximo nível
  if (noBricks(game, &state) && level < GAMES_NUMBER) {
    level++;
    newGame(&games[level], &state, tft);
  } else if (state.remainingLives <= 0) {
    gameOverTouchToStart();
    state.score = 0;
    level = 0;
    newGame(game, &state, tft);
  }
}

void newGame(game_type* newGame, game_state_type* state, Adafruit_TFTLCD& tft) {
  game = newGame;
  setupState(game, state, tft);
  clearDialog(gameSize);
  updateLives(game->lives, state->remainingLives);
  updateScore(state->score);
  setupWall(game, state);
  touchToStart();
  clearDialog(gameSize);
  updateLives(game->lives, state->remainingLives);
  updateScore(state->score);
  setupWall(game, state);
}

void setupStateSizes(game_type* game, game_state_type* state, Adafruit_TFTLCD& tft) {
  state->bottom = tft.height() - 30;
  state->brickwidth = tft.width() / game->columns;
  state->brickheight = tft.height() / 24;
}

void setupState(game_type* game, game_state_type* state, Adafruit_TFTLCD& tft) {
  setupStateSizes(game, state, tft);
  for (int i = 0; i < game->rows; i++) {
    state->wallState[i] = 0;
  }
  state->playerx = tft.width() / 2 - game->playerwidth / 2;
  state->remainingLives = game->lives;
  state->bally = state->bottom << game->exponent;
  state->ballyold = state->bottom << game->exponent;
  state->velx = game->initVelx;
  state->vely = game->initVely;
}

void updateLives(int lives, int remainingLives) {
  for (int i = 0; i < lives; i++) {
    tft.fillCircle((1 + i) * 15, 15, 5, BLACK);
  }
  for (int i = 0; i < remainingLives; i++) {
    tft.fillCircle((1 + i) * 15, 15, 5, YELLOW);
  }
}

void setupWall(game_type* game, game_state_type* state) {
  int colors[] = {RED, RED, BLUE, BLUE, YELLOW, YELLOW, GREEN, GREEN};
  state->walltop = game->top + 40;
  state->wallbottom = state->walltop + game->rows * state->brickheight;
  for (int i = 0; i < game->rows; i++) {
    for (int j = 0; j < game->columns; j++) {
      if (isBrickIn(game->wall, j, i)) {
        setBrick(state->wallState, j, i);
        drawBrick(state, j, i, colors[i]);
      }
    }
  }
}

void drawBrick(game_state_type* state, int xBrick, int yBrickRow, uint16_t backgroundColor) {
  tft.fillRect((state->brickwidth * xBrick) + game->brickGap, 
               state->walltop + (state->brickheight * yBrickRow) + game->brickGap,
               state->brickwidth - game->brickGap * 2, 
               state->brickheight - game->brickGap * 2, backgroundColor);
}

boolean noBricks(game_type* game, game_state_type* state) {
  for (int i = 0; i < game->rows; i++) {
    if (state->wallState[i]) return false;
  }
  return true;
}

void drawPlayer(game_type* game, game_state_type* state) {
  // pinta
  tft.fillRect(state->playerx, state->bottom, game->playerwidth, game->playerheight, YELLOW);
  if (state->playerx != state->playerxold) {
    // remove pixels antigos
    if (state->playerx < state->playerxold) {
      tft.fillRect(state->playerx + game->playerwidth, state->bottom, 
                   abs(state->playerx - state->playerxold), game->playerheight, backgroundColor);
    } else {
      tft.fillRect(state->playerxold, state->bottom, 
                   abs(state->playerx - state->playerxold), game->playerheight, backgroundColor);
    }
  }
}

void drawBall(int x, int y, int xold, int yold, int ballsize) {
  // remove pixels antigos
  if (xold <= x && yold <= y) {
    tft.fillRect(xold, yold, ballsize, y - yold, BLACK);
    tft.fillRect(xold, yold, x - xold, ballsize, BLACK);
  } else if (xold >= x && yold >= y) {
    tft.fillRect(x + ballsize, yold, xold - x, ballsize, BLACK);
    tft.fillRect(xold, y + ballsize, ballsize, yold - y, BLACK);
  } else if (xold <= x && yold >= y) {
    tft.fillRect(xold, yold, x - xold, ballsize, BLACK);
    tft.fillRect(xold, y + ballsize, ballsize, yold - y, BLACK);
  } else if (xold >= x && yold <= y) {
    tft.fillRect(xold, yold, ballsize, y - yold, BLACK);
    tft.fillRect(x + ballsize, yold, xold - x, ballsize, BLACK);
  }
  // pinta nova bola
  tft.fillRect(x, y, ballsize, ballsize, YELLOW);
}

void touchToStart() {
  drawBoxedString(0, 200, " BREAKOUT", 3, YELLOW, BLACK);
  drawBoxedString(0, 240, " TOUCH TO START", 2, RED, BLACK);
  while (waitForTouch() < 0) {}
}

void gameOverTouchToStart() {
  drawBoxedString(0, 180, " GAME OVER", 3, YELLOW, BLACK);
  drawBoxedString(0, 220, " TOUCH TO START", 2, RED, BLACK);
  while (waitForTouch() < 0) {}
}

void updateScore(int score) {
  char buffer[5];
  snprintf(buffer, sizeof(buffer), scoreFormat, score);
  drawBoxedString(tft.width() - 50, 6, buffer, 2, YELLOW, PRIMARY_DARK_COLOR);
}

void checkBrickCollision(game_type* game, game_state_type* state, uint16_t x, uint16_t y) {
  int x1 = x + game->ballsize;
  int y1 = y + game->ballsize;
  int collisions = 0;
  collisions += checkCornerCollision(game, state, x, y);
  collisions += checkCornerCollision(game, state, x1, y1);
  collisions += checkCornerCollision(game, state, x, y1);
  collisions += checkCornerCollision(game, state, x1, y);
  if (collisions > 0) {
    state->vely = (-1 * state->vely);
    if ((((x % state->brickwidth) == 0) && (state->velx < 0)) || 
        (((x + game->ballsize) % state->brickwidth) == 0) && (state->velx > 0)) {
      state->velx = (-1 * state->velx);
    }
  }
}

int checkCornerCollision(game_type* game, game_state_type* state, uint16_t x, uint16_t y) {
  if ((y > state->walltop) && (y < state->wallbottom)) {
    int yBrickRow = (y - state->walltop) / state->brickheight;
    int xBrickColumn = (x / state->brickwidth);
    if (isBrickIn(state->wallState, xBrickColumn, yBrickRow)) {
      hitBrick(state, xBrickColumn, yBrickRow);
      return 1;
    }
  }
  return 0;
}

void hitBrick(game_state_type* state, int xBrick, int yBrickRow) {
  state->score += pointsForRow[yBrickRow];
  drawBrick(state, xBrick, yBrickRow, WHITE);
  delay(16);
  drawBrick(state, xBrick, yBrickRow, BLUE);
  delay(8);
  drawBrick(state, xBrick, yBrickRow, backgroundColor);
  unsetBrick(state->wallState, xBrick, yBrickRow);
  updateScore(state->score);
}

void checkBorderCollision(game_type* game, game_state_type* state, uint16_t x, uint16_t y) {
  // verifica colisão com parede
  if (x + game->ballsize >= tft.width()) {
    state->velx = -abs(state->velx);
  }
  if (x <= 0) {
    state->velx = abs(state->velx);
  }
  if (y <= SCORE_SIZE) {
    state->vely = abs(state->vely);
  }
  if (((y + game->ballsize) >= state->bottom) && ((y + game->ballsize) <= (state->bottom + game->playerheight)) && 
      (x >= state->playerx) && (x <= (state->playerx + game->playerwidth))) {
    // muda vel x próximo às bordas da raquete
    if (x > (state->playerx + game->playerwidth - 6)) {
      state->velx = state->velx - 1;
    } else if (x < state->playerx + 6) {
      state->velx = state->velx + 1;
    }
    state->vely = -abs(state->vely);
  }
}

void checkBallCollisions(game_type* game, game_state_type* state, uint16_t x, uint16_t y) {
  checkBrickCollision(game, state, x, y);
  checkBorderCollision(game, state, x, y);
}

void checkBallExit(game_type* game, game_state_type* state, uint16_t x, uint16_t y) {
  if (((y + game->ballsize) >= tft.height())) {
    state->remainingLives--;
    updateLives(game->lives, state->remainingLives);
    delay(500);
    state->vely = -abs(state->vely);
  }
}

void setBrick(int wall[], uint8_t x, uint8_t y) {
  wall[y] = wall[y] | BIT_MASK[x];
}

void unsetBrick(int wall[], uint8_t x, uint8_t y) {
  wall[y] = wall[y] & ~BIT_MASK[x];
}

boolean isBrickIn(int wall[], uint8_t x, uint8_t y) {
  return wall[y] & BIT_MASK[x];
}

// TFT SETUP
void initTft(Adafruit_TFTLCD& tft) {
  tft.reset();
  uint16_t ID = tft.readID();
  tft.begin(ID);
  tft.setRotation(0);
}

// Screen Painting methods
/** Imprime texto em forecolor sobre caixa preenchida com background color.
    Tamanho do retângulo calculado para incluir todo o texto sem margens
    @param x coordenada horizontal em pontos canto superior esquerdo
    @param y coordenada vertical em pontos canto superior esquerdo
    @param fontsize tamanho da fonte do texto a imprimir
    @param foreColor cor do texto
    @param backgroundColor cor do retângulo preenchido
    @return void */
void drawBoxedString(const uint16_t x, const uint16_t y, const char* string, 
                     const uint16_t fontsize, const uint16_t foreColor, 
                     const uint16_t backgroundColor) {
  tft.setTextSize(fontsize);
  int16_t x1, y1;
  uint16_t w, h;
  tft.getTextBounds(string, x, y, &x1, &y1, &w, &h);
  tft.fillRect(x, y, w, h, backgroundColor);
  tft.setCursor(x, y);
  tft.setTextColor(foreColor);
  tft.print(string);
}

/** Limpa a tela para o background padrão
    @param void
    @return void */
void clearDialog(gameSize_type gameSize) {
  tft.fillRect(gameSize.x, gameSize.y, gameSize.width, gameSize.height, backgroundColor);
  tft.fillRect(gameSize.x, gameSize.y, gameSize.width, SCORE_SIZE, PRIMARY_DARK_COLOR);
}

// READ UI SELECTION
/* Verifica se o usuário está selecionando algum dos elementos UI visíveis habilitados
   O callback onTap do elemento selecionado é chamado e ele é definido como pressionado
   @param lastSelected a última seleção
   @return a nova seleção */
int readUiSelection(game_type* game, game_state_type* state, const int16_t lastSelected) {
  int16_t xpos, ypos; // coordenadas de tela
  TSPoint tp = ts.getPoint(); // tp.x, tp.y são valores ADC
  
  // se compartilhando pinos, precisará corrigir direções dos pinos touchscreen
  pinMode(XM, OUTPUT);
  pinMode(YP, OUTPUT);
  
  // temos pressão mínima que consideramos 'válida'
  // pressão 0 significa sem pressão!
  if (tp.z > MINPRESSURE && tp.z < MAXPRESSURE) {
    xpos = map(tp.x, TS_RT, TS_LEFT, 0, tft.width());
    ypos = map(tp.y, TS_BOT, TS_TOP, 0, tft.height());
    
    // estamos na área dos botões?
    if (xpos > tft.width() / 2) {
      state->playerx += 2;
    } else {
      state->playerx -= 2;
    }
    if (state->playerx >= tft.width() - game->playerwidth)
      state->playerx = tft.width() - game->playerwidth;
    if (state->playerx < 0)
      state->playerx = 0;
    return 1;
  }
  
#ifdef DEMO_MODE
  state->playerx = (state->ballx >> game->exponent) - game->playerwidth / 2;
  if (state->playerx >= tft.width() - game->playerwidth)
    state->playerx = tft.width() - game->playerwidth;
  if (state->playerx < 0)
    state->playerx = 0;
#endif
  return -1;
}

int waitForTouch() {
  int16_t xpos, ypos;
  TSPoint tp = ts.getPoint();
  
  pinMode(XM, OUTPUT);
  pinMode(YP, OUTPUT);
  
  if (tp.z > MINPRESSURE && tp.z < MAXPRESSURE) {
    return 1;
  }
  return -1;
}

Esquemáticos

Jogo Breakout Touch com Arduino: Arcade Clássico em LCD TFT 2,4″
Esquemático: Shield AZ-Delivery 2.4" TFT LCD no Arduino Uno

Processo de manufatura

  1. Construa um Jogo em Tempo Real com Giroscópio: Arduino Nano + Sensor MPU-6050
  2. Construa o Clássico Jogo Pong no Arduino UNO com Display OLED – Tutorial Passo a Passo
  3. Construa um Piano Portátil de Toque Capacitivo com Arduino – Guia Passo a Passo
  4. Controle USB para Jogos DIY com Arduino – Construa Seu Próprio Gamepad de Alto Desempenho
  5. Construa um Console Pong 24×16 LED com Arduino Nano e MAX7219
  6. Construa um Jogo Operação Animatrônico de Minifigura Lego Gigante com Arduino
  7. Adaptadores USB-BLE MIDI Sem Fio – Conectividade Perfeita para Músicos
  8. Pixel Chaser: Jogo LED Interativo de Um Toque com Arduino Nano
  9. Dominando Reconhecimento e Síntese de Fala no Arduino Due
  10. Automatize o Jogo do Dinossauro do Chrome com Arduino e LDR