Desenvolvendo um Jogo Digital do Zero: Parte 12 – Sistema de colisão

O Shark Pong já tem interação com o jogador, mas não há desafio no jogo.

Se o tubarão não andar, o jogo fica sem um objetivo e, consequentemente, sem diversão.

Vamos implementar a movimentação do tubarão e o sistema de colisões entre ele e o resto do cenário.

Todas as partes da saga:

https://fabricadejogos.net/colunas/producao-jogo-digital-do-zero

Incluímos no artigo passado os elementos de interação com o jogo e programamos as suas funcionalidades. São eles: os dois botões em forma de concha que, quando são clicados, o surfista controlado pelo jogador sobe ou desce.

Nesse artigo, nós implementaremos o sistema de movimentação do tubarão e o sistema de colisão dele com as paredes e com os surfistas. Mas, antes de tudo, vamos a mais algumas correções necessárias.

 

Correção de erros

Como de praxe, sempre tem um problema simples para resolver. Dessa vez será apenas transportar um trecho de código de um lugar do arquivo para outro. O problema está, basicamente, no sprite do tubarão. Se você analisar o código inserido no primeiro artigo de programação da série, perceberá que o sprite do tubarão foi adicionado na tela depois dos sprites dos surfistas. Isso faz com que o tubarão esteja na frente dos surfistas durante a execução do jogo.

É claro que isso não deve acontecer, visto que o tubarão está imerso e deve ficar abaixo dos sprites dos surfistas. O que acontece é algo parecido com o que é mostrado na Figura 1. Precisamos deixar como é mostrado na parte direita da figura e, na versão atual, o jogo está implementado como o acontece na parte esquerda.

Figura 1 - Erro de camadas

Figura 1 – Erro de camadas

Para corrigir isso, reposicione o seguinte trecho do código no arquivo “HelloWorldScene.cpp”:

HelloWorld::tubarao = CCSprite::createWithSpriteFrameName(“Tubarao1.png”);

HelloWorld::tubarao->setScale((0.2*size.height)/HelloWorld::tubarao->boundingBox().size.height);

HelloWorld::tubarao->setPosition(ccp(size.width/2,size.height/2));

addChild(HelloWorld::tubarao);

para baixo da seguinte linha de código:

addChild(fundo);

Isso é o suficiente para o sprite do tubarão ficar abaixo dos sprites dos surfistas e do polvo, dando o aspecto de que ele está submerso no mar.

 

Física de movimento uniforme

Utilizaremos conceitos de física para fazer o tubarão andar na tela. Faremos com que ele nade em movimento constante com o passar do tempo. Dessa forma, precisamos entender como funciona o movimento uniforme.

É possível calcular a posição do tubarão na tela com base na sua posição inicial e no tempo corrido entre quadros. Esse cálculo é feito por meio da fórmula do movimento uniforme. Tal fórmula e um exemplo de sua aplicação são mostrados na Figura 2.

Figura 2 - Movimento Uniforme

Figura 2 – Movimento Uniforme

Perceba que a fórmula envolve o uso de quatro variáveis: a posição inicial, a posição final, a velocidade e o tempo. Trazendo isso para o nosso jogo, precisaremos criar uma variável de tempo corrido, uma variável de velocidade e uma variável de posição inicial do tubarão. Não precisaremos criar uma de posição final porque essa será calculada a cada novo quadro processado e não precisa ser armazenada à parte.

Primeiramente, criaremos as variáveis na classe “HelloWorld”, para que possamos fazer com que o tubarão se movimente. Também adicionaremos os protótipos dos métodos que implementaremos nesse artigo. Abra o arquivo “HelloWorldScene.h” e adicione as seguintes linhas de código:

float tempo;

float velocidadeTubarao;

float vetorDirecaoTubarao[2];

float posicaoInicialTubarao[2];

void atualizaQuadro(float dt);

void aumentaVelocidadeTubarao();

logo abaixo dessa:

int idToque;

