Tutorial: Desenvolvendo um Jogo de Sinuca em Cocos2d-x: Parte 5 – Sistema de caçapas

Falta pouco para finalizarmos mais um jogo.

Já temos um protótipo de um jogo de sinuca jogável.

E hoje, faremos com que as bolas de bilhar caiam nas caçapas.

Parte 1Parte 2Parte 3Parte 4 – Parte 5 – Parte 6

Vimos no último tutorial como realizar uma tacada. Para que o jogador pudesse mirar e realizar a tacada, implementamos os métodos de interação com a tela de toque. Enquanto o jogador estiver com o dedo encostado na tela, ele pode mirar a tacada. A tacada é realizada quando o jogador retira o dedo da tela. Também desabilitamos a tela de toque enquanto as bolas estão em movimentação para evitar que o jogador realize uma tacada em um momento inapropriado. A tela de toque volta a ser habilitada quando todas as bolas estiverem paradas.

Implementaremos nesse tutorial o sistema de caçapas. Faremos com que uma bola saia da simulação física caso ela caia em uma boca. Também nos preocuparemos em reposicionar a bola branca caso ela seja encaçapada.

Bora começar esse tutorial.

 

Preparando os sprites

PreparacaoIniciaremos o tutorial de hoje nos preocupando com o principal acontecimento decorrente do jogador encaçapar uma bola. Quando uma bola é encaçapada, tanto o seu sprite como o seu corpo rígido precisam ser retirados da simulação. A melhor forma de fazer isso, sem que haja muita mudança no código fonte, é retirar os sprites da tela. O problema dessa abordagem está em, ao retirar os sprites, eles são liberados da memória automaticamente pelo motor do Cocos2d-x.

Precisamos deixar os sprites das bolas intactos na memória ao retirá-los da tela. Isso porque teremos que reincorporá-los à simulação e precisaremos realizar testes com eles mesmo quando eles não estão sendo apresentados na tela. Para não excluirmos eles da memória ao retirá-los da tela, basta realizar uma chamada ao método “retain”. Os sprites que não podem ser excluídos da memória são aqueles referentes às bolas de bilhar. Assim, chamaremos os métodos retain referentes a cada um dos sprites das bolas. Abra o arquivo “HelloWorldScene.cpp” e adicione a seguinte linha de código:

HelloWorld::bolaBranca->retain();

logo abaixo dessa:

0.25*HelloWorld::mesa->getBoundingBox().size.width,HelloWorld::mesa->getPositionY());

a seguinte linha de código:

HelloWorld::bolaVermelha->retain();

logo abaixo dessa:

HelloWorld::mesa->getPositionY()+HelloWorld::bolaVermelha->getBoundingBox().size.height/2);

e a seguinte linha de código:

HelloWorld::bolaAmarela->retain();

logo abaixo dessa:

HelloWorld::mesa->getPositionY()-HelloWorld::bolaAmarela->getBoundingBox().size.height/2);

Agora implementaremos o sistema de caçapas.

 

Implementando a funcionalidade das caçapas

Linha-montagemO sistema de caçapas não é complicado de entender nem de implementar. Ele é baseado em execuções consecutivas de um método específico enquanto existe movimentação das bolas sobre a mesa. Esse método, que será nomeado “encacapar”, possuirá as funções de verificar se alguma das bolas caiu em alguma caçapa e de tratar esse evento. Simples assim. Dessa forma, precisaremos declarar o protótipo desse método na classe “HelloWorld”. Abra o arquivo “HelloWorldScene.h” e adicione a seguinte linha de código:

void encacapar(float dt);

logo abaixo dessa:

cocos2d::EventListenerTouchOneByOne* toqueNaTela;

Com o protótipo devidamente declarado, podemos começar a implementação do método “encacapar”. Abra o arquivo “HelloWorldScene.cpp” e adicione as seguintes linhas de código no final dele.

