A seguir temos duas caixas que não estão colidindo. Como você pode ver, suas projeções x estão na parte inferior e suas projeções y no lado esquerdo:
//The dot that will move around on the screen class Dot { public: //The dimensions of the dot static const int DOT_WIDTH = 20; static const int DOT_HEIGHT = 20; //Maximum axis velocity of the dot static const int DOT_VEL = 10; //Initializes the variables Dot(); //Takes key presses and adjusts the dot's velocity void handleEvent( SDL_Event& e ); //Moves the dot and checks collision void move( SDL_Rect& wall ); //Shows the dot on the screen void render(); private: //The X and Y offsets of the dot int mPosX, mPosY; //The velocity of the dot int mVelX, mVelY; //Dot's collision box SDL_Rect mCollider; };
Aqui temos o ponto do tutorial de movimento com alguns recursos novos. A função de movimento pega um retângulo que é a caixa de colisão para a parede e o ponto tem uma variável membro chamada mCollider que representa a caixa de colisão.
//Starts up SDL and creates window bool init(); //Loads media bool loadMedia(); //Frees media and shuts down SDL void close(); //Box collision detector bool checkCollision( SDL_Rect a, SDL_Rect b );
Também declaramos uma função para checar se duas caixas colidiram entre si.
Dot::Dot() { //Initialize the offsets mPosX = 0; mPosY = 0; //Set collision box dimension mCollider.w = DOT_WIDTH; mCollider.h = DOT_HEIGHT; //Initialize the velocity mVelX = 0; mVelY = 0; }
O construtor deve certificar que as dimensões de mCollider foram ajustadas.
void Dot::move( SDL_Rect& wall ) { //Move the dot left or right mPosX += mVelX; mCollider.x = mPosX; //If the dot collided or went too far to the left or right if( ( mPosX < 0 ) || ( mPosX + DOT_WIDTH > SCREEN_WIDTH ) || checkCollision( mCollider, wall ) ) { //Move back mPosX -= mVelX; mCollider.x = mPosX; } //Move the dot up or down mPosY += mVelY; mCollider.y = mPosY; //If the dot collided or went too far up or down if( ( mPosY < 0 ) || ( mPosY + DOT_HEIGHT > SCREEN_HEIGHT ) || checkCollision( mCollider, wall ) ) { //Move back mPosY -= mVelY; mCollider.y = mPosY; } }
Aqui temos a nova função de movimento que agora verifica se atingimos a parede. Ela funciona de forma parecida com a versão anterior, apenas que agora faz com que o ponto mova-se para trás se for para fora tela ou atingir a parede.
Primeiro movemos o ponto pelo eixo x, mas também temos que alterar a posição do mCollider. Onde quer que seja que alteremos a posição do ponto, a posição de mCollider tem que ser alterada também. Em seguida verificamos se o ponto foi para fora da tela. Caso afirmativo, movemos o ponto para trás no eixo x. Finalmente, fazemos isso novamente para o movimento no eixo y.
bool checkCollision( SDL_Rect a, SDL_Rect b ) { //The sides of the rectangles int leftA, leftB; int rightA, rightB; int topA, topB; int bottomA, bottomB; //Calculate the sides of rect A leftA = a.x; rightA = a.x + a.w; topA = a.y; bottomA = a.y + a.h; //Calculate the sides of rect B leftB = b.x; rightB = b.x + b.w; topB = b.y; bottomB = b.y + b.h;
Aqui é onde a detecção da colisão acontece. Esse código calcula a projeção superior/inferior e esquerda/direita de cada caixa de colisão.
//If any of the sides from A are outside of B if( bottomA <= topB ) { return false; } if( topA >= bottomB ) { return false; } if( rightA <= leftB ) { return false; } if( leftA >= rightB ) { return false; } //If none of the sides from A are outside B return true; }
Aqui é onde realizamos nosso teste de separação de eixo x. Primeiro checamos as partes superior e inferior das caixas para verificar se elas estão separadas no eixo y. Em seguida checamos as parte esquerda e direita das caixas para verificar se elas estão separadas pelo eixo x. Se houver alguma separação, então não há colisão e retornamos false. Se não pudermos descobrir qualquer separação, então existe uma colisão e retornamos true.
Observação: o SDL tem algumas funções embutidas de detecção de colisão, mas nesse artigo estaremos criando nosso própria. Principalmente porque é importante saber como isso é feito e depois porque se você puder criar a sua própria função de detecção de colisão pode usa-la com a renderização do SDL, Direct3D, Mantle, Metal ou qualquer outra API de renderização.
//Main loop flag bool quit = false; //Event handler SDL_Event e; //The dot that will be moving around on the screen Dot dot; //Set the wall SDL_Rect wall; wall.x = 300; wall.y = 40; wall.w = 40; wall.h = 400;
Antes de entrarmos no loop principal, declaramos o ponto e definimos a posição de dimensão da parede.
//While application is running while( !quit ) { //Handle events on queue while( SDL_PollEvent( &e ) != 0 ) { //User requests quit if( e.type == SDL_QUIT ) { quit = true; } //Handle input for the dot dot.handleEvent( e ); } //Move the dot and check collision dot.move( wall ); //Clear screen SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF ); SDL_RenderClear( gRenderer ); //Render wall SDL_SetRenderDrawColor( gRenderer, 0x00, 0x00, 0x00, 0xFF ); SDL_RenderDrawRect( gRenderer, &wall ); //Render dot dot.render(); //Update screen SDL_RenderPresent( gRenderer ); }
Nesse momento inicial, quando você quer criar algo simples como tetris, esse tipo de detecção de colisão é ok. Para algo mais como um simulador de física para coisas, isso fica muito mais complicado.
Para algo como um simulador de um corpo rígido, temos que criar nossa lógica para fazer essas tarefas a cadas frame:
- Aplicar todas as forças em todos os objetos na cena (gravidade, vento, propulsão, etc)
- Mover os objetos pela aplicação da aceleração e velocidade à sua posição.
- Verificar por colisões para todos os objetos e criar um conjunto de contatos. Uma contato é uma estrutura de dados que tipicamente contem ponteiros para dois objetos que colidiram, um vetor normal do primeiro para o segundo objeto, e a quantidade que os objetos estão penetrando.
- Pegue o conjunto de contatos gerado e resolva as colisões. Isso tipicamente envolve verificar os contatos novamente (dentro de um limite) e resolvê-los.
Se você mal aprendeu sobre colisão de detecções, isso tudo está fora de seu alcance. Isso poderia usar todos um conjunto de tutoriais para ser explicado. Não apenas isso, mas envolve matemática de vetores e física que estão além do escopo desse artigo. Apenas tenha em mente que mais tarde quando você precisar programar jogos que tenham grandes quantidades de objetos colidindo e esteja se perguntando como toda a estrutura para um engine de física funciona.