Tutorial: Desenvolvendo um Jogo de Batalha Naval em Cocos2d-x: Parte 4 – Pontuação e Término de Jogo

Hoje finalizaremos mais um conjunto de tutoriais. Desta vez, nós teremos um jogo de Batalha Naval em Cocos2d-x pronto.

Vamos programar suas últimas funcionalidades.

Parte 1Parte 2Parte 3 – Parte 4

Implementamos no último tutorial a reação aos toques na tela. Fizemos com que um tiro acertasse um navio ou caísse no mar. Assim, caso o jogador acerte um navio, ele é mostrado e as opções de tiro sob ele são excluídas. E caso o jogador erre um tiro, a opção de tiro escolhida é substituída por um sprite de “X”.

Hoje, nós finalizaremos a implementação do nosso jogo de Batalha Naval. Primeiramente, corrigiremos alguns erros de implementação cometidos no último tutorial (sim … eu sou humano e falho as vezes =] ). Depois nós implementaremos as funcionalidades de troca de turnos entre os jogadores, soma de pontuação e procedimento de término de jogo.

Começamos corrigindo alguns erros.

 

Correções e Preparações para Implementação

houston-we-have-a-problemÉ normal aparecer erros e eles precisam ser solucionados o quanto antes. Ao testar o jogo em outro aparelho, eu notei que, ao acertar um navio médio, em alguns casos, as opções de tiro sob o navio eram desativadas de forma errônea. Isso porque o ponto âncora do sprite dos navios médios (somente dos médios) está sobre uma linha divisória entre duas opções de tiro.

No código atual, ao identificar quais opções de tiros um navio médio está sobre, o jogo pode escolher uma OU outra. Caso o jogo escolha a opção errada, ele desativará uma opção de tiro errônea e manterá outra que está sob o navio abatido. Para solucionar esse problema, basta calcular a opção de tiro após uma pequena translação do ponto referência. Assim, abra o arquivo “HelloWorldScene.cpp” e substitua as seguintes linhas de código:

bpx = HelloWorld::naviosM[i]->getPositionX()/HelloWorld::grade[0][0]->getBoundingBox().size.width;

bpy = 6 – HelloWorld::naviosM[i]->getPositionY()/HelloWorld::grade[0][0]->getBoundingBox().size.height;

HelloWorld::grade[bpy][bpx]->setVisible(false);

if(HelloWorld::naviosM[i]->getRotation()==0.0||HelloWorld::naviosM[i]->getRotation()==180.0)

    HelloWorld::grade[bpy-1][bpx]->setVisible(false);

else

    HelloWorld::grade[bpy][bpx-1]->setVisible(false);

por essas:

if(HelloWorld::naviosM[i]->getRotation()==0.0||HelloWorld::naviosM[i]->getRotation()==180.0) {

    bpx = HelloWorld::naviosM[i]->getPositionX()/HelloWorld::grade[0][0]->getBoundingBox().size.width;

    bpy = 6 – (HelloWorld::naviosM[i]->getPositionY()-HelloWorld::grade[0][0]->getBoundingBox().size.height/2)

      /HelloWorld::grade[0][0]->getBoundingBox().size.height;

    HelloWorld::grade[bpy][bpx]->setVisible(false);

    HelloWorld::grade[bpy-1][bpx]->setVisible(false);

} else {

    bpx = (HelloWorld::naviosM[i]->getPositionX()+HelloWorld::grade[0][0]->getBoundingBox().size.width/2)

      /HelloWorld::grade[0][0]->getBoundingBox().size.width;

    bpy = 6 – HelloWorld::naviosM[i]->getPositionY()/HelloWorld::grade[0][0]->getBoundingBox().size.height;

    HelloWorld::grade[bpy][bpx]->setVisible(false);

    HelloWorld::grade[bpy][bpx-1]->setVisible(false);

}

 

bugsDesse jeito, arrumamos um bug. Agora vamos manter a referência do objeto gerenciador de toques na tela. Precisamos da sua referência pois, ao finalizar o jogo, não será permitido mais jogadas por meio de novos toques na tela.

Assim, precisamos criar um atributo na classe “HelloWorld”. Abra o arquivo “HelloWorldScene.h” e adicione a seguinte linha de código:

cocos2d::EventListenerTouchOneByOne* toqueNaTela;

logo abaixo dessa:

cocos2d::Sprite *naviosP[6];

Foi criado o atributo, mas a referência do objeto gerenciador de toques na tela ainda não foi armazenada.

Para isso, abra o arquivo “HelloWorldScene.cpp” e substitua a seguinte linha de código:

