Showing posts with label Box2D. Show all posts
Showing posts with label Box2D. Show all posts

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.

Thursday, April 17, 2014

Shooting bullets using Box2D

I got to the point where i wanted to shoot a bullet from some position toward my main character (its centroid).

To do this you have to make a little set up in the body definition:

b2BodyDef bulletBodyDef;
bulletBodyDef.type=b2_dynamicBody;
bulletBodyDef.bullet = true;

Additionally to the regular settings you have to set "isBullet" to true. This will tell Box2D to calculate collision continuosly for that body to avoid tunneling (passing through other bodies when moving too fast).

To actually shoot the bullet just apply a linear impulse to it. To calculate the direction in my case just get the vector from bullet to target, normalize it and multiply it by the magnitude of the impulse you are applying.

auto force = 10.0f;
auto hyp = target - bullet->GetPosition();
hyp.Normalize();
bullet->ApplyLinearImpulse(b2Vec2(force*hyp.x, force*hyp.y), bullet->GetWorldCenter(), true);

Once you have this you can shoot as many bodies as you want and if you are lucky enough your main character will have tons of holes :)

Tuesday, April 8, 2014

Using Box2D on Cocos2dx

Box2D is an open source C++ engine for simulating rigid bodies in 2D.

This does not pretend to be an exhaustive tutorial about using Box2D, there is a lot of information about that already. I will try to show you the biggest mistake i made when using it and that took me a while to figure out: using the PTM_RATIO.

There will be 2 worlds: a graphic world and a physics simulation world.
When you are working on the graphic world you will have everything on terms of pixels, so you need to define a "pixel to meter ratio". This ratio will allow the worlds to communicate.

You can define a couple macros on C++ like this:

#define PTM_RATIO 50
#define WORLD_TO_SCREEN(n) ((n) * PTM_RATIO) //macro to convert meters to pixels.
#define SCREEN_TO_WORLD(n) ((n) / PTM_RATIO) //macro to convert pixels to meters.

You have to remember to use those macros when sending info from a world to another.

You can send info to the physics world:

myBodyDef.position = b2Vec2(SCREEN_TO_WORLD(mySprite->getPositionX()), SCREEN_TO_WORLD(mySprite->getPositionY()));

You will need a method to update your graphics world, and inside it you will move your graphics depending on the output of the physics world. Remember to scale them back:


mySprite->setPosition(WORLD_TO_SCREEN(myBody->GetPosition().x), WORLD_TO_SCREEN(myBody->GetPosition().y));

The third option you will have is to set some variables to the physics world. If you have them in pixels you can use a macro, but if you have them directly on MKS system (the system Box2D uses by default) you can send them directly:

world = new b2World(b2Vec2(0.0f, -9.8f)); //Sets a gravity of 9.8m/s^2

Box2D is designed to work efficiently with objects ranging from 0.1m to 10m. You can scale them back in your graphics world as much as you want, but remember to use the values in that range in the physics world.

Have fun using Box2D...it is amazing!!! :)