full screen background image

Desenvolvendo um Jogo Digital do Zero: Parte 16 – Implementação das animações

Desenvolvendo um Jogo Digital do Zero: Parte 16 – Implementação das animações

Estamos prestes a finalizar a programação do Shark Pong.

Já implementamos os sistemas de interação, física, inteligência artificial, power up, término do jogo e HUDs. Bastante coisa, não é mesmo?!

Hoje nós implementaremos as animações projetadas pelo designer visual.

Todas as partes da saga:

http://fabricadejogos.net/colunas/producao-jogo-digital-do-zero

Vimos no último artigo como funciona para detectarmos o término do jogo. Fizemos a implementação e mostramos uma mensagem ao jogador que informa se ele ganhou ou perdeu. No artigo de hoje nós deixaremos o jogo bem apresentável graficamente. Até o momento, nós nos preocupamos apenas com funcionalidade e não com beleza. Bora implementar animações.

Antes de tudo, será necessário que você descompacte o arquivo de sprite sheet atualizado para que possamos dar continuidade. Baixe esse arquivo e o descompacte na pasta “Resources”. Você perceberá que os sprites ficarão bem mais definidos e isso possibilitará a implementação das animações propostas. Caso você não faça ideia do que são sprites nem sprite sheets, acesse esse tutorial. Então, vamos iniciar a implementação das animações.

 

Modificações iniciais necessárias

Para que todas as animações fluam corretamente durante o jogo sem que haja nenhum imprevisto, precisamos fazer algumas modificações no código atual. Tais modificações serão realizadas apenas no arquivo “HelloWorldScene.cpp”.

tubaraoIniciaremos com a modificação do frame inicial referente ao sprite do tubarão. Como vínhamos executando até então, o tubarão é mostrado logo no início do jogo no seu estado normal. Porém, como visto no GDD, existe uma animação inicial do tubarão, essa projetada pelo Filipe. Assim, precisaremos deixar o sprite do tubarão no frame correto. Para isso, modifique a seguinte linha de código:

HelloWorld::tubarao = CCSprite::createWithSpriteFrameName(“Tubarao1.png”);

de forma que ela fique assim:

HelloWorld::tubarao = CCSprite::createWithSpriteFrameName(“Tubarao2.png”);

As próximas modificações também serão realizadas no sprite do tubarão. Nós mudamos o ponto âncora do sprite do tubarão para que a ponta da sua barbatana seja a referência. Porém, essa modificação é realizada em um ponto muito adiante do código. Precisaremos realizá-la logo na criação do sprite do tubarão. Assim, adicione a seguinte linha de código:

HelloWorld::tubarao->setAnchorPoint(ccp(16.0/63.0,21.0/38.0));

logo abaixo dessa:

HelloWorld::tubarao->setScale((0.2*size.height)/HelloWorld::tubarao->boundingBox().size.height);

Na última modificação no sprite do tubarão, precisamos posicioná-lo em um lugar um pouco mais afrente da metade da tela. Tudo isso para que seja possível fazer a animação dele saindo do fundo do mar. Essa animação precisa acontecer com o tubarão percorrendo um pequeno espaço, para que ela flua naturalmente. Assim, para posicionar o sprite do tubarão no lugar correto, modifique a seguinte linha de código:

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

de forma que ela fique assim:

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

Pronto, terminamos as modificações iniciais necessárias no sprite do tubarão. Agora realizaremos uma modificação no sprite do polvo. Para que o jogo não inicie com o power up do polvo já aparecendo na tela, precisamos deixá-lo invisível no começo. Além disso, é desnecessário posicionar o sprite do polvo no início do jogo. Assim, modifique a seguinte linha de código:

HelloWorld::polvo->setPosition(ccp(0.8*size.width,0.8*size.height));

de forma que ela fique assim:

HelloWorld::polvo->setVisible(false);

