Tutorial: Desenvolvendo um Jogo de Sinuca em Cocos2d-x: Parte 4 – Bela tacada

Até agora, a simulação física de movimentação das bolas de bilhar já acontece nos conformes.

Hoje nos preocuparemos com a jogabilidade.

Então, vamos implementar o sistema de tacadas.

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

Implementamos no último tutorial a detecção de colisão entre os corpos rígidos das bolas de bilhar e da mesa. Quando uma colisão é detectada, os corpos rígidos passam a sofrer ação de uma nova força de atrito entre eles mesmos e a superfície da mesa. Por último, nós realizamos uma tacada para ver a simulação física das bolas rolando sobre a mesa de bilhar.

Continuaremos nesse tutorial a implementação do nosso jogo de sinuca incluindo a funcionalidade de tacadas. Como normalmente acontece em jogos de sinuca, o jogador tem a possibilidade de ver a direção por onde a bola branca vai rolar antes de realizar uma tacada. No nosso jogo não será diferente. Quando o jogador encostar o dedo na tela no momento de uma tacada, será apresentada uma seta que mostra a direção e a força com que tacada será realizada na bola branca.

Assim sendo, começaremos implementando o sistema de direcionamento e força da tacada.

 

Mirando a tacada

miraComo no padrão de jogos de sinuca, a tacada será mirada pelo jogador por meio de uma seta. Tal seta possuirá sua base na bola branca, onde será realizada a tacada, e apontará para o ponto na tela onde o jogador está encostando o dedo. Além disso, a seta terá o comprimento exato para que a sua base fique na bola branca e a sua ponta fique exatamente onde o jogador encosta o dedo. A direção da seta determinará a direção da tacada e o comprimento da seta determinará a força que a tacada terá.

Dada a explicação do sistema de tacadas, começaremos a codificar. Precisamos criar um atributo na classe “HelloWorld” que armazena a referência do sprite da seta. Também percebe-se que precisaremos de um objeto que detecta toques na tela. Em decorrência disso, precisamos implementar alguns métodos de toque na tela. Neste caso, precisaremos implementar os métodos “onTouchBegan”, “onTouchMoved” e “onTouchEnded”. Assim sendo, adicionaremos tais atributos e protótipos na classe “HelloWorld”. Abra o arquivo “HelloWorldScene.h” e adicione as seguintes linhas de código:

cocos2d::Sprite* seta;

cocos2d::EventListenerTouchOneByOne* toqueNaTela;

bool onTouchBegan(cocos2d::Touch *touch,cocos2d::Event *unused_event);

void onTouchMoved(cocos2d::Touch *touch,cocos2d::Event *unused_event);

void onTouchEnded(cocos2d::Touch *touch,cocos2d::Event *unused_event);

logo abaixo dessa:

cocos2d::PhysicsBody* mesaCorpo;

Com os atributos devidamente criados e com os protótipos declarados, agora podemos instanciar os objetos e implementar os métodos. Começaremos instanciando o objeto do sprite da seta. Para isso, abra o arquivo “HelloWorldScene.cpp” e adicione as seguintes linhas de código:

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

HelloWorld::seta->setScaleY(HelloWorld::mesa->getScale());

HelloWorld::seta->setAnchorPoint(Vec2(1.0,0.5));

HelloWorld::seta->setVisible(false);

addChild(HelloWorld::seta);

logo abaixo dessa:

HelloWorld::bolaBrancaCorpo->setVelocity(Vec2(-300.0,0.0));

Note que a seta inicia invisível, pois ela aparece apenas no momento em que o jogador encostar o dedo na tela antes de uma tacada. Além disso, nós modificamos o ponto âncora da seta, visto que, a partir de agora, nós a posicionaremos na tela a partir de sua base.

