SDL e programação de jogos - detecção de colisão - parte 3

Caso vc não tenha visto a parte 2, aqui o link:
SDL-e-programacao-de-jogos-parte-2

Introdução

Pra quem não sabe, todo jogo usa algoritmos de detecção de colisão e separação de objetos. Esse é um assunto bem amplo (e complexo!) mas neste post vc aprenderá o básico:
  1. vc vai aprender a como detectar colisão entre dois retângulos
  2. vc vai aprender a como separar um retângulo de dentro um do outro

Algoritmo básico: bounding boxes!

O algoritmo de detectar se dois retãngulos estão colidindo é o bounding boxes!
É um dos algoritmos mais fáceis de aprender e também um dos mais fáceis de entender!

Veja abaixo como ele funciona:

  1. pegue um retângulo e chame-o de rectA
  2. pegue outro retângulo e chame-o de rectB
  3. se a posição da lateral direita do rectA é MAIOR ou igual a posição da lateral ESQUERDA de rectB, então, continue para passo 4, senão, não é colisão
  4. se a posição da lateral esquerda do rectA é menor ou igual a posição da lateral DIREITA de rectB, então, continue para passo 5, senão, não é colisão
  5. se a posição da lateral de cima do rectA é menor ou igual a posição da lateral DE BAIXO de rectB, então, continue para passo 6, senão, não é colisão
  6. se a posição da lateral de baixo do rectA é MAIOR ou igual a posição da lateral DE CIMA de rectB, então, continue para passo 7, senão, não é colisão
  7. se chegou até aqui, então, é colisão de rectA com rectB!
Não vou colocar o código completo agora, mas sugiro que vc tente transformar em código o algoritmo acima!

Vc pode colocar tudo numa única expressão lógica ou pode colocar em 4 if. Detalhe: é mais fácil verificar por NÃO colisão do que por colisão!

Fica a seu critério definir o código de colisão. Neste tutorial, vamos usar função para ficar mais genérico e mais desafiador.

Para o próximo código, vamos precisar do algoritmo acima como uma função que chamaremos ela de boundingBox, que recebe dois ponteiros constantes do tipo SDL_Rect (rectA e rectB) e retorna 1 se eles estão colidindo ou retorna 0 em caso de não colisão

Exemplo de uso: detectando colisão!

Quando vc vê um retângulo numa janela do SDL vc pode pensar: mas como diabos algo assim se transforma num jogo complexo?

E eu digo meu caro que um jogo é apenas um conjunto organizado de diversos algoritmos! Ou seja, o jogo é preparado de um jeito que cada algoritmo trabalha praticamente independente dos outros e a soma de odos os trabalhos resultado no que vc vê na tela como jogo

Veja abaixo um código de exemplo: (implemente a função de boundingBox com o algoritmo ali de cima!)


// código de exemplo de como detectar colisão
// compile com gcc -o colisao_boundingbox colisao_boundingbox.c -lSDL2
//código de colisão entre dois retangulos na tela  
//neste exemplo tem um rect amarelo que é controlado pelas setas do teclado e um rect cinza que fica parado.
//quando os dois se encostar pelo boundingBox então o rect do player vai de amarelo e fica vermelho.
//quando não se encostar, o rect do player volta ficar amarelo

#include <SDL2/SDL.h>
#include <stdio.h>

int boundingBox(const SDL_Rect *a, const SDL_Rect * b) {
  return (
          //implemente aqui a lógica de detecção de colisão
        );
}

