Tutorial: Criando um jogo ao estilo Flappy Bird – Parte 4: Postes com alturas aleatórias

Temos uma versão jogável do nosso Flappy Bird (créditos a .GEARS) onde os tubos aparecem sempre na mesma altura.

É divertido e já dá uma ideia da jogabilidade, mas ainda não está parecido com o jogo, onde o desafio está em desviar dos tubos que têm alturas aleatórias.

Faremos agora então que o pato aponte para onde ele está se movimentando no momento.

Vimos no último tutorial como criar os postes que o pato precisa desviar. Para isso, deixamos o mundo físico sem gravidade, para que os postes presos no teto não caíssem, e aplicamos a força da gravidade somente no pato. Criamos os postes fisicamente por meio de objetos rígidos e “colamos figurinhas neles” com Sprites, para que o jogador tenha a visão de postes e não apenas de retângulos.

Nesse tutorial nós modificaremos algumas linhas de código para que os postes possuam alturas aleatórias, dando um desafio maior ao game. Isso não exigirá muitas modificações no código e, por isso, faremos também mais uma alteração simples. No final desse tutorial, o Sprite do pato rotacionará de forma que ele aponte para cima logo após o jogador clicar na tela e aponte para baixo quando ele estiver caindo. Partiu código. o

 

Postes com alturas aleatórias

Realizaremos algumas modificações no código adicionado no tutorial passado. Dessa forma, faça-o antes, para que possamos evoluir o nosso game desempenhando esse. Primeiramente, abra o arquivo “HelloWorldScene.cpp” e adicione as seguintes linhas de código:

static int numeroAleatorio = time(NULL);

float fenda;

srand(numeroAleatorio);

numeroAleatorio = rand();

fenda = 0.3 + 0.4*(((float)numeroAleatorio)/RAND_MAX);

logo abaixo dessa:

b2BodyDef corpoPosteSuperiorDefinicao;

Nessas linhas, nós implementamos um sistema simples de geração de números aleatórios que variam do valor 0,3 até o valor 0,7. Explicando de forma resumida, a altura daquela fenda por entre os postes que o pato precisa passar pode variar de 30% até 70% da altura da tela. No mesmo arquivo, modifique a seguinte linha de código:

corpoPosteSuperiorDefinicao.position.Set((size.width+0.04*size.height)/PTM_RATIO,(0.8*size.height)/PTM_RATIO);

de forma que ela fique assim:

corpoPosteSuperiorDefinicao.position.Set((size.width+0.04*size.height)/PTM_RATIO,((1.0-(1.0-fenda-0.1)/2)*size.height)/PTM_RATIO-0.02);

Agora modifique a seguinte linha de código:

geometriaPosteSuperior.SetAsBox((0.04*size.height)/PTM_RATIO,(0.2*size.height)/PTM_RATIO);

de forma que ela fique assim:

geometriaPosteSuperior.SetAsBox((0.04*size.height)/PTM_RATIO,(((1.0-fenda-0.1)/2)*size.height)/PTM_RATIO);

Na primeira modificação, nós posicionamos o objeto rígido do poste superior levando em consideração onde a fenda estará. Note que reduzimos um valor mínimo de 0,02 no posicionamento vertical para evitar que o corpo rígido entre em colisão com o teto. Na segunda modificação, nós alteramos a altura do objeto rígido em forma de caixa levando em consideração onde a fenda está. Nada de muito complicado até o momento. Agora modifique a seguinte linha de código:

posteCorpoSuperior->setScaleY((0.4*size.height)/posteCorpoSuperior->boundingBox().size.height);

de forma que ela fique assim:

posteCorpoSuperior->setScaleY(((1.0-fenda-0.1)*size.height)/posteCorpoSuperior->boundingBox().size.height);

Modifique também a seguinte linha de código:

posteCabecaSuperior->setAnchorPoint(ccp(0.5,-(0.2-0.08)/0.08));

de forma que ela fique assim:

posteCabecaSuperior->setAnchorPoint(ccp(0.5,-(((1.0-fenda-0.1)/2-0.08))/0.08));

Nessas modificações nós, respectivamente, atualizamos a altura do Sprite do corpo do poste superior e modificamos o ponto âncora do Sprite da cabeça do pote superior, para que ela seja posicionada na ponta do corpo rígido. Agora faremos as mesmas modificações, mas para o poste inferior. Modifique essa linha de código:

corpoPosteInferiorDefinicao.position.Set((size.width+0.04*size.height)/PTM_RATIO,(0.2*size.height)/PTM_RATIO);

de forma que ela fique assim:

corpoPosteInferiorDefinicao.position.Set((size.width+0.04*size.height)/PTM_RATIO,(((fenda-0.1)/2)*size.height)/PTM_RATIO+0.02);

Modifique a seguinte linha de código:

geometriaPosteInferior.SetAsBox((0.04*size.height)/PTM_RATIO,(0.2*size.height)/PTM_RATIO);

