Tutorial: Simulação física com Cocos2d-x – Parte 3: Detecção e resposta de colisão

Até agora, implementamos simulações físicas com corpos rígidos e juntas.

Porém, ainda não vimos como implementar outras funções normalmente utilizadas em jogos que realizam simulação física.

Para finalizar esse conjunto de tutoriais vamos mostrar como realizar detecção e resposta de colisão.

Pistao

Parte 1Parte 2 – Parte 3

Vimos no último tutorial como simular um sistema de pistão de motor de carro. Criamos objetos rígidos de virabrequim, biela e pistão e os interligamos por meio de juntas de pino. Por último, nós adicionamos rotação no virabrequim e, com isso, fizemos o sistema funcionar.

Hoje, nós entenderemos como funciona a detecção e resposta de colisão de objetos rígidos em uma simulação física. Criaremos um ambiente com um objeto rígido em forma de bola no centro e faremos com que, esporadicamente, corpos rígidos em forma de caixa sejam criados em lugares e com movimentação aleatórios. Detectaremos quando uma caixa encosta na bola e faremos com que essa caixa seja excluída do ambiente.

Assim sendo, vamos começar o tutorial de hoje criando um projeto Cocos2d-x com simulação física.

 

Preparando a simulação

Crie um projeto Cocos2d-x com nome “SimulacaoFisica3” e com nome de pacote “com.Santy.SimulacaoFisica3”. Se você não se lembra de como criar um projeto desse tipo, veja como nesse tutorial. A Figura 1 mostra eu criando o projeto do tutorial de hoje.

Figura 1 - Criando o projeto
Figura 1 – Criando o projeto

Como de praxe, todo o projeto recém-criado vem com um trecho de código e com uma imagem desnecessários. Abra a pasta “Resources” e apague a imagem “HelloWorld.png”. Em compensação, é necessário adicionarmos o sprite sheet para que possamos criar os sprites da nossa simulação de hoje. Dessa forma, baixe esse arquivo compactado e o descompacte na pasta “Resources”. Se você não faz ideia do que sejam sprite e sprite sheet, dê uma olhada nesse tutorial.

Faltou apagarmos o trecho de código padrão do Cocos2d-x que mostra na tela a imagem que acabamos de apagar e uma etiqueta com o texto “Hello World”. Abra o arquivo “HelloWorldScene.cpp” e apague todo o código que se inicia aqui (inclusive):

/////////////////////////////

// 3. add your codes below…

// add a label shows “Hello World”

// create and initialize a label

auto label = Label::createWithTTF(“Hello World”, “fonts/Marker Felt.ttf”, 24);

e termina aqui (inclusive):

this->addChild(sprite, 0);

Com isso, nós deixamos a tela limpa, preparada para iniciarmos a programação do tutorial de hoje. Vamos preparar a simulação física incluindo os cálculos de física no processamento. Modifique a seguinte linha de código do arquivo “HelloWorldScene.cpp”:

auto scene = Scene::create();

de forma que ela fique assim:

auto scene = Scene::createWithPhysics();

Adicionado o cálculo de simulação física no aplicativo, vamos nos preocupar com o primeiro ponto importante. Faremos com que os objetos rígidos de caixa se movimentem na tela como se estivessem livres no espaço sideral. Isso significa que devemos tirar a força gravitacional da nossa simulação. Por padrão, o Cocos2d-x cria um mundo físico com gravidade para baixo. Para retirarmos a força de gravidade, adicione a seguinte linha de código no arquivo “HelloWorldScene.cpp”:

scene->getPhysicsWorld()->setGravity(Vec2(0.0,0.0));

logo abaixo dessa:

auto scene = Scene::createWithPhysics();

wallO segundo ponto importante a ser implementado, é a adição de paredes lógicas na tela do aparelho. Isso fará com que os corpos rígidos não saiam da tela. Para incluir essas paredes, adicione as seguintes linhas de código:

Vec2 pontos[] = {Vec2(-visibleSize.width/2,-visibleSize.height/2),Vec2(-visibleSize.width/2,visibleSize.height/2),

  Vec2(visibleSize.width/2,visibleSize.height/2),Vec2(visibleSize.width/2,-visibleSize.height/2)};

PhysicsBody* limitesCorpo = PhysicsBody::createEdgePolygon(pontos,4,PhysicsMaterial(1.0,0.0,0.0));

limitesCorpo->setDynamic(false);

