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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Bullet Physics Tutorial: Getting Started
60 mins
- Getting Started with Bullet
- Downloading the Bullet Source Code
- Installing the Build Tool
- Building Bullet
- Adding Bullet Libraries to an Xcode Project
- Fixing Architecture Configuration Errors
- Physics Worlds
- Creating the World
- Rigid Bodies
- Creating the PNode Class
- Creating a Body
- Initializing the PNode Class
- Making the Ball Fall
- Modifying a Node to Use Physics
- Adding the Ball to the Physics World
- Syncing a Physics Body with a Drawn Object: Position
- Stopping the Ball with the Paddle
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.
- You call the parent
init
and simply pass through all the properties not related to physics. - You save the
tag
value in a property. - You create the shape object from the passed vertices array using the function you created above.
- 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:
- You import PNode.h instead of RWNode.h, since you’re going to inherit from
PNode
. - 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:
- You set the ball’s
mass
to 1.0. You can play with this value to see what it does. - The ball is spherical. Since a sphere is a convex shape, you set the
convex
flag toYES
. - 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.
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:
- It calls the parent method, which enumerates children and calls their
updateWithDelta:
. - 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. - 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!
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
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.