Manual

do

Maker

.

com

T-Display S3 e TFT_eSPI - Colisão de objetos

T-Display S3 e TFT_eSPI - Colisão de objetos

No artigo anterior vimos como compor camadas, aplicando transparência e eliminando flicking. Nesse artigo veremos o conceito de colisão de objetos, que será utilizado para fazer jogo(s?) em artigos mais adiante.

Ainda não tem uma T-Display S3?

A T-Display S3 da Lilygo é a melhor placa com o melhor display e melhor barramento que você vai encontrar hoje no mercado. O display usa 8 bits paralelos, que o torna significativamente mais rápido que SPI, permitindo atualizações de tela muito mais rápidas e fluidas.

Seguindo o artigo T-Display S3 com TFT_eSPI sua placa estará configurada em 5 minutos ou menos. Para adquirí-la, dê uma passada na CurtoCircuito e pegue a sua. É satisfação garantida!

O que é colisão de objetos?

Pensemos em um jogo de tiro. O projétil é um objeto que deverá atingir um alvo - por exemplo, uma nave espacial. Precisamos determinar quando um pixel do projétil toca um pixel da área da nave para então determinar uma explosão ou pontuação; a nave é um objeto e o projétil é outro objeto - ambos, tratados nesse momento como "objetos visuais".

O que é objeto em C++?

Quando utilizamos a IDE do Arduino ou o PlatformIO para programar nossas MCUs, estamos utilizando uma API de C++. Já discorri sobre o tema no artigo "A linguagem do Arduino é C ou C++?".

Um objeto em C++ é constituído por uma classe. Uma classe em C++ contém métodos e atributos. Os métodos são funções, enquanto os atributos são características.

Exemplo de objeto em C++

Um objeto não é necessariamente visual, mas vamos pensar em um objeto visual para exemplificar.

Suponhamos que temos um display touch e nele temos um push button para ativar um relé. Quando pressionamos o botão no display, ele deverá acionar o relé. A função que será executada pelo botão é um método.

O botão pode ser vermelho, cantos arredondados, texto em branco, posicionado no canto inferior direito do display. Essas características são chamadas de atributos.

Criando uma classe contendo essas especificações, podemos multiplicar o objeto, variando os parâmetros do método para, por exemplo, acionar diferentes tipos de atuadores (atuadores são relés, motores etc).

Exemplo de objeto visual e colisão de objetos

Claro que iremos escrever uma classe para nossos componentes de jogo, mas no momento estamos vendo conceitos, e nesse artigo veremos como acontece uma colisão. Para isso, vamos limitar um emoji a movimentar-se dentro da área do display.

Para que o emoji não saia da tela, devemos criar um espaço bidimensional que possa ser reconhecido pelo programa. Assim, temos um display de 320x170, que é a área visível.

O emoji executado nessa área visível tem o tamanho de 64x64. No eixo x, iniciamos em 64 e vamos até 320-64. No eixo y, começamos em 64 e vamos até 170-64. Mas vamos focar "apenas" no eixo x nesse momento: Quando o emoji tocar um dos lados, ele deve inverter sua direção de deslocamento. Para guardar alguns atributos, utilizei uma struct. Ainda não vamos criar uma classe, lembre-se de que esse artigo é sobre colisão de objetos. A struct ficou assim:

struct {
  int width_pos         = 0+emoji_width;
  int height_pos        = 0+emoji_height;
  int width_limit       = 320-emoji_width;
  int height_limit      = 170-emoji_height;
  uint8_t w_direction   = RIGHT;
  uint8_t h_direction   = TOP;
  uint8_t show_colision = 0;
  
} limits;

Nessa struct estão definidos os limites, a direção de x e a direção de y. Além disso, tem o show_colision, que não devemos nos ater nesse momento.

Daí criei uma função (que poderia muito bem ser o método de uma classe), que monitora o deslocamento do emoji.