Perceba que temos um vetor de variáveis que armazena duas posições iniciais e um vetor de variáveis que armazena a direção do tubarão. Como o tubarão se movimentará na vertical e na horizontal, faz-se necessário armazenar essas informações para ambos os eixos de coordenadas. Salve o arquivo “HelloWorldScene.h”. Agora inicializaremos as variáveis criadas para que o jogo inicie corretamente. Abra o arquivo “HelloWorldScene.cpp” e adicione as seguintes linhas de código:

HelloWorld::tubarao->setAnchorPoint(ccp(16.0/63.0,21.0/38.0));

HelloWorld::tempo = 0.0;

HelloWorld::velocidadeTubarao = 0.2*size.width;

HelloWorld::vetorDirecaoTubarao[0] = -1.0;

HelloWorld::vetorDirecaoTubarao[1] = 0.0;

HelloWorld::posicaoInicialTubarao[0] = HelloWorld::tubarao->getPositionX();

HelloWorld::posicaoInicialTubarao[1] = HelloWorld::tubarao->getPositionY();

schedule(schedule_selector(HelloWorld::atualizaQuadro));

logo abaixo dessa:

setTouchEnabled(true);

Note que mudamos o ponto âncora do sprite do tubarão de forma que a barbatana sirva como referência para a movimentação e rotação do sprite. Além disso, nós inicializamos as variáveis físicas declaradas e fizemos com que o método “atualizaQuadro” seja chamado a cada novo quadro processado. É nele que faremos o tubarão andar.

 

Rebatendo o tubarão nas paredes

Antes de pensarmos em fazer o tubarão rebater nas paredes, precisamos pensar em como faremos ele se movimentar. Utilizaremos o conceito de movimento uniforme e calcularemos a posição do tubarão na tela conforme o tempo corrido e a posição inicial dele. Após reposicionar o sprite do tubarão, verificaremos se a sua barbatana passa dos limites da tela. Caso isso ocorra, nós atualizaremos as variáveis físicas e espelharemos a sua direção para que ele não saia da tela. Adicione as seguintes linhas de código no final do arquivo “HelloWorldScene.cpp”:

void HelloWorld::atualizaQuadro(float dt) {

    float rot;

    float posicao[2];

    CCSize size = CCDirector::sharedDirector()->getWinSize();

    HelloWorld::tempo += dt;

    posicao[0] = HelloWorld::posicaoInicialTubarao[0] + (HelloWorld::velocidadeTubarao*HelloWorld::vetorDirecaoTubarao[0]) *HelloWorld::tempo;

    posicao[1] = HelloWorld::posicaoInicialTubarao[1] + (HelloWorld::velocidadeTubarao*HelloWorld::vetorDirecaoTubarao[1]) *HelloWorld::tempo;

    HelloWorld::tubarao->setPosition(ccp(posicao[0],posicao[1]));

    if((HelloWorld::tubarao->getPositionX()<0&&HelloWorld::vetorDirecaoTubarao[0]<0)||

      (HelloWorld::tubarao->getPositionX()>size.width&&HelloWorld::vetorDirecaoTubarao[0]>0)) {

        HelloWorld::tempo = 0.0;

        HelloWorld::vetorDirecaoTubarao[0] = -HelloWorld::vetorDirecaoTubarao[0];

        HelloWorld::posicaoInicialTubarao[0] = HelloWorld::tubarao->getPositionX();

        HelloWorld::posicaoInicialTubarao[1] = HelloWorld::tubarao->getPositionY();

        rot = 180.0 – (180.0*acos(HelloWorld::vetorDirecaoTubarao[0]))/M_PI;

        if(HelloWorld::vetorDirecaoTubarao[1]<0)

            rot = 360.0 – rot;

        HelloWorld::tubarao->setRotation(rot);

    }

    if((HelloWorld::tubarao->getPositionY()<0.3*size.height-HelloWorld::paJogador->boundingBox().size.height/2&&HelloWorld::vetorDirecaoTubarao[1]<0)||

      (HelloWorld::tubarao->getPositionY()>size.height&&HelloWorld::vetorDirecaoTubarao[1]>0)) {

        HelloWorld::tempo = 0.0;

        HelloWorld::vetorDirecaoTubarao[1] = -HelloWorld::vetorDirecaoTubarao[1];

        HelloWorld::posicaoInicialTubarao[0] = HelloWorld::tubarao->getPositionX();

        HelloWorld::posicaoInicialTubarao[1] = HelloWorld::tubarao->getPositionY();

        rot = 180.0 – (180.0*acos(HelloWorld::vetorDirecaoTubarao[0]))/M_PI;

        if(HelloWorld::vetorDirecaoTubarao[1]<0)

            rot = 360.0 – rot;

        HelloWorld::tubarao->setRotation(rot);

    }

}