EventListenerTouchOneByOne* toqueNaTela = EventListenerTouchOneByOne::create();

por essa:

toqueNaTela = EventListenerTouchOneByOne::create();

Então, assim arrumamos os erros e preparamos o código para a implementação das funcionalidades propostas no tutorial de hoje.

 

HUD de Turno no Batalha Naval em Cocos2d-x

spider-manbackComeçaremos com a implementação da funcionalidade mais simples. Precisamos de um objeto gráfico que indique qual dos dois jogadores está com a vez de jogar. Esse objeto é modificado sempre que um dos jogadores erra. Desta forma, precisamos manter a referência desse objeto em um atributo da classe HelloWorld. Abra o arquivo “HelloWorldScene.h” e adicione a seguinte linha de código:

cocos2d::Label *vezJogador;

logo abaixo dessa:

cocos2d::EventListenerTouchOneByOne* toqueNaTela;

Com isso, podemos instanciar o objeto e mostrá-lo na tela. Abra o arquivo “HelloWorldScene.cpp” e adicione as seguintes linhas de código:

HelloWorld::vezJogador = Label::createWithTTF(“Vez do Jogador 1″,”fonts/Marker Felt.ttf”,40.0);

HelloWorld::vezJogador->setAnchorPoint(Vec2(0.5,0.0));

HelloWorld::vezJogador->setPosition(Vec2(visibleSize.width/2,0));

addChild(HelloWorld::vezJogador);

logo abaixo dessa:

HelloWorld::criaCenario();

Isso apenas mostra o objeto gráfico aos jogadores. Agora precisamos fazer com que ele informe qual jogador está com a vez de jogar. Faremos com que os turnos sejam intercalados sempre quando o jogador atual errar o tiro.

Para podermos alternar os jogadores, precisamos de uma variável que armazena um valor do tipo inteiro referente a qual jogador está na vez. Além disso, precisamos de uma variável que armazena o texto a ser mostrado aos jogadores. Crie essas variáveis adicionando as seguintes linhas de código:

static int vez = 1;

char txt[17];

logo abaixo dessa

int bpx,bpy;

Para mudar o texto que informa qual jogador está com a vez, adicione as seguintes linhas de código:

vez = vez==1?2:1;

sprintf(txt,”Vez do Jogador %i”,vez);

HelloWorld::vezJogador->setString(txt);

logo abaixo dessa:

HelloWorld::grade[bpy][bpx]->setSpriteFrame(“Erro.png”);

Com isso, fizemos com que o jogo intercale os turnos entre os jogadores sempre quando eles erram o tiro. O próximo passo é implementar o acréscimo de pontos quando eles acertam.

 

HUDs Informadoras de pontos

certeiroOs pontos de ambos os jogadores serão informados por meio de dois elementos gráficos. Esses elementos são modificados a cada tiro acertado por um jogador. Dessa forma, precisamos incluir mais dois atributos na classe HelloWorld. Abra o arquivo “HelloWorldScene.h” e adicione as seguintes linhas de código:

cocos2d::Label *pontosJogador1;

cocos2d::Label *pontosJogador2;

logo abaixo dessa:

cocos2d::Label *vezJogador;

Vamos instanciar esses objetos e mostrá-los na tela. Abra o arquivo “HelloWorldScene.cpp” e adicione as seguintes linhas de código:

HelloWorld::pontosJogador1 = Label::createWithTTF(“Jogador 1: 0″,”fonts/Marker Felt.ttf”,40.0);

HelloWorld::pontosJogador1->setAnchorPoint(Vec2(0.0,1.0));

HelloWorld::pontosJogador1->setPosition(Vec2(0.0,visibleSize.height));

addChild(HelloWorld::pontosJogador1);

HelloWorld::pontosJogador2 = Label::createWithTTF(“Jogador 2: 0″,”fonts/Marker Felt.ttf”,40.0);

HelloWorld::pontosJogador2->setAnchorPoint(Vec2(1.0,1.0));

HelloWorld::pontosJogador2->setPosition(Vec2(visibleSize.width,visibleSize.height));

addChild(HelloWorld::pontosJogador2);

logo abaixo dessa:

addChild(HelloWorld::vezJogador);

As HUDs estão sendo mostradas, porém não há nenhuma modificação quando um jogador acerta um alvo. Ainda precisamos incrementar a pontuação do jogador que acertar um dos navios. Os pontos de ambos os jogadores precisam ser armazenados em duas variáveis do tipo inteiro. Adicione as seguintes linhas de código:

static int pt1 = 0;

static int pt2 = 0;

logo abaixo dessa:

int bpx,bpy;

