Bullet Physics Tutorial: Getting Started

Learn how to make a simple OpenGL ES Arkanoid game have 3D physics and collision detection in this Bullet Physics tutorial! By Kirill Muzykov.

Leave a rating/review
Save for later
Share
You are currently viewing page 4 of 5 of this article. Click here to view the first page.

Initializing the PNode Class

Now that you have all the methods to construct a body, it’s time to write the init method. Add it right after the instance variable declarations in PNode.mm:

- (instancetype)initWithName:(char *)name
                        mass:(float)mass
                      convex:(BOOL)convex
                         tag:(int)tag
                      shader:(GLKBaseEffect *)shader
                    vertices:(Vertex *)vertices
                 vertexCount:(unsigned int)vertexCount
                 textureName:(NSString *)textureName
               specularColor:(GLKVector4)specularColor
                diffuseColor:(GLKVector4)diffuseColor
                   shininess:(float)shininess
{
    //1
    if (self = [super initWithName:name shader:shader vertices:vertices vertexCount:vertexCount textureName:textureName specularColor:specularColor diffuseColor:diffuseColor shininess:shininess])
    {
        
        //2
        self.tag = tag;
        
        //3
        [self createShapeWithVertices:vertices count:vertexCount isConvex:convex];
        
        //4
        [self createBodyWithMass:mass];
    }
    return self;
}

This method is fairly simple, since you’re calling methods you created earlier.

  1. You call the parent init and simply pass through all the properties not related to physics.
  2. You save the tag value in a property.
  3. You create the shape object from the passed vertices array using the function you created above.
  4. Finally, you create a body using the specified mass.

Now you only need to add a dealloc to clean up your C++ resources and you’ll be done with this section.

Add the dealloc method at the end of PNode.mm:

- (void)dealloc
{
    if (_body)
    {
        delete _body->getMotionState();
        delete _body;
    }
    
    delete _shape;
}

Build and Run the project. You still won’t see anything different, but don’t worry – you’re going to change that in the next section of the tutorial.

Making the Ball Fall

It’s time to see Bullet physics in action!

Before you modify the ball node to use physics, you need to create a tag for it, since PNode’s init requires one.

Switch to PNode.h and add the following #defines right before the @interface line:

#define kBallTag    1
#define kBrickTag   2
#define kPaddleTag  3
#define kBorderTag  4

This way, you’ve defined tags for all the game objects.

Modifying a Node to Use Physics

Go to RWBall.h and change it to the following.

//1
#import "PNode.h"

//2
@interface RWBall : PNode

- (id)initWithShader:(GLKBaseEffect *)shader;

@end

You’ve made just two simple changes:

  1. You import PNode.h instead of RWNode.h, since you’re going to inherit from PNode.
  2. You change the parent class to PNode.

Now rename RWBall.m to RWBall.mm.

Switch to RWBall.mm and replace initWithShader: with the following:

- (id)initWithShader:(GLKBaseEffect *)shader {
    if ((self = [super initWithName:"Ball"
                               mass: 1.0f       //1
                             convex: YES        //2
                                tag: kBallTag   //3
                             shader:shader
                           vertices:(Vertex *)Ball_Sphere_ball_Vertices
                        vertexCount:sizeof(Ball_Sphere_ball_Vertices)/sizeof(Ball_Sphere_ball_Vertices[0])
                        textureName:@"ball.png"
                      specularColor:Ball_Sphere_ball_specular
                       diffuseColor:Ball_Sphere_ball_diffuse
                          shininess:Ball_Sphere_ball_shininess])) {
        self.width = 1.0;
        self.height = 1.0;
    }
    return self;
}

Now the RWBall class is calling PNode’s init instead of RWNode’s. All you have to do is to specify additional properties required for physics objects:

  1. You set the ball’s mass to 1.0. You can play with this value to see what it does.
  2. The ball is spherical. Since a sphere is a convex shape, you set the convex flag to YES.
  3. You mark the ball with its corresponding tag.

Build the project now and you’ll see you get several strange errors in ball.h, a file with the vertices of the ball model.

Errors in ball.h

You’re getting this error because you’ve changed the extension to RWBall.mm and using some C++, which has different rules regarding global variable initialization.