Foi adicionado bastante código, mas você perceberá que ele não é tão complicado assim de entender. Primeiramente, nós somamos o tempo entre quadros na variável “tempo”. Logo após, nós calculamos a posição horizontal e vertical do tubarão utilizando a fórmula da Figura 2 e posicionamos o sprite do tubarão na posição calculada. Depois disso, nós verificamos se o tubarão saiu da tela pela horizontal ou pela vertical. Caso ocorra qualquer um dos dois, nós atualizamos as variáveis físicas para que o tubarão mude o sentido.

Perceba que, sempre quando o tubarão muda a direção, é necessário atualizar todas as variáveis físicas criadas. Perceba também que nós calculamos a angulação dele em relação a tela conforme a direção que ele está seguindo e fizemos com que ele aponte para a direção de onde ele está se movimentando. Com isso, o tubarão não sai da tela e faz um movimento semelhante a uma bola rolando sobre uma mesa de bilhar.

 

Rebatendo o tubarão nas pás

Agora que o tubarão já rebate nas paredes, faremos com que ele rebata nas pranchas dos surfistas. Primeiramente, identificamos quando a barbatana do tubarão está sobre a metade da frente do sprite do surfista. Caso isso aconteça, então é recalculada a direção do tubarão com base nas posições do surfista e do tubarão. Se o tubarão está mais para cima do surfista, ele é rebatido na diagonal superior e o contrário da mesma forma. Isso faz com que o jogador tenha controle sobre a direção que o tubarão tomará apenas decidindo a posição correta para o surfista.

Da mesma forma como fizemos o tubarão mudar a direção, nós precisaremos atualizar as variáveis físicas para que o tubarão se movimente corretamente e precisaremos, também, recalcular o ângulo que o sprite deverá ficar. Para isso, adicione as seguintes linhas de código no final do método “atualizaQuadro”, também no arquivo “HelloWorldScene.cpp”:

if(HelloWorld::tubarao->getPositionX()<=HelloWorld::paJogador->getPositionX()+HelloWorld::paJogador->boundingBox().size.width/2&&

  HelloWorld::vetorDirecaoTubarao[0]<0) {

    if((HelloWorld::tubarao->getPositionY()<=HelloWorld::paJogador->getPositionY()+HelloWorld::paJogador->boundingBox().size.height/2)&&

      (HelloWorld::tubarao->getPositionY()>=HelloWorld::paJogador->getPositionY()-HelloWorld::paJogador->boundingBox().size.height/2)) {

        float modulo;

        HelloWorld::vetorDirecaoTubarao[0] = HelloWorld::tubarao->getPositionX() – HelloWorld::paJogador->getPositionX();

        HelloWorld::vetorDirecaoTubarao[1] = HelloWorld::tubarao->getPositionY() – HelloWorld::paJogador->getPositionY();

        modulo = sqrt(pow(HelloWorld::vetorDirecaoTubarao[0],2) + pow(HelloWorld::vetorDirecaoTubarao[1],2));

        HelloWorld::vetorDirecaoTubarao[0] /= modulo;

        HelloWorld::vetorDirecaoTubarao[1] /= modulo;

        HelloWorld::tempo = 0;

        HelloWorld::posicaoInicialTubarao[0] = HelloWorld::tubarao->getPositionX();

        HelloWorld::posicaoInicialTubarao[1] = HelloWorld::tubarao->getPositionY();

        rot = 180.0 – (180.0*acos(HelloWorld::vetorDirecaoTubarao[0]))/M_PI;

        if(HelloWorld::vetorDirecaoTubarao[1]<0)

            rot = 360.0 – rot;

        HelloWorld::tubarao->setRotation(rot);

    }

} else if(HelloWorld::tubarao->getPositionX()>=HelloWorld::paIA->getPositionX()-HelloWorld::paIA->boundingBox().size.width/2&&

  HelloWorld::vetorDirecaoTubarao[0]>0)

    if((HelloWorld::tubarao->getPositionY()<=HelloWorld::paIA->getPositionY()+HelloWorld::paIA->boundingBox().size.height/2)&&

      (HelloWorld::tubarao->getPositionY()>=HelloWorld::paIA->getPositionY()-HelloWorld::paIA->boundingBox().size.height/2)) {

        float modulo;

        HelloWorld::vetorDirecaoTubarao[0] = HelloWorld::tubarao->getPositionX() – HelloWorld::paIA->getPositionX();

        HelloWorld::vetorDirecaoTubarao[1] = HelloWorld::tubarao->getPositionY() – HelloWorld::paIA->getPositionY();

        modulo = sqrt(pow(HelloWorld::vetorDirecaoTubarao[0],2) + pow(HelloWorld::vetorDirecaoTubarao[1],2));

        HelloWorld::vetorDirecaoTubarao[0] /= modulo;

        HelloWorld::vetorDirecaoTubarao[1] /= modulo;

        HelloWorld::tempo = 0;

        HelloWorld::posicaoInicialTubarao[0] = HelloWorld::tubarao->getPositionX();

        HelloWorld::posicaoInicialTubarao[1] = HelloWorld::tubarao->getPositionY();

        rot = 180.0 – (180.0*acos(HelloWorld::vetorDirecaoTubarao[0]))/M_PI;

        if(HelloWorld::vetorDirecaoTubarao[1]<0)

            rot = 360.0 – rot;

        HelloWorld::tubarao->setRotation(rot);

    }

Agora falta apenas fazermos com que o tubarão aumente a velocidade periodicamente.

 

Aumentar a velocidade do tubarão com o tempo

Implementaremos o aumento de velocidade do tubarão periodicamente. Isso significa que, a cada 10 segundos, o tubarão aumentará 10% da velocidade que ele tinha no início do jogo. Com o tempo, ele fica mais rápido e mais desafiador de rebatê-lo. Para isso, ainda no arquivo “HelloWorldScene.cpp”, adicione a seguinte linha de código:

schedule(schedule_selector(HelloWorld::aumentaVelocidadeTubarao),10);

logo abaixo dessa:

schedule(schedule_selector(HelloWorld::atualizaQuadro));

e as seguintes linhas de código no final do arquivo.

void HelloWorld::aumentaVelocidadeTubarao() {

    CCSize size = CCDirector::sharedDirector()->getWinSize();

    HelloWorld::tempo = 0.0;

    HelloWorld::velocidadeTubarao += 0.1*0.2*size.width;

    HelloWorld::posicaoInicialTubarao[0] = HelloWorld::tubarao->getPositionX();

    HelloWorld::posicaoInicialTubarao[1] = HelloWorld::tubarao->getPositionY();

}

Perceba que as variáveis físicas também precisam ser atualizadas, visto que a velocidade foi mudada. Agora sim, salve os dois arquivos modificados e compile o código. Ao executar o jogo, você verá algo com a animação a seguir. Note que o tubarão rebate nas paredes do celular, e nos surfistas.

Vimos nesse artigo como adicionar a movimentação no tubarão e fizemos com que ele rebata nas paredes e nas pás. Foi necessário entendermos o funcionamento do movimento uniforme para que ele pudesse ser aplicado no Shark Pong. Além disso, fizemos com que o tubarão se movimentasse mais rapidamente com o passar do tempo e arrumamos um pequeno “bug” que fazia com que o sprite do tubarão ficasse na frente do sprite dos surfistas.

No próximo artigo, nós implementaremos o sistema de inteligência artificial que fará com que o surfista oponente responda à movimentação do tubarão.

Um grande abraço e até mais. []

Santiago Viertel

Santiago Viertel

Formado em Bacharelado em Ciência da Computação (UDESC), mestre e doutorando em Análise de Algoritmos (UFPR). Foi programador da Céu Games por 8 anos. Possui a preferência por jogos de estratégia e de tiro em primeira pessoa. Jogando bastante DotA 2, Left 4 Dead 2 e Age of Empires II HD.

Send this to a friend