PolvoDesse modo, não posicionamos o polvo no início do jogo e deixamos ele invisível para que possa aparecer no momento correto. Para finalizarmos as modificações iniciais necessárias, removeremos o código de inicialização das variáveis físicas do tubarão, a chamada do método responsável por iniciar a execução da inteligência artificial do NPC e mais algumas linhas de código de inicialização do jogo. Essa remoção se faz necessária porque o jogo se iniciará somente após a animação do tubarão chegando na superfície da água. Dessa forma, apague as seguintes linhas de código:

HelloWorld::idToque = -1;

setTouchEnabled(true);

HelloWorld::tubarao->setAnchorPoint(ccp(16.0/63.0,21.0/38.0));

HelloWorld::tempo = 0.0;

HelloWorld::velocidadeTubarao = 0.4*size.width;

HelloWorld::vetorDirecaoTubarao[0] = -1.0;

HelloWorld::vetorDirecaoTubarao[1] = 0.0;

HelloWorld::posicaoInicialTubarao[0] = HelloWorld::tubarao->getPositionX();

HelloWorld::posicaoInicialTubarao[1] = HelloWorld::tubarao->getPositionY();

schedule(schedule_selector(HelloWorld::atualizaQuadro));

schedule(schedule_selector(HelloWorld::aumentaVelocidadeTubarao),10);

HelloWorld::tomaDecisaoNPC();

HelloWorld::aparecePolvo();

 

Animações dos surfistas

Agora iniciaremos as implementações referentes às animações. Como de praxe, há a necessidade de modificação de alguns trechos de código para que as animações referentes aos sprites dos surfistas ocorram da forma desejada. Mas quais são essas modificações e para que elas servem?

SurfistaPrimeiramente explicarei o contexto. Durante toda a execução do jogo, ocorrerá uma animação constante dos surfistas que dá um aspecto de eles realmente estarem deslizando sobre a superfície da água. Essa animação não pode parar em momento algum, pelo menos até um dos surfistas perder. Porém, em alguns trechos de código, nós limpamos TODAS as animações que acontecem nos sprites dos surfistas. Caso você não lembre, nós fazemos isso nos momentos que o surfista manipulado pelo jogador para de andar para cima ou para baixo e no momento que o surfista controlado pelo NPC fica parado por causa do polvo.

Como arrumaremos esse problema? Primeiramente, modificaremos os trechos de código onde iniciamos as animações de movimentação dos sprites dos sufistas. Essas animações de movimentação serão identificadas com um número, no nosso caso será o valor 1, e nos momentos que precisarmos parar a movimentação do sprite nós paramos apenas a animação identificada. Assim, precisaremos realizar dois tipos de modificações: as que identificam as animações de movimentação e as que param essa animação baseada no número identificador.

Vamos começar adicionando os números identificadores nas animações de movimentação de ambos os surfistas. Precisaremos modificar apenas três trechos de código. Para modificar o primeiro, substitua a seguinte linha de código do arquivo “HelloWorldScene.cpp”:

HelloWorld::paJogador->runAction(CCMoveTo::create(tempo,ccp(HelloWorld::paJogador->getPositionX(),0.3*size.height)));

por essas três:

CCMoveTo* ac = CCMoveTo::create(tempo,ccp(HelloWorld::paJogador->getPositionX(),0.3*size.height));

ac->setTag(1);

HelloWorld::paJogador->runAction(ac);

Para modificar o segundo trecho, substitua a seguinte linha de código:

HelloWorld::paJogador->runAction(CCMoveTo::create(tempo,ccp(HelloWorld::paJogador->getPositionX(),0.9*size.height)));

por essas três:

CCMoveTo* ac = CCMoveTo::create(tempo,ccp(HelloWorld::paJogador->getPositionX(),0.9*size.height));

ac->setTag(1);

HelloWorld::paJogador->runAction(ac);

Por último, para modificar o terceiro trecho, adicione a seguinte linha de código:

seq->setTag(1);

logo ACIMA dessa:

HelloWorld::paIA->runAction(seq);

