Tutorial: Criando um jogo ao estilo Flappy Bird – Parte 5: Pontuação e elementos de HUD

Estamos na etapa final de mais um conjunto de tutoriais.

Até agora, fizemos o pato voar e criamos os postes em alturas aleatórias.

Temos uma versão jogável que já tem desafio, mas ainda não tem fim.

Bora finalizar mais um game para o nosso portfólio.

Vimos no último tutorial como fazer com que os postes tenham as suas fendas em alturas aleatórias. Para isso, a cada novo par de postes adicionado no ambiente físico, foi sorteado um valor aleatório entre 30% e 70% do tamanho da tela, para que os postes superior e inferior sejam criados com base nisso. Também fizemos com que o Sprite do pato rotacionasse de forma que ele olhasse para a direção que estivesse indo no momento. Para isso, nós normalizamos o vetor velocidade e encontramos a sua angulação por meio da função arco seno, que recebe como parâmetro o valor de coordenada do eixo Y do vetor normalizado.

Nesse tutorial nós finalizaremos mais um conjunto de tutoriais que resultam em um jogo. Mais especificamente, incluiremos o sistema de pontuação do nosso jogo ao estilo Flappy Bird (créditos a .GEARS) e adicionaremos a detecção de colisão entre o pato e qualquer coisa para finalizar o jogo quando isso acontecer. Obviamente, também adicionaremos elementos de HUD, que mostram por quantos postes o jogador já passou e também o fim do jogo, quando o pato encosta em algo. Partiu. o/

 

Sistema de pontuação

Antes de iniciarmos a programação, vale lembrar que você precisa finalizar o tutorial passado, para que possamos continuar com o desenvolvimento do jogo. Caso você está em dia com isso, abra o arquivo “HelloWorldScene.h” e adicione as seguintes linhas de código:

int pontuacao;

int indice;

cocos2d::CCLabelTTF* etiquetaPontuacao;

logo abaixo dessa:

cocos2d::CCArray* postesInferiores;

Note que criamos três variáveis importantes para que o nosso sistema de contagem de postes funcione corretamente. Criamos uma variável nomeada “pontuacao”, que armazenará a quantidade de postes já ultrapassados; uma variável nomeada “indice”, que armazenará o índice do Sprite do próximo poste a ser ultrapassado na lista de Sprites que criamos no último tutorial; e uma variável que referencia a etiqueta que mostra a pontuação do jogador na tela. Precisamos da variável que armazena o índice do Sprite do próximo poste a ser ultrapassado, para que saibamos quais são os postes mostrados na tela que o jogador já passou e quais são os que o jogador ainda falta passar.

Agora abra o arquivo “HelloWorldScene.cpp” e adicione as seguintes linhas de código:

HelloWorld::pontuacao = 0;

HelloWorld::indice = 0;

HelloWorld::etiquetaPontuacao = CCLabelTTF::create(“0”,“”,50);

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

addChild(HelloWorld::etiquetaPontuacao,1);

logo abaixo dessa:

HelloWorld::corpoPato->CreateFixture(&cascaPato);

É possível ver que, nas duas primeiras linhas do código recém-adicionado, nós inicializamos as duas variáveis inteiras que criamos, aquela que armazena a pontuação do jogador e a que armazena o índice do próximo poste a ser passado. Nas próximas linhas, nós criamos e adicionamos na tela uma etiqueta que apresenta a quantidade de postes já ultrapassados e a posicionamos na parte superior central da tela. Obviamente, o jogador inicia com a pontuação zerada.

Agora adicione as seguintes linhas de código:

if(HelloWorld::postesSuperiores->count()>0) {

    CCSprite* poste = static_cast<CCSprite*>(HelloWorld::postesSuperiores->objectAtIndex(HelloWorld::indice));

    if(HelloWorld::pato->getPositionX()>poste->getPositionX()+

      HelloWorld::pato->getContentSize().width/2+poste->getContentSize().width/2) {

        char label[5];

        HelloWorld::pontuacao++;

        sprintf(label,“%i”,HelloWorld::pontuacao);

        HelloWorld::etiquetaPontuacao->setString(label);

        HelloWorld::indice = HelloWorld::indice + 2;

    }

}