addComponent(limitesCorpo);

logo abaixo dessa:

this->addChild(menu, 1);

Com a criação do ambiente físico sem gravidade e com paredes lógicas, podemos começar a pensar na criação aleatória de caixas.

 

Criando objetos rígidos aleatórios

Como funcionará a criação aleatória de corpos rígidos? Criaremos corpos rígidos em formato de caixa em lugares aleatórios da tela do aparelho, com velocidade e direção aleatórias a cada 2 décimos de segundo. Com o passar do tempo, a tela será preenchida por caixas com movimentação aleatória. A criação dessas caixas será realizada por um método específico que implementaremos, nomeado “criaCaixa”. Para que possamos programar a execução desse método a cada 2 décimos de segundo, precisamos declará-lo na classe “HelloWorld”. Abra o arquivo “HelloWorldScene.h” e adicione a seguinte linha de código:

void criaCaixa(float dt);

logo abaixo dessa:

virtual bool init();

Agora implementaremos esse método adicionando as seguintes linhas de código no final do arquivo “HelloWorldScene.cpp”.

void HelloWorld::criaCaixa(float dt) {

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

    Sprite* caixa = Sprite::createWithSpriteFrameName(“Caixa.png”);

    caixa->setScale((0.1*size.height)/caixa->getContentSize().height);

    caixa->setPosition(

      cocos2d::random(caixa->getBoundingBox().size.width/2.0,size.width-caixa->getBoundingBox().size.width/2.0),

      cocos2d::random(caixa->getBoundingBox().size.height/2.0,size.height-caixa->getBoundingBox().size.height/2.0));

    addChild(caixa);

    PhysicsBody* corpo = PhysicsBody::createBox(caixa->getContentSize(),PhysicsMaterial(1.0,0.0,0.0));

    corpo->setVelocity(Vec2(cocos2d::random(-500.0,500.0),cocos2d::random(-500.0,500.0)));

    caixa->addComponent(corpo);

}

alturaO método “criaCaixa”, primeiramente, adiciona um sprite de caixa na tela com altura igual a 10% da altura da tela do aparelho e com posição aleatória na tela. Logo após, ele cria um corpo rígido em formato de caixa e atribui uma velocidade com direção e sentido aleatórios. Isso fará com que o corpo rígido entre em movimento. Por último, o corpo rígido é interligado com o sprite recém-criado. Simples assim. =]

Declarar o método “criaCaixa” na classe “HelloWorld” e o implementar no arquivo “HelloWorldScene.cpp” não é o suficiente para que as caixas sejam criadas constantemente. Na verdade, ainda falta programar a execução desse método a cada 2 décimos de segundo. Para isso, adicione a seguinte linha de código:

schedule(schedule_selector(HelloWorld::criaCaixa),0.2);

logo abaixo dessa:

addComponent(limitesCorpo);

Isso é o suficiente para a criação aleatória de caixas. Agora, implementaremos a detecção e resposta de colisão das caixas com a bola de pedra.

 

Detecção e resposta de colisão

Para finalizar o tutorial de hoje, nós implementaremos um objeto central que destrói toda a caixa que nele encostar. Esse objeto será uma bola de pedra que não sofre nenhum efeito se um corpo rígido colidir com ela. Na verdade, a intenção é mostrar como detectar quando dois corpos entram em colisão e como programar o efeito para essa colisão. No seu jogo, você pode programar uma animação de quebra da caixa, por exemplo. Aqui, para simplificar as coisas, nós apenas apagaremos a caixa que bater na bola de pedra. Para criar a bola, adicione as seguintes linhas de código no arquivo “HelloWorldScene.cpp”:

SpriteFrameCache::getInstance()->addSpriteFramesWithFile(“SpriteSheet.plist”);

Sprite* bola = Sprite::createWithSpriteFrameName(“Bola.png”);

bola->setScale((0.2*visibleSize.height)/bola->getContentSize().height);

bola->setPosition(visibleSize.width/2.0,visibleSize.height/2.0);

bola->setTag(1);

addChild(bola);

PhysicsBody* corpo = PhysicsBody::createCircle(bola->getContentSize().width/2.0,PhysicsMaterial(1.0,0.0,0.0));

corpo->setDynamic(false);

corpo->setContactTestBitmask(1);

bola->addComponent(corpo);

logo abaixo dessa:

addComponent(limitesCorpo);