titanicNote que existem três tipos diferentes de navios. Cada tipo disponibiliza uma pontuação distinta se for abatido. Vamos incrementar a pontuação do jogador que está com a vez caso ele acerte um navio grande. Adicione as seguintes linhas de código:

if(vez==1) {

    pt1 += 3;

    sprintf(txt,”Jogador 1: %i”,pt1);

    HelloWorld::pontosJogador1->setString(txt);

} else {

    pt2 += 3;

    sprintf(txt,”Jogador 2: %i”,pt2);

    HelloWorld::pontosJogador2->setString(txt);

}

logo abaixo dessas:

    HelloWorld::grade[bpy][bpx+1]->setVisible(false);

}

Agora, vamos incrementar a pontuação do jogador que está com a vez caso ele acerte um navio médio. Adicione as seguintes linhas de código:

if(vez==1) {

    pt1 += 2;

    sprintf(txt,”Jogador 1: %i”,pt1);

    HelloWorld::pontosJogador1->setString(txt);

} else {

    pt2 += 2;

    sprintf(txt,”Jogador 2: %i”,pt2);

    HelloWorld::pontosJogador2->setString(txt);

}

logo abaixo dessas:

    HelloWorld::grade[bpy][bpx-1]->setVisible(false);

}

Por último, vamos incrementar a pontuação do jogador que está com a vez, caso ele acerte um navio pequeno. Adicione as seguintes linhas de código:

if(vez==1) {

    pt1++;

    sprintf(txt,”Jogador 1: %i”,pt1);

    HelloWorld::pontosJogador1->setString(txt);

} else {

    pt2++;

    sprintf(txt,”Jogador 2: %i”,pt2);

    HelloWorld::pontosJogador2->setString(txt);

}

logo abaixo dessas:

bpy = 6 – HelloWorld::naviosP[i]->getPositionY()/HelloWorld::grade[0][0]->getBoundingBox().size.height;

HelloWorld::grade[bpy][bpx]->setVisible(false);

Com isso, já temos as funcionalidades de gerenciamento de turnos e de pontuação devidamente implementadas. Só falta implementar o procedimento de finalização do jogo.

 

Término de Jogo e Aviso aos Jogadores

O jogo será finalizado quando todos os navios forem abatidos. Dessa forma, a cada navio abatido, será realizada uma checagem para verificar se todos já foram abatidos. Caso isso aconteça, o jogo é finalizado. Para implementar essa checagem, adicione as seguintes linhas de código no final da implementação do método “onTouchBegan”.

else {

    for(i=0;i<2;i++)

        if(!HelloWorld::naviosG[i]->isVisible()) {

            flag = 1;

            i = 2;

        }

    for(i=0;i<4;i++)

        if(!HelloWorld::naviosM[i]->isVisible()) {

            flag = 1;

            i = 4;

        }

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

        if(!HelloWorld::naviosP[i]->isVisible()) {

            flag = 1;

            i = 6;

        }

    if(flag==0) {

        HelloWorld::toqueNaTela->setEnabled(false);

        Label *fimDeJogo = Label::createWithTTF(“Fim de Jogo”,”fonts/Marker Felt.ttf”,60.0);

        fimDeJogo->setPosition(Vec2(Director::getInstance()->getVisibleSize().width/2,

        Director::getInstance()->getVisibleSize().height/2));

        addChild(fimDeJogo);

        HelloWorld::vezJogador->setVisible(false);

    }

}

Note que é verificado se todos os navios estão sendo apresentados na tela. Caso isso aconteça, então todos os navios já foram abatidos. Assim, é mostrada aos jogadores uma mensagem de término de jogo.

Figura 1 - Jogo executando
Figura 1 – Jogo executando

 

Agora sim … temos um jogo de Batalha Naval em Cocos2d-x devidamente implementado. Salve os arquivos, compile o código e execute o jogo.

Note que: a vez só é alterada caso um jogador erre o tiro, que as pontuações são incrementadas conforme o tamanho do navio abatido e que o jogo só é finalizado quando todos os navios já foram abatidos.

Perceba também que nada acontece caso um jogador clique sobre uma opção já escolhida anteriormente. Aos curiosos de plantão, segue um vídeo do jogo sendo executado. A figura abaixo mostra o jogo sendo executado:

Implementamos nesse tutorial as últimas funcionalidades do nosso jogo de Batalha Naval em Cocos2d-x. Primeiramente, arrumamos um bug implementado no tutorial passado. Depois nós implementamos os sistemas de turnos, pontuação e término do jogo.

No próximo tutorial nós iniciaremos a implementação de mais um jogo. Esperem para ver. ;D

Um grande abraço. []

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