logo abaixo dessas:

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

    spritesPoste = static_cast<CCSprite*>(HelloWorld::postesInferiores->objectAtIndex(i));

    corposRigidosPoste = static_cast<b2Body*>(spritesPoste->getUserData());

    spritesPoste->setPosition(ccp(corposRigidosPoste->GetWorldCenter().x*PTM_RATIO,corposRigidosPoste->GetWorldCenter().y*PTM_RATIO));

}

Nesse bloco de código adicionado, nós verificamos se o pato já passou do próximo poste que ele precisa ultrapassar. Em caso afirmativo, a variável “pontuacao” é incrementada, o valor da pontuação é atualizado na tela e a variável que armazena o índice do próximo poste também é incrementada em 2. Você pode achar um pouco confuso o motivo de eu ter incrementado em 2 o valor dessa última variável. Isso porque nós tomamos como base os Sprites utilizados para desenhar os postes superiores. Para se criar um poste superior, nós criamos dois Sprites, um para o corpo e um para a lâmpada e, logo após a criação, os adicionamos na lista de Sprites dos postes superiores. Dessa forma, quando passamos dois postes, na verdade, incrementamos em dois o índice dos próximos postes com base na lista de Sprites que formam os postes superiores. É possível notar que essa validação é realizada a cada novo quadro mostrado na tela.

Para finalizar a implementação dessa funcionalidade, precisamos fazer com que o índice dos próximos postes seja atualizado quando excluímos dois postes que saem da tela. Quando isso acontece, precisamos decrementar em 2 o valor da variável “indice”. Dessa forma, adicione a seguinte linha de código:

HelloWorld::indice = HelloWorld::indice – 2;

logo abaixo dessas:

removeChild(spritesPoste);

HelloWorld::postesSuperiores->removeObjectAtIndex(0);

spritesPoste = static_cast<CCSprite*>(HelloWorld::postesSuperiores->objectAtIndex(0));

removeChild(spritesPoste);

HelloWorld::postesSuperiores->removeObjectAtIndex(0);

HelloWorld::mundoFisico->DestroyBody(corposRigidosPoste);

spritesPoste = static_cast<CCSprite*>(HelloWorld::postesInferiores->objectAtIndex(0));

corposRigidosPoste = static_cast<b2Body*>(spritesPoste->getUserData());

removeChild(spritesPoste);

HelloWorld::postesInferiores->removeObjectAtIndex(0);

spritesPoste = static_cast<CCSprite*>(HelloWorld::postesInferiores->objectAtIndex(0));

removeChild(spritesPoste);

HelloWorld::postesInferiores->removeObjectAtIndex(0);

spritesPoste = static_cast<CCSprite*>(HelloWorld::postesInferiores->objectAtIndex(0));

removeChild(spritesPoste);

HelloWorld::postesInferiores->removeObjectAtIndex(0);

HelloWorld::mundoFisico->DestroyBody(corposRigidosPoste);

Pronto, a funcionalidade de contagem de postes ultrapassados pelo pato já está implementada. Daremos continuidade com a implementação de detecção de colisão e o término do jogo.

 

Identificação de colisões

O jogo é finalizado quando o pato colide com algo. Para detectar uma colisão, podemos utilizar uma classe do Box2D que possui essa finalidade. Para podermos implementar um método que é chamado quando existe colisão no mundo físico, precisamos criar uma classe que herda as características da classe do Box2D nomeada “b2ContactListener”. Essa classe implementa quatro métodos que são chamados em diferentes momentos durante um contato entre dois corpos rígidos. Precisamos apenas detectar o momento que acontece quando se inicia um contato. O método da classe “b2ContactListener”, que é chamado no momento de início de um contato é o “BeginContact”. Assim sendo, precisamos criar uma classe que herda “b2ContactListener” e implementar o método “BeginContact”.

Para iniciarmos essa implementação, abra o arquivo “HelloWorldScene.h” e adicione as seguintes linhas de código:

class Contato : public b2ContactListener {

private:

    HelloWorld* classeRaiz;

public:

    void referenciaHelloWorld(HelloWorld *hw);