void shiftEmoji(){
  //controle de deslocamento horizontal
  if (limits.width_pos > limits.width_limit && limits.w_direction == RIGHT){               
    limits.w_direction = LEFT;                                                             

  }                                                                                        
  else if (limits.width_pos < 0 && limits.w_direction == LEFT){                            
    limits.w_direction = RIGHT;                                                            

  }                                                                                        
                                                                                           
  limits.width_pos = limits.w_direction == RIGHT ?
                     limits.width_pos+1 : 
                     limits.width_pos-1;
}

A função shiftEmoji() é executada na função loop(). Isso significa que a cada ciclo, um deslocamento é executado. No primeiro if, verificamos o limite de deslocamento e a direção. Se chegou no limite da respectiva direção, ela deve ser invertida. Daí vem o segundo if. Se o limite for atingido na respectiva direção, inverte novamente.

Saindo das condicionais, há uma ação que precisa ser executada sempre: o incremento ou decremento da posição x. Invés de adicionar a uma função condicional, utilizei o operador ternário:

limits.width_pos = limits.w_direction == RIGHT ?
                   limits.width_pos+1 :
                   limits.width_pos-1;

Isso significa que o valor da posição x é incrementado quando indo para a direita e decrementado quando indo para a esquerda.

Reagir a uma colisão de objetos

A reação a uma colisão pode ser o que desejar. No exemplo acima, mudamos a direção, mas no vídeo que você vê em nosso canal youtube.com/@dobitaobyte estou trocando o emoji quando ele colide.

Para trocar o emoji quando ele colide, criei uma função chamada showEmoji(bool colision). Se não estiver em colisão, uma condicional mostra o emoji sorrindo. Se houver uma colisão, mostra o emoji reclamando:

void showEmoji(bool colision){
  if (limits.show_colision > 0){
    limits.show_colision -=1;
    bg_sprite.pushImage(0,0,170,320,img2display);
    emoji.pushImage(0,0,64,64,emoji_colision_img);
    emoji.pushToSprite(&bg_sprite, limits.height_pos, limits.width_pos, TFT_BLACK);
    bg_sprite.pushSprite(0,0);
  }
  if (colision){
    limits.show_colision = 10;
  }
  else if (limits.show_colision == 0){
    bg_sprite.pushImage(0,0,170,320,img2display);
    emoji.pushImage(0,0,64,64,emoji_img);
    emoji.pushToSprite(&bg_sprite, limits.height_pos, limits.width_pos, TFT_BLACK);
    bg_sprite.pushSprite(0,0);
  }
}

O código é simples e é uma rotina, por isso é bom tê-lo em um método ou função, já que se repetirá sempre.

Como criar a imagem para display ST7789?

Precisamos criar um background (que pode ser uma cor sólida ou uma imagem), assim como precisamos criar as imagens dos emojis. Para criar as imagens, desenvolvi um programa que agiliza o processo, tanto para displays RGB565 como para grayscale 4 e 8 bits, além de monocromático. Especificamente para o T-Display S3, fiz um vídeo mostrando como criar a imagem para display usando EasyMaker, gratuito e, por enquanto, com versão apenas para Windows.

Será fácil construirmos interfaces bonitas, utilizando esse programa!

Código completo de exemplo

O código completo que limita o emoji dentro da área do display é esse a seguir. Crie as imagens que estão no include; os emojis devem ter 64x64 e a imagem de fundo deve ter 320x170.

#include "Arduino.h"
#include "TFT_eSPI.h" /* Please use the TFT library provided in the library. */
#include "Wire.h"
#include "pin_config.h"
#include "img2dsp.h"
#include "emoji.h"
#include "emojiColision.h"

#define RIGHT  0
#define LEFT   1
#define TOP    2
#define BOTTON 3

//Instância do TFT
TFT_eSPI tft = TFT_eSPI();

//Criando sprites, que serão aplicados na instância do TFT
TFT_eSprite emoji          = TFT_eSprite(&tft); //Aqui passamos o endereço (&) da instância
TFT_eSprite emoji_colision = TFT_eSprite(&tft); //Aqui passamos o endereço (&) da instância
TFT_eSprite bg_sprite      = TFT_eSprite(&tft); //Aqui passamos o endereço (&) da instância