void HelloWorld::encacapar(float dt) {

    static float raioCacapas = pow(HelloWorld::bolaBranca->getBoundingBox().size.width/2.0,2.0);

    static float cacapas[6][2] = {

      {HelloWorld::mesa->getPositionX() – (348.0f/800.0f)*HelloWorld::mesa->getBoundingBox().size.width,

        HelloWorld::mesa->getPositionY() + (174.0f/454.0f)*HelloWorld::mesa->getBoundingBox().size.height},

      {HelloWorld::mesa->getPositionX(),

        HelloWorld::mesa->getPositionY() + (189.0f/454.0f)*HelloWorld::mesa->getBoundingBox().size.height},

      {HelloWorld::mesa->getPositionX() + (346.0f/800.0f)*HelloWorld::mesa->getBoundingBox().size.width,

        HelloWorld::mesa->getPositionY() + (172.0f/454.0f)*HelloWorld::mesa->getBoundingBox().size.height},

      {HelloWorld::mesa->getPositionX() – (349.0f/800.0f)*HelloWorld::mesa->getBoundingBox().size.width,

        HelloWorld::mesa->getPositionY() – (176.0f/454.0f)*HelloWorld::mesa->getBoundingBox().size.height},

      {HelloWorld::mesa->getPositionX(),

        HelloWorld::mesa->getPositionY() – (188.0f/454.0f)*HelloWorld::mesa->getBoundingBox().size.height},

      {HelloWorld::mesa->getPositionX() + (346.0f/800.0f)*HelloWorld::mesa->getBoundingBox().size.width,

        HelloWorld::mesa->getPositionY() – (173.0f/454.0f)*HelloWorld::mesa->getBoundingBox().size.height}

    };

    Vec2 cacapa;

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

        cacapa = Vec2(cacapas[i][0],cacapas[i][1]);

        if(!HelloWorld::bolaBrancaCorpo->getVelocity().isZero()&&

          cacapa.distanceSquared(HelloWorld::bolaBranca->getPosition())<raioCacapas) {

            HelloWorld::bolaBrancaCorpo->resetForces();

            HelloWorld::bolaBrancaCorpo->setVelocity(Vec2::ZERO);

            removeChild(HelloWorld::bolaBranca);

            unschedule(schedule_selector(HelloWorld::paraBolaBranca));

        }

        if(!HelloWorld::bolaVermelhaCorpo->getVelocity().isZero()&&

          cacapa.distanceSquared(HelloWorld::bolaVermelha->getPosition())<raioCacapas) {

            HelloWorld::bolaVermelhaCorpo->resetForces();

            HelloWorld::bolaVermelhaCorpo->setVelocity(Vec2::ZERO);

            removeChild(HelloWorld::bolaVermelha);

            unschedule(schedule_selector(HelloWorld::paraBolaVermelha));

        }

        if(!HelloWorld::bolaAmarelaCorpo->getVelocity().isZero()&&

          cacapa.distanceSquared(HelloWorld::bolaAmarela->getPosition())<raioCacapas) {

            HelloWorld::bolaAmarelaCorpo->resetForces();

            HelloWorld::bolaAmarelaCorpo->setVelocity(Vec2::ZERO);

            removeChild(HelloWorld::bolaAmarela);

            unschedule(schedule_selector(HelloWorld::paraBolaAmarela));

        }

    }

}

Na primeira parte do método “encacapar”, nós especificamos o raio das caçapas e um vetor de pontos na tela que correspondem aos seus centros. Na segunda parte, nós verificamos, para cada caçapa, se alguma das três bolas caiu nela. Caso isso aconteça, é cessada a movimentação da bola que caiu na caçapa e o sprite referente a mesma bola é retirado da tela. A ideia é simples, porém ainda pode haver uma dúvida no ar: como é detectado que uma bola caiu em uma caçapa? Essa detecção é realizada da seguinte forma: se o ponto central da caçapa está dentro do círculo envoltório do corpo rígido referente a bola de bilhar, então a bola caiu na caçapa. Nada muito complicado de entender. ;D

O principal método do sistema de caçapas foi implementado. Porém, ainda não foi programada a sua execução de forma consecutiva. Note que não é necessário que ele execute consecutivamente desde o início até o fim do jogo. Precisamos que ele seja executado apenas enquanto existe movimentação das bolas sobre a mesa. É com isso que nos preocuparemos agora.

 

Programando a execução do método “encacapar”

MovimentacaoPara finalizarmos o tutorial de hoje, basta fazermos com que o método “encacapar” seja executado consecutivamente enquanto as bolas se movimentam. Esse evento é iniciado quando o jogador realiza uma tacada e é finalizado quando as bolas param de se movimentar. Na verdade, não é errado fazer com que esse método seja executado consecutivamente do início ao fim do jogo. Porém, se programarmos a sua execução conforme explicado, nós faremos com que o jogo não tenha processamento desnecessário, visto que nenhuma bola parada pode ser encaçapada. Evitando processamento desnecessário, nós economizamos a bateria do aparelho. Tudo em prol da otimização. =]