sonyprojectmorpheusProgramadores mais experientes, notarão que ainda não havíamos carregado o sprite sheet para que pudéssemos criar os sprites da simulação. Assim, o carregamos agora para que o aplicativo não “desse pau”. Criamos um sprite de bola de pedra com altura igual a 20% da altura da tela e o colocamos no centro da tela. Logo após, nós criamos um corpo rígido estático para a bola e o interligamos ao sprite. Note que nós identificamos o sprite da bola com a Tag 1 e que igualamos o bitmask do corpo da bola com o valor 1. Essas duas linhas de código não estão ali a toa. Quando identificarmos a colisão de dois corpos, nós precisaremos identificar se um deles corresponde ao corpo rígido da pedra. Por isso nós identificamos o sprite da pedra com a Tag 1.

No caso do bitmask, para que o sistema de detecção de colisão funcione, é necessário que os corpos tenham algum valor de bitmask. Assim, nós precisamos atribuir algum valor de bitmask para os corpos que desejamos detectar colisão. Obviamente, a pedra precisa ter algum valor de bitmask para que sejam detectadas colisões com ela. Além disso, as caixas também precisam ter um valor de bitmask atribuído aos seus corpos rígidos. Para solucionar isso, adicione a seguinte linha de código:

corpo->setContactTestBitmask(1);

logo ACIMA dessa:

caixa->addComponent(corpo);

Se você executar o aplicativo do jeito que está, a simulação física ocorrerá normalmente, porém nenhuma caixa é destruída se entrar em contato com a bola central. Ainda precisamos criar um método “ouvidor” que detecta a colisão entre dois corpos rígidos. Declararemos esse método na classe “HelloWorld” e o nomearemos “onContactBegin”. Abra o arquivo “HelloWorldScene.h” e adicione a seguinte linha de código:

bool onContactBegin(cocos2d::PhysicsContact& contact);

logo abaixo dessa:

void criaCaixa(float dt);

Agora implementaremos esse método. Adicione as seguintes linhas de código no final do arquivo “HelloWorldScene.cpp”.

bool HelloWorld::onContactBegin(PhysicsContact& contact) {

    Node* na = contact.getShapeA()->getBody()->getNode();

    Node* nb = contact.getShapeB()->getBody()->getNode();

    if(na->getTag()==1)

        nb->removeFromParentAndCleanup(true);

    else if(nb->getTag()==1)

        na->removeFromParentAndCleanup(true);

    return true;

}

BillardO funcionamento do método “onContactBegin” é simples. Se o corpo A for a bola, então o corpo B é excluído da tela. Se o corpo B for a bola, então o corpo A é excluído da tela. Se nenhum dos dois corpos for a bola, então não é feito nada em relação à colisão detectada. Simples, não. ;D

A implementação do método “onContactBegin” não é o suficiente para que a resposta e detecção de colisão aconteça. Para finalizar, precisamos criar um objeto “ouvidor” para que seja computada a detecção de colisão. Vamos criar esse objeto adicionando as seguintes linhas de código no arquivo “HelloWorldScene.cpp”:

EventListenerPhysicsContact* detector = EventListenerPhysicsContact::create();

detector->onContactBegin = CC_CALLBACK_1(HelloWorld::onContactBegin,this);

getEventDispatcher()->addEventListenerWithSceneGraphPriority(detector,this);

logo abaixo dessa:

bola->addComponent(corpo);

Agora sim … você pode compilar o código e executar a simulação. Note que, existe uma bola de pedra central e que caixas são criadas aleatoriamente na tela. Essas caixas são arremessadas para uma direção aleatória com força também aleatória. Perceba que, quando uma caixa colide com a bola de pedra, a caixa é excluída da simulação. Isso porque o método “onContactBegin” é chamado e é identificada a colisão de alguma caixa com a pedra. Para os curiosos de plantão, o vídeo a seguir ilustra a simulação.

 

Vimos no tutorial de hoje como detectar colisões e como implementar efeitos em colisões. Criamos uma simulação física com caixas flutuando e com uma pedra circular no centro. Fizemos com que cada colisão fosse detectada e com que somente as colisões com a bola de pedra tivessem algum efeito diferente do padrão. As caixas que colidirem com a pedra são imediatamente retiradas da simulação, sendo esse o tratamento de resposta de colisão.

Daremos início a implementação de outro jogo no próximo tutorial. Porém, eu ainda não sei qual. =P

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