Agora realizaremos as modificações que param as animações de movimentação dos surfistas. Essas modificações são mais numerosas, porém, mais diretas. Apenas substitua todas as linhas de código que são assim:

HelloWorld::paJogador->cleanup();

por essa:

HelloWorld::paJogador->stopActionByTag(1);

e substitua essa linha de código:

HelloWorld::paIA->cleanup();

por essa:

HelloWorld::paIA->stopActionByTag(1);

Todas essas modificações foram feitas para que a animação dos surfistas deslizando sobre água fluíssem do início ao fim do jogo. Ufa … Agora basta iniciar essas animações logo no início do aplicativo. Para isso, adicione as seguintes linhas de código:

CCArray* surfJog = CCArray::create();

surfJog->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaJogador2.png”));

surfJog->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaJogador1.png”));

surfJog->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaJogador2.png”));

surfJog->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaJogador3.png”));

HelloWorld::paJogador->runAction(CCRepeatForever::create(

  CCAnimate::create(CCAnimation::createWithSpriteFrames(surfJog,0.15))));

CCArray* surfIA = CCArray::create();

surfIA->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaIA2.png”));

surfIA->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaIA1.png”));

surfIA->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaIA2.png”));

surfIA->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaIA3.png”));

HelloWorld::paIA->runAction(CCRepeatForever::create(

  CCAnimate::create(CCAnimation::createWithSpriteFrames(surfIA,0.15))));

logo abaixo dessa:

addChild(HelloWorld::botaoSubir);

 

Animação inicial do tubarão

tubarao-aparecendoVocê lembra que no início do artigo nós apagamos um trecho de código que inicializava as variáveis físicas do tubarão e começava o jogo? Então. Se você perceber, até esse momento, nós ainda não iniciamos o jogo. Na verdade, o início do jogo se dá somente após o término da animação de entrada do tubarão no cenário. É exatamente isso que faremos agora: implementar o método que inicia o jogo, animar a entrada do tubarão no cenário e chamar o método implementado.

Inicialmente, como implementaremos um novo método, precisamos adicionar o seu protótipo na classe “HelloWorld”. Para isso, abra o arquivo “HelloWorldScene.h” e adicione a seguinte linha de código:

void iniciaJogo();

logo abaixo dessa:

float posicaoInicialTubarao[2];

Obviamente, o protótipo não é o suficiente para dar início ao jogo. Precisamos implementar o método “iniciaJogo”. Adicione as seguintes linhas de código no final do arquivo “HelloWorldScene.cpp”:

void HelloWorld::iniciaJogo() {

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

    HelloWorld::tempo = 0.0;

    HelloWorld::velocidadeTubarao = 0.4*size.width;

    HelloWorld::vetorDirecaoTubarao[0] = -1.0;

    HelloWorld::vetorDirecaoTubarao[1] = 0.0;

    HelloWorld::posicaoInicialTubarao[0] = HelloWorld::tubarao->getPositionX();

    HelloWorld::posicaoInicialTubarao[1] = HelloWorld::tubarao->getPositionY();

    HelloWorld::idToque = -1;

    setTouchEnabled(true);

    schedule(schedule_selector(HelloWorld::atualizaQuadro));

    schedule(schedule_selector(HelloWorld::aumentaVelocidadeTubarao),10);

    schedule(schedule_selector(HelloWorld::aparecePolvo),10);

    HelloWorld::tomaDecisaoNPC();

}

Se você perceber, apenas inicializamos as variáveis físicas do tubarão, a variável de gerenciamento de toques na tela, acionamos a tela de toque e iniciamos a execução dos métodos que dão vida ao jogo. Agora só falta iniciar a animação de entrada do tubarão e realizar a chamada do método “iniciaJogo” após o término dessa animação. Para isso, adicione as seguintes linhas de código:

CCArray* tub = CCArray::create();

tub->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“Tubarao2.png”));

tub->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“Tubarao3.png”));

tub->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“Tubarao4.png”));