Continuando, uma tacada é realizada quando o jogador retira o dedo da tela após mirar a tacada. Assim, adicione a seguinte linha de código:

schedule(schedule_selector(HelloWorld::encacapar));

logo ACIMA dessa:

HelloWorld::toqueNaTela->setEnabled(false);

Com isso, nós programamos a execução do método “encacapar” a cada quadro apresentado ao jogador. Agora precisamos nos preocupar com o fim dessas execuções consecutivas. Conforme implementamos no tutorial passado, quando qualquer bola para de se movimentar, é verificado se todas as outras também estão paradas. Caso isso aconteça, é habilitada a tela de toque para que o jogador possa realizar a próxima tacada. Nesse mesmo instante, nós podemos parar a execução consecutiva do método “encacapar”, pois todas as bolas já estão paradas. Dessa forma, no método já implementado “paraBolaBranca”, substitua a seguinte linha de código:

HelloWorld::bolaAmarelaCorpo->getVelocity().isZero())

por essa:

HelloWorld::bolaAmarelaCorpo->getVelocity().isZero()) {

e adicione as seguintes linhas de código:

    unschedule(schedule_selector(HelloWorld::encacapar));

}

logo abaixo dessa:

HelloWorld::toqueNaTela->setEnabled(true);

Você pode notar que nós apenas aumentamos o escopo da estrutura de seleção (“if”) e adicionamos uma linha de código que tem a função de parar a execução consecutiva do método “encacapar”. Repita esses passos para os métodos “paraBolaVermelha” e “paraBolaAmarela”.

nerds-trabalhandoProgramadores mais experientes notarão que existe também outra forma das bolas pararem. Também precisamos verificar se todas as bolas estão paradas quando uma bola for encaçapada. Isto porque todas as outras bolas podem estar em repouso quando uma bola que está em movimento cair em uma caçapa. Nesse momento, as execuções consecutivas do método “encacapar” devem ser paradas também. Assim, adicione as seguintes linhas de código no final do método “encacapar”.

if(HelloWorld::bolaBrancaCorpo->getVelocity().isZero()&&

  HelloWorld::bolaVermelhaCorpo->getVelocity().isZero()&&

  HelloWorld::bolaAmarelaCorpo->getVelocity().isZero()) {

    HelloWorld::toqueNaTela->setEnabled(true);

    unschedule(schedule_selector(HelloWorld::encacapar));

}

Ainda temos mais um problema a ser solucionado. Do jeito que está implementado atualmente, quando uma bola é encaçapada, ela é retirada da simulação física e da tela. Isso acontece com todas as bolas de bilhar, inclusive com a bola branca. A título de informação, a bola branca deve voltar à mesa caso seja encaçapada, senão o jogo não tem como continuar. Para fazermos isso, adicione as seguintes linhas de código:

if(!HelloWorld::bolaBranca->getParent()) {

    HelloWorld::bolaBranca->setPosition(HelloWorld::mesa->getPositionX()+

      0.25*HelloWorld::mesa->getBoundingBox().size.width,HelloWorld::mesa->getPositionY());

    addChild(HelloWorld::bolaBranca);

}

logo ACIMA de todas as linhas de código do arquivo “HelloWorldScene.cpp” que são assim:

HelloWorld::toqueNaTela->setEnabled(true);

Agora SIIIIM … Ufa … Finalizamos a implementação do tutorial de hoje. Salve os arquivos “HelloWorldScene.cpp” e “HelloWorldScene.h”, compile o código e execute o jogo. Você perceberá que todas as bolas podem ser encaçapadas em qualquer boca. Você também perceberá que, se a bola branca for encaçapada, ela retorna a mesa quando não houver mais bolas em movimento. Para os curiosos de plantão, segue um vídeo do jogo na sua atual versão.

Vimos no tutorial de hoje como funciona o sistema de caçapas. Implementamos esse sistema de tal forma que seja verificado se qualquer bola entrou em qualquer caçapa enquanto existe movimentação das bolas na mesa. Se isso acontecer, a bola é retirada da tela. Também fizemos com que a bola branca volte a mesa se ela for encaçapada.

Finalizaremos a implementação do nosso jogo de sinuca no próximo tutorial. Nele nós implementaremos o término do jogo e as HUDs.

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.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *