Tutorial: Criando um jogo de desviar o objeto – Parte 4: Implementando a detecção de colisão

Falta muito pouco para finalizarmos o desenvolvimento de mais um game.

Precisamos terminar o jogo no momento em que uma pedra encosta no crânio de cristal.

Bora terminar mais um conjunto de tutoriais.

No tutorial passado nós vimos como funciona a lógica para que as pedras sejam arremessadas de um lado ao outro da tela do aparelho. Vimos também que faltava verificar o momento em que as pedras atingissem o crânio de cristal. Quando uma pedra encosta no crânio, o jogo é para ser finalizado, correto? Então … isso é o que faremos no tutorial de hoje.

Iniciando, vamos realizar as modificações necessárias no código. Não é preciso dizer que para fazer esse tutorial vocês precisam terminar o tutorial passado, né? Abra o arquivo “HelloWorldScene.h” e vamos adicionar a seguinte linha de código:

void calculaColisao();

logo abaixo dessa:

void destroiPedra(cocos2d::CCNode* s);

No mesmo arquivo, adicione essa linha:

cocos2d::CCArray* pedras;

logo abaixo dessa:

cocos2d::CCSprite* cranio;

Adicionamos essas duas linhas porque, a partir de agora, precisaremos armazenar cada nova pedra adicionada em uma lista para. Assim, posteriormente, podemos calcular a colisão entre ela e o crânio. Para isso, criamos uma lista chamada “pedras” e um método chamado “calculaColisao”. Adiante eu explico certinho como funciona o método adicionado.

Feche esse arquivo e abra o arquivo “HelloWorldScene.cpp”. Adicione as seguintes linhas de código:

HelloWorld::pedras = CCArray::create();

HelloWorld::pedras->retain();

logo abaixo dessa linha:

addChild(cranio);

Adicione mais essa linha:

schedule(schedule_selector(HelloWorld::calculaColisao));

logo abaixo dessa:

setTouchEnabled(true);

E agora? O que fizemos? Aqui, a lista de pedras foi inicializada na memória para que possamos utilizá-la quando o jogo iniciar. Logo após foi requisitado ao Cocos2d-x que o método “calculaColisao” seja executado a cada novo quadro mostrado ao jogador. Para que isso? A cada quadro mostrado, o jogo verifica se houve colisão entre o crânio de cristal e alguma pedra. Agora ficou meio óbvio para que serve o método “calculaColisao”, não?! =]

Agora adicione a seguinte linha de código:

HelloWorld::pedras->addObject(pedra);

logo abaixo dessa:

pedra->setPosition(in);

Não obstante, adicione essa linha:

HelloWorld::pedras->removeObject(s,true);

logo abaixo dessa:

void HelloWorld::destroiPedra(CCNode* s) {

Nessa outra edição, nós estamos adicionando a pedra criada na lista de pedras e as removendo quando necessário. Perceba que toda vez que uma nova pedra é criada, ela é adicionada na lista de pedras adicionadas no início desse tutorial. Perceba também que, quando uma pedra finaliza a sua movimentação pela tela, ela também é removida da lista de pedras. Isso faz com que a lista fique sempre atualizada e possua somente as pedras que são mostradas na tela. Agora, basta verificarmos a cada quadro se o crânio de cristal está em colisão com alguma das pedras existentes da lista. Para isso, basta adicionar no final do arquivo o método “calculaColisao”, esse implementado a seguir:

void HelloWorld::calculaColisao() {

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

    static int raioCranio = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“cranio.png”)->getRect().size.width>CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“cranio.png”)->getRect().size.height

        ?CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“cranio.png”)->getRect().size.width*((0.3*size.height)/CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“cranio.png”)->getRect().size.height)*0.5

        :CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“cranio.png”)->getRect().size.height*((0.3*size.height)/CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“cranio.png”)->getRect().size.height)*0.5;

    static int raioPedra = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“pedra.png”)->getRect().size.width>CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“pedra.png”)->getRect().size.height

        ?CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“pedra.png”)->getRect().size.width*((0.2*size.height)/CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“pedra.png”)->getRect().size.height)*0.5

        :CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“pedra.png”)->getRect().size.height*((0.2*size.height)/CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“pedra.png”)->getRect().size.height)*0.5;

    static int distanciaMaxima = (raioCranio + raioPedra)*(raioCranio + raioPedra);

    for(int i=0;i<HelloWorld::pedras->count();i++) {

        int dist = (HelloWorld::cranio->getPosition().x-static_cast<CCNode*>(HelloWorld::pedras->objectAtIndex(i))->getPosition().x)*(HelloWorld::cranio->getPosition().x-static_cast<CCNode*>(HelloWorld::pedras->objectAtIndex(i))->getPosition().x) +

            (HelloWorld::cranio->getPosition().y-static_cast<CCNode*>(HelloWorld::pedras->objectAtIndex(i))->getPosition().y)*(HelloWorld::cranio->getPosition().y-static_cast<CCNode*>(HelloWorld::pedras->objectAtIndex(i))->getPosition().y);

        if(dist<=distanciaMaxima) {

            cleanup();

            setTouchEnabled(false);

            HelloWorld::click = false;

            HelloWorld::pedras->removeAllObjects();

            HelloWorld::pedras->release();

            break;

        }

    }

}

Esse método é um pouco complicado de entender se você for ler diretamente, mas eu vou explicá-lo. Eu detecto colisão entre o crânio e uma pedra simplesmente verificando colisão entre dois objetos esféricos. Imagine um círculo que está exatamente sobre a pedra e um círculo que está exatamente sobre o crânio de cristal. Caso esses círculos se intersectem (um fique sobre o outro), então o jogo detecta uma colisão. Mas, como esses círculos são calculados e como sabemos quando um intersecta outro? Notem que nas primeiras linhas desse método nós calculamos os raios dos círculos referentes ao crânio e às pedras. Logo após, calculamos, para cada pedra da lista, se o crânio está em colisão com ela. Para isso, calculamos a distância entre o crânio e a pedra e verificamos se essa distância é menor ou igual a soma dos raios do crânio e da pedra. Caso seja, isso significa que as esferas se intersectam e, consequentemente, há colisão. Caso haja colisão, desligamos o touchscreen e finalizamos o jogo.

Figura 1 - Jogo finalizado porque foi detectada uma colisão

Figura 1 – Jogo finalizado porque foi detectada uma colisão

Se você executar o jogo agora, aparecerá algo como o que é mostrado na Figura 1. Notem que existe uma colisão entre uma pedra e o crânio, nesse momento o jogo acabou. Aos curiosos de plantão, vejam o jogo executando no vídeo a seguir. Seria interessante se fosse adicionado na tela um label falando que o jogo foi finalizado. Deixo isso a encargo de vocês, leitores e programadores. ;D

Vimos nesse tutorial como implementar o sistema de detecção de colisão no game de desviar o crânio de cristal das pedras. Vimos que o sistema funciona com duas esferas imaginárias: uma que limita o crânio e uma que limita as pedras. A ideia implementada é verificar quando a esfera do crânio intersecta uma esfera referente a uma das pedras lançadas. Isso é detectado realizando o cálculo de distância entre o centro do crânio e o centro de cada pedra. Caso essa distância seja menor do que a soma dos raios das esferas, então é detectada a colisão. Simples, não? =]

Eu ainda não sei qual será o tema do próximo tutorial. Provavelmente será sobre o início da implementação de outro jogo.

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

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