int main() {
  SDL_Init(SDL_INIT_VIDEO);
  //100 posição horizontal em pixels dentro do seu monitor do canto esquerdo superior para direita
  //150 posição vertical em pixels dentro do seu monitot do canto esquerdo superior pra baixo
  //640 é a largura da janela em pixel
  //480 é a altura da janela em pixel
  SDL_Window * janela = SDL_CreateWindow("loops e jogos", 100,150,640,480, SDL_WINDOW_OPENGL);
  SDL_Renderer * renderer = SDL_CreateRenderer(janela, -1, SDL_RENDERER_ACCELERATED);
  
  SDL_Event evento;
  
  SDL_Rect player;
  player.x = 220;
  player.y = 240;
  player.w = 32;
  player.h = 64;
  
  //cria o sólido
  //ele é apenas um retângulo simples
  SDL_Rect solido;
  solido.x = 320;
  solido.y = 200;
  solido.w = 32;
  solido.h = 128;
  
  int fim = 0;
  //loop principal
  while (!fim) {
    //loop de eventos
    //novo loop de eventos
    while (SDL_PollEvent(&evento)) {
      //verifica se clicou em fechar
      if (evento.type == SDL_QUIT)
        fim = 1;
      //verifica se a tecla pra baixo foi apertada
      if (evento.type == SDL_KEYDOWN) {
        if (evento.key.keysym.sym == SDLK_DOWN) {
          //move o player pra baixo +5 pixels
          player.y = player.y + 5;
        }
        //verifica se a tecla apertada é a seta direita
        else if (evento.key.keysym.sym == SDLK_RIGHT) {
          //move o player +5 pixels pra direta
          player.x = player.x + 5;
        }
        //se é a tecla seta esquerda
        else if (evento.key.keysym.sym == SDLK_LEFT) {
          //mova pra esquerda, como que faz?
          //se somar +5 ao player.x é mover pra direita, então diminiuir -5 em player.x é mover pra esquerda!
          //R:
          player.x = player.x - 5;
        }
        else if (evento.key.keysym.sym == SDLK_UP) {
          //mmova pra cima, como que faz?
          //se mover pra baixo é somar +5 ao player.y, então, mover pra cima é diminuir -5 no player.y!
          //R:
          player.y = player.y - 5;
        }
      }
    }
    
    //código de update
    //faz mover, faz pular, etc
    //verifica se o player e o sólido estão colidindo
    //observe que NÃO é separado o player do sólido, mas apenas verifica se estão se tocando! (ui que gostoso se tocar!)
    //se playerColidiuComSolido é 1, e tnão estão colidindo, se é 0 não estão.
    int playerColidiuComSolido = boundingBox(&player, &solido);
    
    //código de desenho na tela
    //aqui é onde o usuário vê o resultado na tela
    
    //limpa a tela com a cor preta
    //toda a tela é desenhado de preto
    SDL_SetRenderDrawColor(renderer, 0,0,0,255);
    SDL_RenderClear(renderer);
    
    //desenha o SÓLIDO na tela com a cor cinza!
    SDL_SetRenderDrawColor(renderer, 128,128,128,255);
    SDL_RenderFillRect(renderer, &solido);
    
    //verifica se NÃO colidiu o player e o sólido
    if (playerColidiuComSolido == 0) {
      //Se NÃO colidiu
      //desenha o player na tela com a cor amarela!
      SDL_SetRenderDrawColor(renderer, 255,255,0,255);
    }
    else {
      //Se colidiu
      //desenha o player na tela com a cor vermelho!
      SDL_SetRenderDrawColor(renderer, 255,0,0,255);
    }
    SDL_RenderFillRect(renderer, &player);
    
    
    //tem que chamar essa função pra mostrar tudo na janela
    SDL_RenderPresent(renderer);
    //NOTA: tem que esperar um pouco antes de ir pra próxima iteração
    SDL_Delay(16);
  }
  
  //por fim libera memória
  SDL_DestroyRenderer(renderer);
  SDL_DestroyWindow(janela);
  SDL_Quit();
  return 0;
}

No exemplo acima, vc deve implementar a função boundingBox() para detectar se os dois retângulos estão colidindo.

Veja o exemplo completo aqui: colisao_boundingbox.c

No próximo tutorial, veremos como separar um retângulo de dentro um do outro e também como melhorar o código da movimentação do player.

Exercícios:

  1. Experimente detectar colisão do mouse com um retângulo na tela. O mouse nada mais é que um ponto (x, y) sem as dimensões (w, h). Vc pode criar outra função chamada pointBox() para detectar se o mouse está dentro ou fora de algum retângulo. Se está dentro de um dos retângulos, então, mudar a cor do retênculo, se está fora, coloque a cor normal do retângulo. NOTA: veja ali em baixo um exemplo de código para pegar as coordenadas do mouse.
  2. Experimente colocar a posição (x, y) do player pra ser a posição (mouseX, mouseY) no código do exercício 1 e veja se continua detectando colisão. Quando colocar o player na posição (mouseX, mouseY) ele vai acompanhar o movimento do mouse dentro da janela e vc pode verifica se continua mudando de cor o player.
  3. Experimente fazer o sólido ficar movendo de um lado a outro na horizontal, basta criar uma variável chamada solidoVelX com valor +5, dai no loop principal fazer solido += solidoVelX, depois verificar se a posição solido.x é maior que 640 (largura da janela), se for, vc coloca solidoVelX = -5 (pra mover pra esquerda), se não se solido.x for menor que zero, então, vc coloca o solidoVelX com valor igual a +5
Para exercício 1:

//declare mouseX e mouseY fora do loop principal e inicialize com valor 0
// no loop de eventos
while (SDL_PollEvent(&evento)) {
  //resto do código dos eventos
  ...
  //esse if será executado se o evento colocado pelo SDL_PollEvent() for do tipo mouse motion
  if (evento.type == SDL_MOUSEMOTION) {
    mouseX = evento.motion.x;
    mouseY = evento.motion.y;
  }
}
//resto do código
//aqui vc pode verificar com sua própria pointBox() se o ponto (mouseX, mouseY)...
//...está dentro do player e depois verificar se está dentro do solido

Comentários

Postagens mais visitadas deste blog

SDL e programação de jogos parte 1

SDL e programação de jogos parte 2