setatelaAgora implementaremos os métodos em que declaramos os protótipos anteriormente. Quando o jogador iniciar um toque na tela, será chamado o método “onTouchBegan”. Nele implementaremos a sub-rotina que apresenta a seta na tela, posiciona a base da seta sobre a bola branca, rotaciona a seta conforme a angulação entre o eixo horizontal e a posição do dedo do jogador e alonga a seta de forma que ela fique com a ponta sob o dedo do jogador. Para implementar o método “onTouchBegan”, adicione as seguintes linhas de código no final do arquivo “HelloWorldScene.cpp”.

bool HelloWorld::onTouchBegan(Touch *touch,Event *unused_event) {

    Size size = Director::getInstance()->getVisibleSize();

    Vec2 p = touch->getLocationInView();

    p.y = size.height – p.y;

    p.operator-=(HelloWorld::bolaBranca->getPosition());

    if(p.y>0.0)

        HelloWorld::seta->setRotation((Vec2::angle(Vec2(-1.0,0.0),p)*180.0)/M_PI);

    else

        HelloWorld::seta->setRotation(-(Vec2::angle(Vec2(-1.0,0.0),p)*180.0)/M_PI);

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

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

    HelloWorld::seta->setVisible(true);

    return true;

}

Quando o jogador mover o dedo que já está encostado na tela, é necessário apenas atualizar a angulação e o comprimento da seta. Essa sub-rotina será realizada pelo método “onTouchMoved”. Para implementar esse método, adicione as seguintes linhas de código no final do arquivo “HelloWorldScene.cpp”.

void HelloWorld::onTouchMoved(Touch *touch,Event *unused_event) {

    Size size = Director::getInstance()->getVisibleSize();

    Vec2 p = touch->getLocationInView();

    p.y = size.height – p.y;

    p.operator-=(HelloWorld::bolaBranca->getPosition());

    if(p.y>0.0)

        HelloWorld::seta->setRotation((Vec2::angle(Vec2(-1.0,0.0),p)*180.0)/M_PI);

    else

        HelloWorld::seta->setRotation(-(Vec2::angle(Vec2(-1.0,0.0),p)*180.0)/M_PI);

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

}

Quando o jogador tirar o dedo da tela, o método “onTouchEnded” é chamado. Esse método precisa de um pouco mais de atenção para implementá-lo. Deixaremos isso para a próxima seção do tutorial. Apenas adicione a implementação dele vazia, adicionando as seguintes linhas de código no final do arquivo “HelloWorldScene.cpp”.

void HelloWorld::onTouchEnded(Touch *touch,Event *unused_event) {

}

detetoresCom os três métodos responsáveis pelo tratamento de toques na tela devidamente implementados, podemos criar uma instância do objeto detector de toques na tela. Criaremos uma instância desse objeto e faremos ele executar os métodos implementados quando os eventos de toque na tela acontecer. Adicione as seguintes linhas de código:

HelloWorld::toqueNaTela = EventListenerTouchOneByOne::create();

HelloWorld::toqueNaTela->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan,this);

HelloWorld::toqueNaTela->onTouchMoved = CC_CALLBACK_2(HelloWorld::onTouchMoved,this);

HelloWorld::toqueNaTela->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEnded,this);

getEventDispatcher()->addEventListenerWithSceneGraphPriority(HelloWorld::toqueNaTela,this);

logo abaixo dessa:

addChild(HelloWorld::seta);

Programadores mais experientes notarão que ainda não excluímos a linha de código adicionada no último tutorial que realiza uma tacada inicial na bola branca. Essa linha foi adicionada apenas para teste e deve ser removida. Assim sendo, apague a seguinte linha de código do arquivo “HelloWorldScene.cpp”.

HelloWorld::bolaBrancaCorpo->setVelocity(Vec2(-300.0,0.0));

Agora podemos finalmente pensar em realizar a tacada quando o jogador soltar o dedo da tela.

 

Realizando a tacada

