Tutorial: Criando um jogo ao estilo Arkanoid – Parte 5: Quebrando os blocos com a bola de fogo

Estamos nos encaminhando para os últimos tutoriais sobre o desenvolvimento de um jogo ao estilo Arkanoid.

Incluiremos hoje a segunda principal funcionalidade do game: a destruição dos blocos amarelos.

Bora implementar colisões.

No último tutorial implementamos a detecção e a resposta de colisão entre a bola de fogo e a pá. Vimos que a pá rebate a bola em uma angulação específica, conforme o local em que a bola encosta na pá. Vimos também como reiniciar o jogo, caso a bola de fogo saia pela parte inferior da tela. Nesse tutorial implementaremos uma funcionalidade que possibilita criarmos um objetivo para o jogo. Como em todos os jogos ao estilo Arkanoid, esse possui como principal objetivo destruir todos os blocos localizados na parte superior da tela. Porém, a destruição dos blocos ainda não foi implementada no nosso game e é exatamente isso o que faremos hoje.

Antes de tudo eu preciso lembrar que, se você não fez o tutorial passado, não terá como continuar a implementação desse. Dessa forma, desempenhe o tutorial passado para darmos continuidade. Abra o arquivo “HelloWorldScene.h” e adicione a seguinte linha de código:

cocos2d::CCArray* blocos;

logo abaixo dessa:

cocos2d::CCSprite* bola;

Com essa linha de código criamos uma referência a uma lista de blocos amarelos. Isso é necessário para que possamos avaliar constantemente se existe colisão entre a bola de fogo e algum bloco amarelo adicionado no início do jogo. Além disso, essa lista existe para que possamos ter controle sobre um bloco que colide com a bola de fogo. Assim, poderemos fazer com que ele suma da tela do aparelho caso a bola de fogo encoste nele. Dando continuidade, você pode salvar e fechar o atual arquivo e abrir o arquivo “HelloWorldScene.cpp”. No arquivo aberto recentemente, modifique as seguintes linhas de código:

CCSprite* bloco = CCSprite::createWithSpriteFrameName(“bloco.png”);

bloco->setScale((0.1*size.height)/bloco->boundingBox().size.height);

bloco->setPosition(ccp(size.width/2,0.6*size.height));

addChild(bloco);

por essas:

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

HelloWorld::blocos->retain();

for(int i=0;i<18;i++) {

    CCSprite* bloco = CCSprite::createWithSpriteFrameName(“bloco.png”);

    bloco->setScale((0.1*size.height)/bloco->boundingBox().size.height);

    bloco->setPosition(ccp(size.width/2 + (i%6 – 2.5)*bloco->boundingBox().size.width,0.8*size.height – (i/6)*bloco->boundingBox().size.height));

    HelloWorld::blocos->addObject(bloco);

    addChild(bloco);

}

Nessa modificação, nós fizemos com que não fosse criado apenas um único bloco amarelo para ser destruído. Ao invés disso, criamos uma lista de blocos amarelos e os posicionamos na parte superior da tela do aparelho. Mais especificamente, adicionamos três fileiras com 6 blocos cada, totalizando 18 blocos amarelos. A Figura 1 mostra como os blocos foram criados.

Figura 1 - Fileiras de blocos amarelos

Figura 1 – Fileiras de blocos amarelos

Agora, basta implementarmos a detecção e a resposta de colisão entre a bola de fogo e os blocos amarelos. Note que a detecção é realizada a cada novo quadro mostrado na tela. Isso porque a bola de fogo pode, de um quadro para o outro, estar em colisão com um bloco amarelo, pois ela está em constante movimento. Dessa forma, precisaremos adicionar mais um trecho de código ao final do método “moveBola”. Assim sendo, adicione as seguintes linhas de código:

float menorDist=size.width+size.height,dx,dy;

CCSprite* blocoEscolhido=NULL;

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

    CCSprite* bloco = static_cast<CCSprite*>(HelloWorld::blocos->objectAtIndex(i));

    float dist = sqrt((HelloWorld::bola->getPositionX()-bloco->getPositionX())*(HelloWorld::bola->getPositionX()-bloco->getPositionX())+(HelloWorld::bola->getPositionY()-bloco->getPositionY())*(HelloWorld::bola->getPositionY()-bloco->getPositionY()));

    if(HelloWorld::bola->boundingBox().intersectsRect(bloco->boundingBox())&&dist<menorDist) {

        if((HelloWorld::vetorDirecao[0]>0&&HelloWorld::bola->getPositionX()+HelloWorld::bola->boundingBox().size.width/2>bloco->getPositionX()-bloco->boundingBox().size.width/2&&HelloWorld::bola->getPositionX()<bloco->getPositionX()) ||

          (HelloWorld::vetorDirecao[0]<0&&HelloWorld::bola->getPositionX()-HelloWorld::bola->boundingBox().size.width/2<bloco->getPositionX()+bloco->boundingBox().size.width/2&&HelloWorld::bola->getPositionX()>bloco->getPositionX())) {

            menorDist = dist;

            blocoEscolhido = bloco;

        }

        else if((HelloWorld::vetorDirecao[1]>0&&HelloWorld::bola->getPositionY()+HelloWorld::bola->boundingBox().size.height/2>bloco->getPositionY()-bloco->boundingBox().size.height/2&&HelloWorld::bola->getPositionY()<bloco->getPositionY()) ||

          (HelloWorld::vetorDirecao[1]<0&&HelloWorld::bola->getPositionY()-HelloWorld::bola->boundingBox().size.height/2<bloco->getPositionY()+bloco->boundingBox().size.height/2&&HelloWorld::bola->getPositionY()>bloco->getPositionY())) {

            menorDist = dist;

            blocoEscolhido = bloco;

        }

    }

}

