Tutorial: Criando um jogo ao estilo Angry Birds – Parte 4: Atirando as balas de canhão

Já criamos uma fase e implementamos a física dos objetos rígidos existentes nela.

Porém, o jogo está totalmente estático e não é possível qualquer tipo de interação ainda.

Vamos implementar o tiro do canhão e dar mais dinâmica ao nosso game.

Adicionamos no último tutorial a simulação física dos objetos que incluímos na fase criada no Tiled. Para isso, tivemos que criar um corpo rígido para cada objeto incluso na fase. Com isso, apenas reposicionamos e rotacionamos cada Sprite conforme o seu objeto rígido localizado no mundo do Box2D. Nesse tutorial, nós incluiremos a primeira e principal funcionalidade do nosso jogo Catapult Shooting: o lançamento das balas de canhão.

Primeiramente, vou avisar que você precisa implementar o tutorial anterior para dar sequência nesse. Caso você já o fez, então vamos iniciar esse tutorial abrindo o arquivo “HelloWorldScene.h”. Inclua as seguintes linhas de código:

cocos2d::CCSprite* seta;

cocos2d::CCArray* balasCanhaoSprites;

void ccTouchesBegan(cocos2d::CCSet *pTouches,cocos2d::CCEvent *pEvent);

void ccTouchesMoved(cocos2d::CCSet *pTouches,cocos2d::CCEvent *pEvent);

void ccTouchesEnded(cocos2d::CCSet *pTouches,cocos2d::CCEvent *pEvent);

logo abaixo dessa:

b2Body* objetoRigidoVaso;

Note que adicionamos um novo Sprite que representa a seta responsável por mostrar a direção e a força com que a bala de canhão será arremessada. Criamos também uma lista de Sprites que armazenará os Sprites de todas as balas de canhão já arremessadas durante o jogo. Incluímos também os protótipos das funções que são chamadas quando o jogador aperta o dedo na tela. Como você sabe, teremos que adicionar a interação com o jogo por meio do toque na tela, para que o jogador possa mirar o tiro do canhão.

Salve e feche o arquivo “HelloWorldScene.h”, já fizemos todas as modificações necessárias nele. Abra o arquivo “HelloWorldScene.cpp” e adicione as seguintes linhas de código:

HelloWorld::seta = CCSprite::createWithSpriteFrameName(“Seta.png”);

if(descricao->valueForKey(“orientacao”)->intValue()==0) {

    HelloWorld::seta->setFlipX(true);

    HelloWorld::seta->setAnchorPoint(ccp(1,0.5));

}

else

    HelloWorld::seta->setAnchorPoint(ccp(0,0.5));

HelloWorld::seta->setPosition(HelloWorld::canhao->getPosition());

HelloWorld::seta->setVisible(false);

addChild(HelloWorld::seta);

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

HelloWorld::balasCanhaoSprites->retain();

setTouchEnabled(true);

logo abaixo dessa:

canhao->CreateFixture(&cascaCanhao);

Nessas linhas de código, nós criamos o Sprite da seta que mostra a direção e a força com que a bala será atirada. Note que, dependendo se o canhão está apontando para a direita ou para a esquerda, eu faço a seta também apontar para a esquerda ou para a direita. Isto porque, quando atualizarmos a rotação do canhão enquanto o jogador estiver apontando, será utilizado como base um mesmo valor de rotação para ambos os Sprites. Então, se o canhão sofreu um flip (está apontando para a esquerda), então a seta também precisa sofrer o flip. Adicionamos o Sprite na tela, mas o deixamos invisível, para que a seta apareça somente quando o jogador estiver apontando o tiro. Ao final, nós criamos a lista de balas de canhão e acionamos a tela de toque no aparelho.

Como adicionamos a lista de Sprites das balas de canhão já atiradas, vamos atualizar, a cada novo quadro apresentado, as posições e rotações dessas balas. Então, adicione as seguintes linhas de código:

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

    spr = static_cast<CCSprite*>(HelloWorld::balasCanhaoSprites->objectAtIndex(i));

    corpo = static_cast<b2Body*>(spr->getUserData());

    spr->setPosition(ccp(corpo->GetPosition().x*PTM_RATIO,corpo->GetPosition().y*PTM_RATIO));

    spr->setRotation(-(corpo->GetAngle()*180)/M_PI);

}

logo abaixo dessa:

HelloWorld::vaso->setRotation(-(HelloWorld::objetoRigidoVaso->GetAngle()*180)/M_PI);

