Box2D C++ tutorials - Drawing your own objects

Last edited: July 14 2013

Chinese version -> 中文

Drawing your own objects


From the previous topic, it's obvious that using debug draw will not make for a very visually appealing game. Usually we would like to use our own method for drawing objects in the scene, and access the physics information from Box2D so that we know where to draw them. In this topic, we'll set up a class to use as a game entity, and then look at how to keep it in the right place. We will do this by storing a pointer to a Box2D body in the game entity.

For now all the entity class will do is render itself, but later we will expand it to demonstrate other topics. The focus of these tutorials is not on rendering, so we'll just draw a basic smiley face on a circle shape to confirm that it moves and rotates correctly.

For this topic let's set up an empty fenced area to start with.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    FooTest() {        
      //a static body
      b2BodyDef myBodyDef;
      myBodyDef.type = b2_staticBody;
      myBodyDef.position.Set(0, 0);
      b2Body* staticBody = m_world->CreateBody(&myBodyDef);
  
      //shape definition
      b2PolygonShape polygonShape;
    
      //fixture definition
      b2FixtureDef myFixtureDef;
      myFixtureDef.shape = &polygonShape;
      
      //add four walls to the static body
      polygonShape.SetAsBox( 20, 1, b2Vec2(0, 0), 0);//ground
      staticBody->CreateFixture(&myFixtureDef);
      polygonShape.SetAsBox( 20, 1, b2Vec2(0, 40), 0);//ceiling
      staticBody->CreateFixture(&myFixtureDef);
      polygonShape.SetAsBox( 1, 20, b2Vec2(-20, 20), 0);//left wall
      staticBody->CreateFixture(&myFixtureDef);
      polygonShape.SetAsBox( 1, 20, b2Vec2(20, 20), 0);//right wall
      staticBody->CreateFixture(&myFixtureDef);
    }
Drawing objects Let's call our game entity class Ball since it will be round and bouncing:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  //outside and before the FooTest class
  class Ball {
  public:
    //class member variables
    b2Body* m_body;
    float m_radius;
  
  public:
    Ball(b2World* world, float radius) {
      m_body = NULL;
      m_radius = radius;
    }
    ~Ball() {}
  
  };
  
  //FooTest class member variable
  std::vector<Ball*> balls;