tub->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“Tubarao5.png”));

tub->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“Tubarao1.png”));

HelloWorld::tubarao->runAction(CCAnimate::create(CCAnimation::createWithSpriteFrames(tub,0.15)));

HelloWorld::tubarao->runAction(CCMoveTo::create(0.75,ccp(size.width/2,size.height/2)));

CCSequence* seq = CCSequence::create(CCDelayTime::create(0.75),

  CCCallFunc::create(this,callfunc_selector(HelloWorld::iniciaJogo)),NULL);

runAction(seq);

logo abaixo dessa:

HelloWorld::paIA->runAction(CCRepeatForever::create(

  CCAnimate::create(CCAnimation::createWithSpriteFrames(surfIA,0.15))));

Se você notar, nós demos início a animação do sprite do tubarão fazendo o mesmo andar até o centro da tela e fizemos com que fosse iniciada a animação de entrada do tubarão no cenário. Ao final, fizemos o aplicativo esperar 0,75 segundos para iniciar o jogo por meio de uma chamada ao método “iniciaJogo”. Esse tempo é exatamente o necessário para acontecer a animação de entrada do tubarão no cenário.

 

Animações de término do jogo digital

Tubarao ComendoQuando o tubarão sai da arena, ele devorará o surfista que deixar ele sair. Essa animação é complexa e não deve ser feita de forma brusca. Para que o final do jogo aconteça com naturalidade, é necessário fazer com que, primeiramente, o tubarão volte para o fundo do mar e, somente após isso, ele apareça por debaixo do surfista perdedor e o devore. Assim, temos, na verdade, uma sequência de duas animações: a do tubarão desaparecendo do cenário e a do tubarão devorando o surfista perdedor.

Por ser uma sequência de animações distintas, serão necessários a implementação de dois métodos: um que é executado para finalizar uma animação e iniciar a outra e outro método que é executado para finalizar a última animação. Seus nomes são, respectivamente, “tubaraoComendo” e “terminoTubaraoComendo”. Assim, iniciaremos a implementação dessas animações adicionando os protótipos desses métodos na classe “HelloWorld”. Abra o arquivo “HelloWorldScene.h” e adicione as seguintes linhas de código:

void tubaraoComendo();

void terminoTubaraoComendo();

logo abaixo dessa:

float posicaoInicialTubarao[2];

Agora implementaremos o início da primeira animação. Abra o arquivo “HelloWorldScene.cpp” e substitua a seguinte linha de código:

cleanup();

por essas três:

HelloWorld::paJogador->stopActionByTag(1);

HelloWorld::paIA->stopActionByTag(1);

unscheduleAllSelectors();

Como vimos anteriormente, a chamada do método “cleanup” não é bom para o nosso jogo. Ele fará com que todas as animações parem de acontecer, inclusive as dos surfistas deslizando sobre o mar. Para evitar que essas animações parem, nós apenas paramos as movimentações dos surfistas e paramos de executar os métodos que dão vida ao jogo. Se você perceber, essas linhas de código foram introduzidas no método “acabaJogo”, que é responsável por finalizar o jogo quando o tubarão sai do cenário. Ainda nesse mesmo método, logo ACIMA dessa linha de código:

if(jogador) {

adicione as seguintes linhas de código:

CCArray* tub = CCArray::create();

tub->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“Tubarao5.png”));

tub->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“Tubarao4.png”));

tub->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“Tubarao3.png”));

tub->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“Tubarao2.png”));

HelloWorld::tubarao->runAction(CCAnimate::create(CCAnimation::createWithSpriteFrames(tub,0.15)));

float posFinal[2];

posFinal[0] = HelloWorld::tubarao->getPositionX() + HelloWorld::velocidadeTubarao*HelloWorld::vetorDirecaoTubarao[0]*0.6;

posFinal[1] = HelloWorld::tubarao->getPositionY() + HelloWorld::velocidadeTubarao*HelloWorld::vetorDirecaoTubarao[1]*0.6;