Note que incluímos esse código dentro do método “atualizaMundo”. Dessa forma, as posições e rotações dos Sprites das balas de canhão já atiradas também serão atualizadas a cada novo quadro. Notem que eu usei o método “getUserData”, esse existente na biblioteca Cocos2d-x. Você pode atribuir uma informação qualquer a um Sprite. Sendo isso possível, eu atribuí a cada Sprite de bala de canhão o objeto rígido que o representa no mundo físico do Box2D. Então, quando eu pego os objetos da lista de Sprites de balas de canhão, eu também já pego o objeto rígido do Box2D por meio do método “getUserData”. Então, eu só atualizo o Sprite conforme as posições e rotações dos objetos rígidos no mundo físico. Para que isso dê certo, obviamente, é necessário incluir no user data um corpo rígido no momento que um Sprite de bala de canhão é criado.

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

void HelloWorld::ccTouchesBegan(CCSet *pTouches,CCEvent *pEvent) {

    HelloWorld::seta->setVisible(true);

    CCPoint dedo = static_cast<CCTouch*>(pTouches->anyObject())->getLocationInView();

    dedo.y = CCDirector::sharedDirector()->getWinSize().height – dedo.y;

    float angulo = atan((dedo.y-HelloWorld::canhao->getPositionY())/(dedo.x-HelloWorld::canhao->getPositionX()));

    if(dedo.x<HelloWorld::canhao->getPositionX())

        angulo += M_PI;

    if(HelloWorld::canhao->isFlipX())

        angulo += M_PI;

    HelloWorld::canhao->setRotation(-(180*angulo)/M_PI);

    HelloWorld::seta->setRotation(-(180*angulo)/M_PI);

    float tamanho = sqrt((dedo.x-HelloWorld::canhao->getPositionX())*(dedo.x-HelloWorld::canhao->getPositionX()) +

        (dedo.y-HelloWorld::canhao->getPositionY())*(dedo.y-HelloWorld::canhao->getPositionY()));

    HelloWorld::seta->setScaleX(tamanho/HelloWorld::seta->getContentSize().width);

}

void HelloWorld::ccTouchesMoved(CCSet *pTouches,CCEvent *pEvent) {

    CCPoint dedo = static_cast<CCTouch*>(pTouches->anyObject())->getLocationInView();

    dedo.y = CCDirector::sharedDirector()->getWinSize().height – dedo.y;

    float angulo = atan((dedo.y-HelloWorld::canhao->getPositionY())/(dedo.x-HelloWorld::canhao->getPositionX()));

    if(dedo.x<HelloWorld::canhao->getPositionX())

        angulo += M_PI;

    if(HelloWorld::canhao->isFlipX())

        angulo += M_PI;

    HelloWorld::canhao->setRotation(-(180*angulo)/M_PI);

    HelloWorld::seta->setRotation(-(180*angulo)/M_PI);

    float tamanho = sqrt((dedo.x-HelloWorld::canhao->getPositionX())*(dedo.x-HelloWorld::canhao->getPositionX()) +

        (dedo.y-HelloWorld::canhao->getPositionY())*(dedo.y-HelloWorld::canhao->getPositionY()));

    HelloWorld::seta->setScaleX(tamanho/HelloWorld::seta->getContentSize().width);

}

void HelloWorld::ccTouchesEnded(CCSet *pTouches,CCEvent *pEvent) {

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

    CCPoint dedo = static_cast<CCTouch*>(pTouches->anyObject())->getLocationInView();

    dedo.y = CCDirector::sharedDirector()->getWinSize().height – dedo.y;

    HelloWorld::seta->setVisible(false);

    float vetor[2] = {dedo.x-HelloWorld::canhao->getPositionX(),dedo.y-HelloWorld::canhao->getPositionY()},tamanho;

    tamanho = sqrt((vetor[0]*vetor[0]) + (vetor[1]*vetor[1]));

    vetor[0] /= tamanho;

    vetor[1] /= tamanho;

    tamanho = 62*HelloWorld::canhao->getScale();

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

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

    balaCanhao->setPosition(ccp(HelloWorld::canhao->getPositionX() + vetor[0]*tamanho,HelloWorld::canhao->getPositionY() + vetor[1]*tamanho));

    addChild(balaCanhao);

    b2BodyDef definicaoBala;

    definicaoBala.position.Set(balaCanhao->getPositionX()/PTM_RATIO,balaCanhao->getPositionY()/PTM_RATIO);

    definicaoBala.type = b2_dynamicBody;

    b2Body* corpoBala = HelloWorld::mundoFisico->CreateBody(&definicaoBala);

    b2CircleShape geometriaBala;

    geometriaBala.m_radius = (balaCanhao->boundingBox().size.width/2)/PTM_RATIO;

    b2FixtureDef cascaBala;

    cascaBala.shape = &geometriaBala;

    cascaBala.density = 7830;

    corpoBala->CreateFixture(&cascaBala);

    balaCanhao->setUserData(corpoBala);

    HelloWorld::balasCanhaoSprites->addObject(balaCanhao);

    corpoBala->ApplyLinearImpulse(b2Vec2(250*(dedo.x-HelloWorld::canhao->getPositionX()),250*(dedo.y-HelloWorld::canhao->getPositionY())),

        corpoBala->GetWorldCenter());

}