de forma que ela fique assim:

geometriaPosteInferior.SetAsBox((0.04*size.height)/PTM_RATIO,(((fenda-0.1)/2)*size.height)/PTM_RATIO);

Modifique a seguinte linha de código:

posteCorpoInferior->setScaleY((0.4*size.height)/posteCorpoInferior->boundingBox().size.height);

de forma que ela fique assim:

posteCorpoInferior->setScaleY(((fenda-0.1)*size.height)/posteCorpoInferior->boundingBox().size.height);

Modifique a seguinte linha de código:

posteCabecaInferior->setAnchorPoint(ccp(0.5,-(0.2-0.08)/0.08));

de forma que ela fique assim:

posteCabecaInferior->setAnchorPoint(ccp(0.5,-(((fenda-0.1)/2-0.08))/0.08));

Modifique a seguinte linha de código:

postePeInferior->setAnchorPoint(ccp(0.5,0.2/0.04));

de forma que ela fique assim:

postePeInferior->setAnchorPoint(ccp(0.5,((fenda-0.1)/2)/0.04));

Note que também modificamos o ponto âncora do Sprite do pé do poste inferior. Se você compilar o código e executar o jogo, perceberá que os postes serão gerados com fenda em alturas aleatórias. Vamos adiante.

 

Rotação do pato

Para fazermos com que o pato aponte para onde ele está se movimentando no momento, precisaremos modificar a angulação do Sprite do pato a cada quadro. Essa angulação será descoberta por meio do vetor velocidade do pato.

Algebricamente, podemos normalizar esse vetor, ou seja, deixá-lo com módulo (tamanho) igual a 1, e descobrir a angulação entre esse vetor e o eixo X. Para descobrir essa angulação, podemos utilizar a função arcoseno, que devolve o valor de ângulo em radianos com base no valor do seno do ângulo. O seno poderemos descobrir diretamente pelo valor de coordenada do eixo Y do vetor velocidade após a normalização.

A Figura 1 mostra a relação entre o ângulo de um vetor normalizado em relação ao eixo X e o seu valor de seno.

Figura 1 - Angulo entre um vetor e o eixo X

Figura 1 – Angulo entre um vetor e o eixo X

Entendendo a álgebra para descobrir o ângulo com base no vetor velocidade do pato, adicione as seguintes linhas de código:

float aux,vel[2] = {2.0,HelloWorld::corpoPato->GetLinearVelocity().y};

aux = sqrt(vel[0]*vel[0] + vel[1]*vel[1]);

vel[1] /= aux;

aux = asin(vel[1]);

HelloWorld::pato->setRotation(-(180*aux)/M_PI);

logo abaixo dessa:

HelloWorld::pato->setPosition(ccp(HelloWorld::corpoPato->GetPosition().x*PTM_RATIO,HelloWorld::corpoPato->GetPosition().y*PTM_RATIO));

Note que, primeiramente, criamos um vetor de velocidade do pato com coordenada X igual a 2 e coordenada Y igual ao valor de coordenada Y do vetor velocidade do pato. Forçamos que a coordenada X fosse 2 porque, na verdade, o pato não anda para frente, mas são os postes que andam, o que acaba dando o aspecto de movimentação do pato. Logo após, normalizamos a coordenada Y do vetor e calculamos o valor de arcoseno da coordenada.

Nesse momento, temos o ângulo do pato em relação ao eixo X em radianos, ou seja, precisamos converter emgraus para que possamos atualizar a angulação do Sprite do pato. Fazemos essa conversão diretamente no método que modifica a angulação do Sprite.

Toda a alteração que fizemos no código por meio do tutorial de hoje resulta em uma execução parecida com a que é mostrada na Figura 2. Nela, os postes têm alturas aleatórias e o pato está apontando para cima, pelo fato de eu ter capturado a imagem logo após eu ter encostado na tela. Legal, né?!

Figura 2 - Jogo executando

Figura 2 – Jogo executando

Vimos no tutorial de hoje como deixar os postes com altura aleatória e como fazer o pato modificar a sua angulação conforme o seu vetor velocidade.

Tivemos que escrever um sistema simples de geração de números aleatórios que determinava a altura do poste. Com a atura determinada, precisamos fazer com que os objetos rígidos e Sprites dos postes se adequassem à altura encontrada.

Logo após, nós implementamos a angulação do Sprite do pato com base na normalização do vetor velocidade do objeto rígido que o representa no mundo físico. Após a normalização, calculamos o valor do arcoseno e convertemos o ângulo de radianos para graus. Com isso, encontramos o valor da angulação do Sprite do pato.

No próximo tutorial nós implementaremos o sistema de colisão, que determina o término do jogo. Também contaremos os tubos que o jogador já passou e mostraremos uma mensagem quando o jogo é finalizado.

Por hoje é isso, povo, nos vemos no próximo tutorial. []s

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