Friday, November 7, 2014

Adding physics to my game - Box2D

I wrote about Box2D before in previous post, but now i am going to go into detail and starting from scratch, writing about the key concepts and little details that help understand how things work.

For this entry i will work on creating a world, a body, a sprite and adding all that together to make a simple scene, where we see our sprite falling.

I will use PTM_RATIO, WORLD_TO_SCREEN and SCREEN_TO_WORLD macros, so make sure you read about them in my old post.

We start with our init method:


bool GameScene::init()
{
    if ( !Layer::init() )
    {
        return false;
    }
    
    Size visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();

 b2Vec2 gravity(0, -9.8f); //normal earth gravity, 9.8 m/s/s straight down!
 gameWorld = new b2World(gravity);//Set a new world with the given gravity

 auto player = Sprite::create("player.png");
 player->setPosition(Point(visibleSize.width/2, visibleSize.height/2));

 b2BodyDef playerBodyDef;
 playerBodyDef.type = b2_dynamicBody; //this will be a dynamic body
 playerBodyDef.position.Set(SCREEN_TO_WORLD(player->getPosition().x), SCREEN_TO_WORLD(player->getPosition().y)); //set the starting position
 playerBodyDef.angle = 0; //set the starting angle
 playerBodyDef.userData = player;

 b2Body *playerBody = gameWorld->CreateBody(&playerBodyDef);

 // Create shape definition and add to body

 //set each vertex of polygon in an array
 b2Vec2 vertices[5];
 vertices[0].Set(-1,  2);
 vertices[1].Set(-1,  0);
 vertices[2].Set( 0, -3);
 vertices[3].Set( 1,  0);
 vertices[4].Set( 1,  1);
  
 b2PolygonShape playerShape;
 playerShape.Set(vertices, 5); //pass array to the shape

 b2FixtureDef playerFixtureDef;

 playerFixtureDef.shape = &playerShape; //change the shape of the fixture
 playerFixtureDef.density = 10.0f;
        playerFixtureDef.friction = 0.4f;
        playerFixtureDef.restitution = 0.1f;

 b2Fixture *playerFixture = playerBody->CreateFixture(&playerFixtureDef);

 this->addChild(player);

 this->scheduleUpdate();
    return true;
}

For this to work properly, add a private member "gameWorld" to your GameScene.h (and #include "Box2D\Box2D.h").

For this exercise, all i do is create a world initialized with some value for gravity. You can change gravity variable to play with it and modify the physics output for your world.

To add bodies, you need to create some kind of template and then create an actual physics body with it. In the example the template is playerBodyDef. I add type (read Box2D docs on body types), position, angle and user data.
I Added a sprite, and then convert the position of my sprite into coordinates that my physics body can handle and position it there.
Remember to add userData, it is important to recognize the sprite attached to your body.

Once we are done setting the BodyDef, we create an actual body (playerBody).

The fixtures represent physical properties and just like bodies, we create a template first (playerFixtureDef) and then create an actual fixture, attached to a body. The fixture is the one that interacts with the physics world.

The last line in our method is this->scheduleUpdate(), which will start our game's main loop.

The void update(float dt) method can be overriden to go into the game's main loop, and we will, so add that to your GameScene.cpp.


void GameScene::update(float dt){
 int velocityIterations = 10;
 int positionIterations = 10;

 // Instruct the world to perform a single step of simulation. It is
 // generally best to keep the time step and iterations fixed.
 gameWorld->Step(dt, velocityIterations, positionIterations);

 bool blockFound = false;

 // Iterate over the bodies in the physics world
 for (b2Body* b = this->gameWorld->GetBodyList(); b; b = b->GetNext()){
  if(b->GetUserData() != NULL){
   Sprite *sprite = (Sprite*)b->GetUserData();
   sprite->setPosition(Point(b->GetPosition().x*PTM_RATIO, b->GetPosition().y*PTM_RATIO));
   sprite->setRotation(-1*CC_RADIANS_TO_DEGREES(b->GetAngle()));
  }
 }

}

velocityIterations and positionIterations are values that are used in the physics calculation, and the higher they are the more accurate our physics will be, but the longer it will take to calculate them.

The gameWorld->Step() is telling the world to update physics.

We need to manually update our sprite according to physics world, so we search in every body in the physics world for sprites (remember i told you to add userData in body?) and if some body has a sprite, we update it's position. Remember we need to convert from meters to pixels, and that's what PTM_RATIO is for.

With this you should be able to set up a body in a scene and see how it falls into it's death (not really). You can simulate several things, but without anything else to interact it is kinda plain (feel free to add more bodies and watch them collide). I will go into adding more things to the scene in a future entry.

No comments:

Post a Comment