(To get the last part to compile, you may have to #include <vector> at the top of the file.) Notice that now instead of storing references to Box2D bodies directly, we let each game entity look after that and we store a reference to the game entities instead. Add a render function to the Ball class to draw a nice smiley face. The important thing to note here is that we are drawing the face so that it is centered on the point (0,0) and it will not be rotated. The radius is taken to be 1 for the default rendering.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  //Ball::render
    void render() {
      glColor3f(1,1,1);//white
  
      //nose and eyes
      glPointSize(4);
      glBegin(GL_POINTS);
      glVertex2f( 0, 0 );
      glVertex2f(-0.5, 0.5 );
      glVertex2f( 0.5, 0.5 );
      glEnd();
      
      //mouth
      glBegin(GL_LINES);
      glVertex2f(-0.5,  -0.5 );
      glVertex2f(-0.16, -0.6 );
      glVertex2f( 0.16, -0.6 );
      glVertex2f( 0.5,  -0.5 );
      glEnd();
  
      //circle outline
      glBegin(GL_LINE_LOOP);
      for (float a = 0; a < 360 * DEGTORAD; a += 30 * DEGTORAD) 
        glVertex2f( sinf(a), cosf(a) );
      glEnd();
    }
Just a couple more things to do. In the FooTest constructor, after the fenced area is set up, add a Ball entity into the scene (this should really be deleted in the class destructor if you wish to add that too).
1
2
3
  //add ball entity to scene in constructor
  Ball* ball = new Ball(m_world, 1);
  balls.push_back( ball );
And finally, to actually draw the ball entities we'll need to add to the Step() function. If you put this after the call to Test::Step(), the ball will be drawn on top of the existing debug draw data.
1
2
3
  //in Step() function, after Test::Step()
  for (int i = 0; i < balls.size(); i++)
    balls[i]->render();
Drawing objects Now we have one ball entity, but it's being drawn at the default location of (0,0) and we didn't need a physics engine to do that huh? Let's add to the constructor of the Ball class to set up a circle shape body for the ball. Now it becomes apparent why the Ball constructor requires the b2World pointer:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    // Ball class constructor
    Ball(b2World* world, float radius) {
      m_body = NULL;
      m_radius = radius;
  
      //set up dynamic body, store in class variable
      b2BodyDef myBodyDef;
      myBodyDef.type = b2_dynamicBody;
      myBodyDef.position.Set(0, 20);    
      m_body = world->CreateBody(&myBodyDef);
  
      //add circle fixture
      b2CircleShape circleShape;
      circleShape.m_p.Set(0, 0); 
      circleShape.m_radius = m_radius; //use class variable
      b2FixtureDef myFixtureDef;
      myFixtureDef.shape = &circleShape;
      myFixtureDef.density = 1; 
      m_body->CreateFixture(&myFixtureDef); 
    }
Drawing objects Ok, there is a physics body in the scene but our face is not being drawn at the body's position. To do this, let's add another function to the Ball class to set up the OpenGL transform before rendering. Depending on what rendering API you use, there may be better methods for doing this.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    //in Ball class
    void renderAtBodyPosition() {
          
      //get current position from Box2D
      b2Vec2 pos = m_body->GetPosition();
      float angle = m_body->GetAngle();
  
      //call normal render at different position/rotation
      glPushMatrix();
      glTranslatef( pos.x, pos.y, 0 );
      glRotatef( angle * RADTODEG, 0, 0, 1 );//OpenGL uses degrees here
      render();//normal render at (0,0)
      glPopMatrix();
    }
Don't forget to change the rendering call in the Step() function to use the new renderAtBodyPosition. Now even if you turn the debug draw display off (uncheck the 'Shapes' checkbox on in the control panel) your own rendering code still draws the ball at the correct position and rotation. Drawing objects Just for fun we could use some more feedback from the physics engine to change the color of the ball at different speeds. For example, set the color like this:
1
2
3
4
5
  //in Ball::render
  b2Vec2 vel = m_body->GetLinearVelocity();
  float red = vel.Length() / 20.0;
  red = min( 1, red );
  glColor3f(red,0.5,0.5);
How about adding a whole bunch of balls to the scene - just change the FooTest constructor to loop a few times:
1
2
3
4
5
  //in FooTest constructor
  for (int i = 0; i < 20; i++) {
    Ball* ball = new Ball(m_world, 1);
    balls.push_back( ball );
  }
Drawing objects We could make the balls random sizes too, since variable sizes were considered when we set up the Ball class:
1
2
3
4
5
  for (int i = 0; i < 20; i++) {
    float radius = 1 + 2 * (rand()/(float)RAND_MAX); //random between 1 - 3
    Ball* ball = new Ball(m_world, radius);
    balls.push_back( ball );
  }
Drawing objects Uh-oh... somethings not right here, the balls don't collide properly anymore. Or at least that's what you could be thinking if this was a more complex scene, and you had confidently thrown away the debug draw code in favour of your eye-popping fancy graphics. Since we have the debug draw ready to hand, it only takes a second to see what's really going on in the physics engine: Drawing objects Okay so that was a bit contrived, but I just wanted to illustrate the usefulness of having a debug draw implementation available throughout your development progress. Finally, to fix the size problem we just need to scale the rendering code:
1
2
3
4
5
6
7
  //inside Ball::renderAtBodyPosition
  glPushMatrix();
  glTranslatef( pos.x, pos.y, 0 );
  glRotatef( angle * RADTODEG, 0, 0, 1 );
  glScalef( m_radius, m_radius, 1 ); //add this to correct size
  render();
  glPopMatrix();
Drawing objects


Next: User data