tacadaQuando o jogador soltar o dedo da tela, será atribuída uma velocidade para a bola branca com base na direção e comprimento da seta. Além disso, será aplicada uma força de atrito na bola branca, exatamente como fizemos no tutorial passado. Outro ponto importante, após a execução de uma tacada, o jogador não poderá realizar novas tacadas até o momento em que todas as bolas estejam paradas. Para limitar isso, a tela de toque será desabilitada após a tacada e será reabilitada somente quando todas as bolas pararem. Para finalizar, a seta que mirava a última tacada será deixada invisível para que a simulação física seja mostrada naturalmente ao jogador.

Todas as rotinas descritas serão realizadas pelo método “onTouchEnded”. Assim sendo, no arquivo “HelloWorldScene.cpp”, adicione as seguintes linhas e código:

static float g = 9.8;

static float mi = 3.0;

Vec2 dir,fa;

float t;

Size size = Director::getInstance()->getVisibleSize();

Vec2 p = touch->getLocationInView();

p.y = size.height – p.y;

p.operator-=(HelloWorld::bolaBranca->getPosition());

HelloWorld::seta->setVisible(false);

HelloWorld::bolaBrancaCorpo->setVelocity(p.operator/(HelloWorld::mesa->getScale()));

dir = HelloWorld::bolaBrancaCorpo->getVelocity();

dir.normalize();

dir.negate();

fa = dir.operator*(HelloWorld::bolaBrancaCorpo->getMass()*g*mi);

HelloWorld::bolaBrancaCorpo->applyForce(fa);

t = (HelloWorld::bolaBrancaCorpo->getVelocity().length()*HelloWorld::bolaBrancaCorpo->getMass())/fa.length();

schedule(schedule_selector(HelloWorld::paraBolaBranca),t);

HelloWorld::toqueNaTela->setEnabled(false);

logo após essa:

void HelloWorld::onTouchEnded(Touch *touch,Event *unused_event) {

Para finalizar o tutorial de hoje, precisamos habilitar a tela de toque para que seja possível o jogador realizar a próxima tacada após todas as bolas entrarem em repouso. Para isso, quando cada bola parar, testaremos se todas as outras também já estão paradas. Caso isso seja verdade, então a tela de toque é habilitada para que o jogador possa realizar a próxima tacada. Assim, no arquivo “HelloWorldScene.cpp”, adicione as seguintes linhas de código:

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

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

    HelloWorld::toqueNaTela->setEnabled(true);

logo abaixo dessas:

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

unschedule(schedule_selector(HelloWorld::paraBolaBranca));

as seguintes linhas de código:

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

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

    HelloWorld::toqueNaTela->setEnabled(true);

logo abaixo dessas:

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

unschedule(schedule_selector(HelloWorld::paraBolaVermelha));

e as seguintes linhas de código:

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

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

    HelloWorld::toqueNaTela->setEnabled(true);

logo abaixo dessas:

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

unschedule(schedule_selector(HelloWorld::paraBolaAmarela));

Agora sim. Finalizamos a implementação do tutorial de hoje. Já podemos compilar o código e executar o jogo. Você perceberá que será possível realizar a primeira tacada ao encostar o dedo na tela. Após realizar a primeira tacada, o jogo não deixa o jogador realizar outra enquanto existir pelo menos uma bola em movimento. Quando todas as bolas pararem, o jogo habilita a tela de toque e deixa o jogador realizar a próxima tacada, exatamente como queríamos que acontecesse. A Figura 1 ilustra a seta apresentada quando o jogador está prestes a realizar uma tacada na bola vermelha.

Figura 1 - Uma tacada sendo realizada
Figura 1 – Uma tacada sendo realizada

Implementamos nesse tutorial o sistema de tacadas do nosso jogo de sinuca. Para implementá-lo, nós precisamos criar um sprite de seta para que o jogador possa visualizar a direção e a força com as quais a bola será tacada. Além disso, nós criamos também um objeto detector de toques na tela e implementamos as rotinas necessárias para executar a tacada. Tais rotinas são a mira da tacada e a execução dela.

Veremos no próximo tutorial como implementar o sistema das caçapas. Faremos com que as bolas que chegarem as caçapas sejas retiradas da mesa.

Um grande abraço e até lá. []

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 *