if(blocoEscolhido!=NULL) {

    if((HelloWorld::vetorDirecao[0]>0&&HelloWorld::bola->getPositionX()+HelloWorld::bola->boundingBox().size.width/2>blocoEscolhido->getPositionX()-blocoEscolhido->boundingBox().size.width/2&&HelloWorld::bola->getPositionX()<blocoEscolhido->getPositionX()) ||

      (HelloWorld::vetorDirecao[0]<0&&HelloWorld::bola->getPositionX()-HelloWorld::bola->boundingBox().size.width/2<blocoEscolhido->getPositionX()+blocoEscolhido->boundingBox().size.width/2&&HelloWorld::bola->getPositionX()>blocoEscolhido->getPositionX()))

        dx = abs(HelloWorld::bola->getPositionX() – blocoEscolhido->getPositionX());

    else

        dx = 0;

    if((HelloWorld::vetorDirecao[1]>0&&HelloWorld::bola->getPositionY()+HelloWorld::bola->boundingBox().size.height/2>blocoEscolhido->getPositionY()-blocoEscolhido->boundingBox().size.height/2&&HelloWorld::bola->getPositionY()<blocoEscolhido->getPositionY()) ||

      (HelloWorld::vetorDirecao[1]<0&&HelloWorld::bola->getPositionY()-HelloWorld::bola->boundingBox().size.height/2<blocoEscolhido->getPositionY()+blocoEscolhido->boundingBox().size.height/2&&HelloWorld::bola->getPositionY()>blocoEscolhido->getPositionY()))

        dy = abs(HelloWorld::bola->getPositionY() – blocoEscolhido->getPositionY());

    else

        dy = 0;

    if(dx>dy) {

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

        removeChild(blocoEscolhido);

        HelloWorld::blocos->removeObject(blocoEscolhido,true);

    }

    else {

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

        removeChild(blocoEscolhido);

        HelloWorld::blocos->removeObject(blocoEscolhido,true);

    }

}

logo abaixo dessas linhas:

    HelloWorld::vetorDirecao[0] = HelloWorld::vetorDirecao[0]/tamanho;

    HelloWorld::vetorDirecao[1] = HelloWorld::vetorDirecao[1]/tamanho;

}

Fizemos bastantes coisas aqui e todas merecem ser explanadas. Primeiramente é necessário entender que, de um quadro para o outro, a bola de fogo pode entrar em colisão com mais de um bloco amarelo simultaneamente. A Figura 2 mostra um exemplo de quando a bola de fogo entra em colisão com dois blocos amarelos ao mesmo tempo. Dessa forma, primeiramente é encontrado qual bloco amarelo está em colisão com a bola de fogo e que está mais próximo dela. Isso é feito até a primeira metade do bloco de código que adicionamos.

Figura 2 - Colisão da bola de fogo

Figura 2 – Colisão da bola de fogo

Na outra metade, é decidido se a bola de fogo está colidindo em um dos lados do bloco mais próximo ou se ela está colidindo na parte superior ou inferior dele. Para isso, é verificado se a bola de fogo interpenetrou o bloco mais verticalmente ou horizontalmente. Caso ela tenha interpenetrado mais verticalmente, então existe colisão em cima ou embaixo do bloco. Caso isso aconteça, o bloco é retirado da tela e o valor de coordenada y do vetor direção é multiplicado por -1. O mesmo é feito caso a bola de fogo interpenetre mais horizontalmente o bloco amarelo. Isso fará com que a bola de fogo sempre colida com o bloco amarelo mais próximo dela e rebata, tanto verticalmente como horizontalmente. Legal, não?!

Nesse tutorial nós adicionamos mais uma funcionalidade no nosso game ao estilo Arkanoid. Essa funcionalidade nos proporcionou incluir um objetivo no jogo: destruir todos os blocos amarelos. Implementamos a detecção e a resposta de colisão entre a bola de fogo e os blocos amarelos. Vimos que existe a possibilidade da bola de fogo colidir com mais de um bloco amarelo simultaneamente. Dessa forma, precisamos encontrar qual o bloco mais próximo que há colisão. Achando esse bloco, nós o retiramos da tela e invertemos um dos componentes do vetor direção da bola de fogo. No próximo tutorial adicionaremos as HUDs no nosso jogo e finalizaremos mais um conjunto de tutoriais no qual desenvolvemos um game.

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