But there’s an easy fix. Switch to ball.h and add the extern keyword before each variable declaration. You should end up with this:

extern const GLKVector4 Ball_Sphere_ball_ambient;
extern const GLKVector4 Ball_Sphere_ball_diffuse;
extern const GLKVector4 Ball_Sphere_ball_specular;
extern const float Ball_Sphere_ball_shininess;
extern const Vertex Ball_Sphere_ball_Vertices[420];

The extern keyword simply notifies the compiler that, although the variable is defined here, the variable’s value is initialized in ball.m.

Build the project. You should get no errors.

You may see a couple more warnings regarding Conversion from string literal to ‘char *’ is deprecated. Those are totally okay to ignore for now (or you can fix them the way I mentioned earlier in this tutorial).

Adding the Ball to the Physics World

The next step is to add the ball to your physics world.

Switch to RWGameScene.mm and scroll to the place where you create the ball node. Replace these lines:

        _ball = [[RWBall alloc] initWithShader:shader];
        _ball.position = GLKVector3Make(_gameArea.width/2, _gameArea.height * 0.1, 0);
        _ball.diffuseColor = GLKVector4Make(0.5, 0.9, 0, 1);
        _ballVelocityX = 10;
        _ballVelocityY = 10;
        [self.children addObject:_ball];

With the following:

        _ball = [[RWBall alloc] initWithShader:shader];
        _ball.position = GLKVector3Make(_gameArea.width/2, _gameArea.height * 0.1, 0);
        _ball.diffuseColor = GLKVector4Make(0.5, 0.9, 0, 1);
        [self.children addObject:_ball];
        _world->addRigidBody(_ball.body); //Adding ball to world

Notice that final line. When you create the ball object, it calls PNode’s init and at the end of initialization, you already have a physics body that you can access using the body property you created earlier in PNode.

You simply need to add the body to the world. That’s what _world->addRigidBody(_ball.body); does.

Next up is stepping the world. Remember, this is what gives the physics engine time to process the physics simulation and update the positions of each physics body accordingly.

Scroll to updateWithDelta: and replace it with the following:

- (void)updateWithDelta:(GLfloat)aDelta {
    //1
    [super updateWithDelta:aDelta];
    
    //2
    _world->stepSimulation(aDelta);
    
    //3
    NSLog(@"Ball height: %f", _ball.body->getWorldTransform().getOrigin().getY());
}

The method got smaller, didn’t it? :] Well, it will grow a bit larger, but it will still remain smaller than before because the physics engine will do most of the work. Here’s what updateWithDelta: does now:

  1. It calls the parent method, which enumerates children and calls their updateWithDelta:.
  2. Remember what I described earlier? Since you can only update in updateWithDelta:, which is called at some rate, you need to pass to the world the amount of time elapsed since the last step, so that the world can simulate until that point. This exactly what _world->stepSimulation(aDelta); does.
  3. The code logs the ball’s y-position. You’ll see why later – it will be a surprise. :]

Build and run the project, and you’ll see the ball seems to be stuck in space!

BallStuck

But wait! Why is the ball not moving, even though you can see in the Log that its position changes?

2013-10-15 13:41:26.410 Breakout[44391:a0b] Ball height: 0.000000
2013-10-15 13:41:26.575 Breakout[44391:a0b] Ball height: -0.002778
2013-10-15 13:41:26.608 Breakout[44391:a0b] Ball height: -0.008333

2013-10-15 13:41:30.941 Breakout[44391:a0b] Ball height: -24.383337
2013-10-15 13:41:30.975 Breakout[44391:a0b] Ball height: -24.752781

Here is a very important thing to understand: Physics objects are not drawn objects!

There is a physics world where you create bodies and all the simulation happens, but changes to simulated objects are not automatically applied to drawn objects and vice-versa. When you move a drawn object, the related physics object doesn’t move automatically.

This means you have to write some code to synchronize the position of the physics object with the drawn ball. The easiest thing to do is to override the position property of RWNode in the PNode class.

Kirill Muzykov

Contributors

Kirill Muzykov

Author

Over 300 content creators. Join our team.