Nesse código, nós implementamos as funcionalidades dos três métodos que são chamados quando interagimos com o jogo por meio da tela de toque. No método “ccTouchesBegan” nós incluímos tudo o que é necessário fazer quando iniciamos um toque na tela do aparelho. Note que nesse método nós deixamos o Sprite da seta visível, para que possamos ver a direção e a força com que a bala de canhão será lançada. Nós também mudamos a rotação do Sprite do canhão e do Sprite da seta, fazendo-os apontar para onde se localiza o dedo do jogador na tela. Tivemos que fazer alguns tratamentos para que o canhão seja rotacionado de forma correta. Como utilizamos a função arco tangente e, como essa função devolve valores de angulação entre 0 e 180 graus, então tivemos que verificar quando o jogador aponta com o canhão para trás, por exemplo. Também precisamos verificar se o canhão está voltado para a direita ou para a esquerda e atualizar a rotação do canhão levando isso em consideração. Dessa forma, apenas atualizamos a rotação do canhão e da seta e terminamos o método escalando o Sprite da seta de forma que ela possua um lado posicionado na base do canhão e o outro lado no dedo do jogador. Isso dará a força com que a bala será arremessada.

No método “ccTouchesMoved” fizemos a mesma coisa do método “ccTouchesBegan”, com a exceção de que não é necessário deixar o Sprite da seta visível, pois já o deixamos anteriormente. Note que precisamos atualizar a angulação do canhão e da seta, bem como a largura dela, porque o jogador pode mover o dedo entre um quadro e outro.

Para finalizar, implementamos no método “ccTouchesEnded” o tiro de uma bala de canhão. Para isso, precisamos fazer várias coisas. Primeiramente, deixamos invisível a seta que mostra a direção e a força que a bala de canhão é arremessada. Depois criamos um Sprite para a bala de canhão que será mostrada ao jogador. Logo após, criamos um objeto rígido dinâmico no mundo do Box2D que possui a densidade do aço (bala de aço) e que representará o Sprite da bala de canhão no mundo físico. Logo após, incluímos no Sprite criado uma referência ao objeto rígido da bala de canhão criada no mundo físico, para que possamos atualizar os Sprites a cada novo quadro processado. Por fim, adicionamos o Sprite na lista de Sprites de balas de canhão e aplicamos um impulso da bala de canhão para que ela seja atirada na direção de onde o jogador apontou. Note que a força da bala é proporcional à distância entre o dedo do jogador e a base do canhão.

Se você compilar esse código, você já pode atirar balas de canhão na direção que você bem entender. Ainda não há um limite de balas de canhão a serem atiradas, bem como não há um tratamento para finalizar o game quando o vaso é quebrado. Incluiremos esse tratamento nos próximos tutoriais. O resultado do tutorial de hoje é mostrado no vídeo a seguir. Legal, né!? =]

Vimos nesse tutorial como incluir a funcionalidade de arremesso das balas de canhão na direção que o jogador bem entender. Vimos que foi necessário incluir uma lista de Sprites de balas de canhão para que eles fossem atualizados na tela a cada quadro apresentado. Também vimos a necessidade de atualizar a rotação do canhão e da seta, bem com a largura dela a cada quadro apresentado, após o jogador iniciar um toque na tela. Isso porque ele pode mover o dedo entre um quadro e outro. Vimos também que precisamos criar as balas de canhão toda vez que o jogador retira o dedo da tela e que precisamos aplicar um impulso no objeto rígido para que a bala de canhão seja arremessada com direção e força certas. No próximo tutorial nós implementaremos a destruição do vaso para podermos dar um objetivo ao nosso game.

Um grande abraço e até mais. []

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