    void BeginContact(b2Contact *contact);

};

logo abaixo dessa:

cocos2d::CCLabelTTF* etiquetaPontuacao;

Nesse bloco de código adicionado, nós criamos uma subclasse de HelloWorld denominada “Contato”. Essa subclasse herda os atributos e métodos da classe “b2ContactListener”. Dessa forma, agora podemos implementar o método “BeginContact” com todas as modificações necessárias para finalizar o game. Quando houver um contato no mundo físico, o método “BeginContact”, da classe “Contato”, será chamado.

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

HelloWorld::Contato* detectorDeContato = new HelloWorld::Contato();

detectorDeContato->referenciaHelloWorld(this);

HelloWorld::mundoFisico->SetContactListener(detectorDeContato);

logo abaixo dessa:

HelloWorld::postesInferiores->retain();

Nessas três linhas nós criamos um objeto da classe “Contato” e o referenciamos como sendo o objeto gerenciador de eventos de contato. Dessa forma, quando houver um contato entre dois objetos rígidos no mundo físico, os métodos desse objeto serão chamados. Atentem à segunda linha implementada, que realizamos a chamada do método “referenciaHelloWorld”. Nela, nós passamos como parâmetro a referência do nosso objeto HelloWorld. Isso é necessário para que possamos realizar modificações no ambiente gráfico do Cocos2d-x. Precisamos dessa referência porque faremos o jogo parar no momento do contato e adicionaremos uma etiqueta de fim de jogo.

Para finalizar, adicione as seguintes linhas de código no final do arquivo:

void HelloWorld::Contato::BeginContact(b2Contact *contact) {

    if(contact->GetFixtureA()->GetBody()==HelloWorld::Contato::classeRaiz->corpoPato||

      contact->GetFixtureB()->GetBody()==HelloWorld::Contato::classeRaiz->corpoPato) {

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

        HelloWorld::Contato::classeRaiz->cleanup();

        HelloWorld::Contato::classeRaiz->setTouchEnabled(false);

        CCLabelTTF* fim = CCLabelTTF::create(“Fim de jogo”,“”,80);

        fim->setPosition(ccp(size.width/2,size.height/2));

        HelloWorld::Contato::classeRaiz->addChild(fim,1);

    }

}

void HelloWorld::Contato::referenciaHelloWorld(HelloWorld *hw) {

    HelloWorld::Contato::classeRaiz = hw;

}

Note que implementamos dois métodos da classe “Contato”. No método “referenciaHelloWorld”, nós apenas armazenamos a referência da classe HelloWorld para que possamos utilizá-la no momento que houver um contato. Quando isso acontecer, o método “BeginContact”, implementado nesse último bloco de código adicionado, será chamado. Nesse método, nós primeiramente identificamos se a colisão ocorreu com o objeto rígido que representa o pato. Caso isso ocorra, nós paramos de processar os quadros, deixando o jogo parado, desabilitamos a tela de toque, para evitar que o pássaro anime o bater de asas, e incluímos no centro da tela uma etiqueta grande que avisa o fim do game.

Se você salvar as modificações realizadas nos arquivos “HelloWorldScene.h” e “HelloWorldScene.cpp”, compilar o código e executar o game, você verá tudo fucionando. Wee o/. Terminamos, assim, mais um conjunto de tutoriais que resultam em um game. Para os curiosos de plantão, segue o vídeo de como ficou o jogo na sua versão final.

Vimos nesse tutorial como funciona o sistema de pontuação do jogo que implementamos. Precisamos criar duas variáveis inteiras: uma que armazena a pontuação e outra que armazena o índice do Sprite dos próximos postes. Incrementamos a pontuação quando o pato ultrapassa um poste e atualizamos a pontuação na tela. Atualizamos a variável de índice sempre quando um poste é ultrapassado e sempre quando dois postes saem do campo de visão da tela. No próximo tutorial veremos como criar um outro game clássico, mas isso é para o ano que vem.

Bem pessoal, esse é último tutorial que escreverei neste ano. Dessa forma, eu desejo a vocês um excelente Natal e um ótimo ano novo, cheio de realizações e aprendizado. Um grande abraço e nos vemos no próximo ano. []

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