HelloWorld::tubarao->runAction(CCMoveTo::create(0.6,ccp(posFinal[0],posFinal[1])));

CCSequence* seq = CCSequence::create(CCDelayTime::create(0.6),

  CCCallFunc::create(this,callfunc_selector(HelloWorld::tubaraoComendo)),NULL);

runAction(seq);

Note que, primeiro iniciamos uma animação no sprite do tubarão de forma que ela fosse inversa àquela de início de jogo. Então, ao invés do tubarão aparecer na superfície do mar, ele sai da superfície em direção ao fundo do mar. Não obstante, nós calculamos a posição onde o tubarão estaria após o término dessa animação (0,6 segundos) e o fizemos seguir até lá. Por fim, é programada a execução do método “tubaraoComendo” para após decorrer esse tempo. Para finalizar, implementaremos os métodos “tubaraoComendo” e “terminoTubaraoComendo”. Adicione as seguintes linhas de código no final do arquivo:

void HelloWorld::tubaraoComendo() {

    HelloWorld::tubarao->setVisible(false);

    if(HelloWorld::tubarao->getPositionX()>HelloWorld::paIA->getPositionX()) {

        CCArray* surfIA = CCArray::create();

        surfIA->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaIA2.png”));

        surfIA->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaIAPerdeu1.png”));

        surfIA->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaIAPerdeu2.png”));

        surfIA->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaIAPerdeu3.png”));

        surfIA->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaIAPerdeu4.png”));

        surfIA->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaIAPerdeu5.png”));

        HelloWorld::paIA->cleanup();

        HelloWorld::paIA->runAction(CCAnimate::create(CCAnimation::createWithSpriteFrames(surfIA,0.15)));

    } else {

        CCArray* surfJog = CCArray::create();

        surfJog->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaJogador2.png”));

        surfJog->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaJogadorPerdeu1.png”));

        surfJog->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaJogadorPerdeu2.png”));

        surfJog->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaJogadorPerdeu3.png”));

        surfJog->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaJogadorPerdeu4.png”));

        surfJog->addObject(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(“SurfistaJogadorPerdeu5.png”));

        HelloWorld::paJogador->cleanup();

        HelloWorld::paJogador->runAction(CCAnimate::create(CCAnimation::createWithSpriteFrames(surfJog,0.15)));

    }

    CCSequence* seq =     CCSequence::create(CCDelayTime::create(0.9),

  CCCallFunc::create(this,callfunc_selector(HelloWorld::terminoTubaraoComendo)),NULL);

    runAction(seq);

}

void HelloWorld::terminoTubaraoComendo() {

    if(HelloWorld::tubarao->getPositionX()>HelloWorld::paIA->getPositionX())

        HelloWorld::paIA->setVisible(false);

    else

        HelloWorld::paJogador->setVisible(false);

}

No método “tubaraoComendo”, nós deixamos o sprite do tubarão invisível, porque ele já está submerso, e iniciamos a animação dele devorando o surfista perdedor. Note que, no final de sua execução, nós programamos a execução do método “terminoTubaraoComendo” para ser executado após decorrer 0.9 segundos (a duração dessa animação). Ao final, é executado o método “terminoTubaraoComendo”, que apenas deixa invisível o sprite que animou o tubarão devorando. Isso porque o tubarão volta para o fundo do mar após “fazer o serviço”. Pronto, terminamos de programar as animações do SharkPong. O resultado será algo como o que é mostrado no vídeo a seguir.

 

Aqui finalizamos a etapa de programação da saga Desenvolvendo um Jogo Digital do Zero. Porém, a saga continua com a parte sobre a produção de trilha e efeitos sonoros do jogo. Para isso, contamos com Victor, nosso amigo e membro da Quasar Sound Work.

Muito obrigado por acompanharem essa saga e nos vemos em outros artigos. Agora eu passo a palavra para você, Victor.

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). Atualmente é programador da Céu Games (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.


Show Buttons
Hide Buttons