//Limite de movimento
struct {
  int width_pos         = 0+emoji_width;
  int height_pos        = 0+emoji_height;
  int width_limit       = 320-emoji_width;
  int height_limit      = 170-emoji_height;
  uint8_t w_direction   = RIGHT;
  uint8_t h_direction   = TOP;
  uint8_t show_colision = 0;
  
} limits;

void setup() {
  Serial.begin(115200);
  //inicia o objeto
  tft.init();
  //RGB para GBR, que é o formato utilizado
  tft.setSwapBytes(true); 
  tft.setRotation(0); //Usando EasyMaker, 0. Senão, 3

  //Criando uma área de sprite para o emoji
  emoji.createSprite(64,64); 
  emoji_colision.createSprite(64,64); 
  //no vídeo explico porque está comentado
  //emoji.setSwapBytes(true);  

  //Agora criamos o sprite do background
  bg_sprite.createSprite(170,320);
  ////RGB para GBR, que é o formato utilizado
  bg_sprite.setSwapBytes(true);
  //Exibe a imagem da maneira tradicional. Ok, quando não tiver animação
  tft.pushImage(0,0,170,320,img2display); 

  //Coloca a imagem no sprite
  emoji.pushImage(0,0,64,64,emoji_img); 
  //Exibe a imagem, removendo a cor preta, dando transparência
  emoji.pushSprite(40,40,TFT_BLACK); 

  //Coloca a imagem no sprite
  emoji.pushImage(0,0,64,64,emoji_colision_img); 
  //Exibe a imagem, removendo a cor preta, dando transparência
  emoji.pushSprite(40,40,TFT_BLACK); 
}

void showEmoji(bool colision){
  if (limits.show_colision > 0){
    limits.show_colision -=1;
    bg_sprite.pushImage(0,0,170,320,img2display);
    emoji.pushImage(0,0,64,64,emoji_colision_img);
    emoji.pushToSprite(&bg_sprite, limits.height_pos, limits.width_pos, TFT_BLACK);
    bg_sprite.pushSprite(0,0);
  }
  if (colision){
    limits.show_colision = 10;
  }
  else if (limits.show_colision == 0){
    bg_sprite.pushImage(0,0,170,320,img2display);
    emoji.pushImage(0,0,64,64,emoji_img);
    emoji.pushToSprite(&bg_sprite, limits.height_pos, limits.width_pos, TFT_BLACK);
    bg_sprite.pushSprite(0,0);
  }
}
void shiftEmoji(){
  //controle de deslocamento horizontal
  if (limits.width_pos > limits.width_limit && limits.w_direction == RIGHT){               
    limits.w_direction = LEFT;                                                             
    showEmoji(true);
  }                                                                                        
  else if (limits.width_pos < 0 && limits.w_direction == LEFT){                            
    limits.w_direction = RIGHT;                                                            
    showEmoji(true);
  }                                                                                        
                                                                                           
  limits.width_pos = limits.w_direction == RIGHT ? limits.width_pos+1 : limits.width_pos-1;
  

  //controle de deslocamento vertical
  if (limits.height_pos > limits.height_limit && limits.h_direction == TOP  ){              
    limits.h_direction = BOTTON;                                                            
    showEmoji(true);
  }                                                                                         
  else if (limits.height_pos < 0 && limits.h_direction == BOTTON){                          
    limits.h_direction = TOP;                                                               
    showEmoji(true);
  }                                                                                         
                                                                                            
  limits.height_pos = limits.h_direction == TOP ? limits.height_pos+1 : limits.height_pos-1;//#
  

  showEmoji(false);
}

void loop(){
  shiftEmoji();
}

Vídeo de exemplo de colisão de objetos

O vídeo estará disponível em nosso canal youtube.com/@dobitaobyte, tão logo seja editado. Aproveite e se inscreva para não perder as novidades. Nem todos os temas podem ser de seu interesse, mas os que forem, terão a qualidade cada vez mais aprimoradas, como você poderá confirmar ao acessar o canal.

Inscreva-se no nosso canal Manual do Maker no YouTube.

Também estamos no Instagram.

Nome do Autor

Djames Suhanko

Autor do blog "Do bit Ao Byte / Manual do Maker".

Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.