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
Syncing a Physics Body with a Drawn Object: Position
Switch to PNode.mm and add the following two methods right after dealloc
:
//1
-(void)setPosition:(GLKVector3)position
{
//2
[super setPosition:position];
//3
if (_body)
{
btTransform trans = _body->getWorldTransform();
trans.setOrigin(btVector3(position.x, position.y, position.z));
_body->setWorldTransform(trans);
}
}
//4
-(GLKVector3)position
{
if (_body)
{
//5
btTransform trans = _body->getWorldTransform();
return GLKVector3Make(trans.getOrigin().x(), trans.getOrigin().y(), trans.getOrigin().z());
}
else
{
//6
return [super position];
}
}
As you probably know, all that fancy @property
stuff in Objective-C is just a convenient way of creating getter and setter methods. You’re using that knowledge to override the position
property of RWNode
with custom code.
A transform is a translation [vector] plus a rotation [quaternion]. A “transform” alone is usually sufficient to describe everything you need to use to map a physics body [in Bullet] into your graphical rendering system.
Since you don’t want to change the rotation yet, you need to get the current transform of the body, change the position part and send it back.
- Start with the setter method. It will allow you to change the position of the physics body when you set the position for a node.
- Call a
super
implementation, in case for some reason you don’t have the body yet. - In case you do have a physics body, you need to apply a transform to the body. In Bullet, you don’t work with
position
alone. Instead you work withbtTransform
, which holds both the position and rotation of the object. Here is the definition of a transform from the Bullet wiki:A transform is a translation [vector] plus a rotation [quaternion]. A “transform” alone is usually sufficient to describe everything you need to use to map a physics body [in Bullet] into your graphical rendering system.
Since you don’t want to change the rotation yet, you need to get the current transform of the body, change the position part and send it back.
- It’s time to change the getter method by implementing backwards synchronization. This means when you get the
position
property in order to understand where you need to draw the object, you get the position of the physics body and draw the object right at that location. - Getting the position is easy. You get the transform of the body and take only the position part.
- In case for some reason
PNode
doesn’t have the body, just use the default property implementation.
A transform is a translation [vector] plus a rotation [quaternion]. A “transform” alone is usually sufficient to describe everything you need to use to map a physics body [in Bullet] into your graphical rendering system.
Build and Run the project. Your ball should fall down through the paddle.
w00t physics in action!
Stopping the Ball with the Paddle
The ball shouldn’t fall through the paddle, should it? You’re not rewriting the rules of the game, after all!
To fix this, you have to make almost the same changes to the paddle as you did to the ball. Let’s see if you can make these changes yourself. Take a peek in the spoiler sections for answers if you’re lost.
Switch to RWPaddle.h and change the parent class to PNode:
[spoiler title=”RWPaddle.h after changes”]
//1
#import "PNode.h"
//2
@interface RWPaddle : PNode
- (id)initWithShader:(GLKBaseEffect *)shader;
@end
It’s the same as with RWBall.h.
Limiting Movement with a Border
Launching the Ball
Adding Bricks to the Physics World
Winning and Losing with Physics
Maintaining a Constant Velocity
Syncing a Physics Body with a Drawn Object: Rotation
Where to Go from Here?
Then rename RWPaddle.m to RWPaddle.mm.
Switch to RWPaddle.mm and change the call to super initWithShader:
to use PNode
’s init
method with physics parameters.
Set the new parameters as follows and leave the existing parameters untouched:
A mass of zero means that forces or collisions with other objects do not affect the object. It can be static or, as in this case, kinematic (since you move the paddle using touches).
The paddle is also a convex object.
You’re just marking it with the special tag you prepared earlier.
[spoiler title=”RWPaddle.mm initWithShader:”]
[/spoiler]
Finally, switch to paddle.h and fix the variable declarations by adding the extern
keyword at the start of each line with variable declarations.
[spoiler title=”paddle.h – fixed version”]
[/spoiler]
I hope you’ve had no issues and haven’t needed to look at the spoilers. :]
There is one final thing missing: Adding the paddle body to the world.
Switch to RWGameScene.mm and find this line:
Add the following line right below it:
Build and Run the project. You should see the ball bouncing nicely on the paddle.
Bouncing the ball on the paddle is fun, but your goal is a bit different. Let’s get one step closer to it by adding the border that will limit the ball’s movement when you launch it in the next section.
The changes you need to make here are once again familiar: converting the RWBorder
to use PNode
instead of RWNode
. I believe you can do them yourself.
Switch to RWBorder.h and change the parent class of RWBorder
to PNode
:
[spoiler title=”RWBorder.h after changes”]
[/spoiler]
Rename RWBorder.m to RWBorder.mm.
Switch to RWBorder.mm and change the call to super initWithShader:
to use PNode
’s version of this method.
Set additional parameters as follows, leaving the existing parameters untouched:
Border is a static body in Bullet terminology, since it has zero mass, and this means forces and collisions don’t move it. In addition, you won’t move it from the code.
Note that the border has a concave rather than convex shape, and thus you have to pass NO
to PNode
’s init
so that it can create the border’s shape another way.
Use the border tag.
[spoiler title=”RWBorder.mm initWithShader:”]
[/spoiler]
Fix the variable declarations in border.h by adding the extern
keyword at the start of each line with variable declarations.
[spoiler title=”border.h – fixed version”]
[/spoiler]
To add the border to the world, switch to RWGameScene.mm and find this line:
Add the following line right below it:
This will add the border’s body to the world.
Build and Run the project. You will still see the ball bouncing on the paddle, but you will change that very soon!
Right now the ball’s bouncing is kind of wimpy. It’s time to man up and launch him into the sky!
Well, in manner of speaking. I hope it doesn’t actually launch into the sky because that would mean the border you added in previous section doesn’t work. :]
Switch to RWGameScene.mm and find this line:
Add the following line of code right below it:
Here you set the ball’s velocity. You set the x- and y-components of the velocity vector because you want to set the ball moving along both directions – diagonally, since you give x and y the same values.
There’s one more thing. Currently you have gravity enabled, but in the original Breakout game, there is none. You want the ball to keep moving in the direction it’s heading, not be slowed down by the effect of gravity, so you need to disable the gravity.
Switch to RWGameScene.mm and scroll to the bottom of initPhysics
.
Replace the line that sets the gravity:
With this:
Build and run the game. The ball should launch to the top and right, bounce off the border and go through the bricks. Now that’s progress! As for the ghostly bricks, you’ll fix them in a minute.
Bricks are the ball’s natural enemies. Wait… who turned on Discovery channel? :]
Converting bricks to physical nodes should be straightforward – it’s the same process you’ve already completed with the ball, paddle and border.
Switch to RWBrick.h and change its parent class to PNode
. Don’t forget to import PNode.h.
[spoiler title=”RWBrick.h after changes”]
[/spoiler]
Then rename RWBrick.m to RWBrick.mm.
Switch to RWBrick.mm and change initWithShader:
to use PNode
’s init
method. Try to guess the correct values for the following brick properties (if in doubt, check the spoiler):
[spoiler title=”RWBrick.mm initWithShader:”]
[/spoiler]
Fix the extern
-related issue in brick.h.
[spoiler title=”brick.h – fixed version:”]
[/spoiler]
And add all brick bodies to the world.
Switch to RWGameScene.mm and find the line where bricks are added to an array – this one:
Add the following line right below it:
Build and Run the project. You have all the physics bodies in place and the ball interacts with all of them.
There is only one issue: You cannot win (since the bricks no longer get destroyed on collision), and although the ball falls down, you can’t lose, either!
Maybe someone would settle for this draw situation, but you won’t!
Losing is easy, so let’s start with that. :]
Switch to RWGameScene.mm and find updateWithDelta
.
Add the following code to the bottom of the method:
This code is pretty simple. You check if the ball is below the zero line and switch to the Game Over scene if it is.
Build and Run the project. Let the ball fall and you should see the Game Over scene.
After the sad experience of losing, you must get revenge on those bricks and win the game!
Switch to RWGameScene.mm and import PNode.h at the top of the file, right after all other imports and includes:
Scroll down to updateWithDelta:
and add the following code to the end of the method, right after the code block where you check if the player lost:
As advised in the official Bullet wiki here:
The best way to determine if collisions happened between existing objects in the world is to iterate over all contact manifolds.
This is exactly what are you going to do. Here’s your plan:
Note: A contact manifold is a cache that contains all contact points between pairs of collision objects.
This seems tricky, at least to me. The existence of a manifold holding a pair of bodies doesn't mean they are in contact right now. This is why you should check the number of contacts.
In this line, you've saved a reference to the PNode
object in the body object property and here you're going to get it back to be able to check the tag
property.
Add the brick destruction method right below updateWithDelta
:
This method does three simple things:
Build and run the game. It should now be fully playable.
And yet, as you’ll notice, the gameplay experience is not quite as it was in the original. For one thing, the ball begins to slow down after several collisions. This can be due to friction or some slight variance with the physics calculations. Here is the fix for that.
Switch to RWGameScene.mm and add an instance variable called _desiredVelocity
.
Then go to initWithShader:
, also in RWGameScene.mm, and find this line:
Add the following line right below it:
This way, you save the magnitude of the vector without the direction, since the direction changes as the ball bounces off of different objects.
Scroll down to updateWithDelta:
and add the following code at the end of the method:
Now you’re using the saved magnitude to keep the ball’s velocity constant.
It is also time to remove the NSLog
from the method. Do that now.
Here is the complete version of updateWithDelta:
, in case you want to check if everything is correct.
[spoiler title="RWGameScene.mm final updateWithDelta:"]
[/spoiler]
Build and run, and now your ball should move at a much better (constant) speed!
I know it looks like the bricks are rotating as they were in the initial project, but it’s a lie! You are rotating drawable bricks, but the physical bricks are not rotating, and that is why the ball always bounces back the same way.
You're going to fix this. The easiest way to do it is to override the rotation properties (rotationX
, rotationY
, rotationZ
) of the PNode
class, just as you did with the position property, and keep them in sync with the physics body.
Disclaimer: You may remember that Bullet uses quaternions to represent rotation, while the original project uses Euler angles. In this tutorial, you're going to write some quick code to convert from Euler to quaternions and back. In a real-world project, you should stick with one or the other and use either quaternions or Euler angles everywhere in the code.
Switch to PNode.mm and add the following methods somewhere near the end of the file:
This requires an explanation:
Now it’s time to implement the same for the y- and z-rotations. Simply paste the following code below the x-rotation property implementation in PNode.mm:
Build and run the game.
Ah, that’s better. The ball now bounces more naturally off of the bricks. Of course it doesn’t look 100% realistic since you've restricted the ball’s movement to the (x,y) plane but it’s still much better.
You might also notice that the ball now spins. Before the physics body spun due to fiction, but the visual representation didn't. Now that you've overridden the rotation methods, the visual representation now matches!
Here is the final project with all the source code from this Bullet physics tutorial.
Congratulations, you have successfully integrated the Bullet physics engine into a 3D game. I hope this tutorial inspires you to continue learning and create your own 3D games using Bullet.
If you have any questions or comments, please post them below!
- Import PNode.h instead of RWNode.h.
- Change the parent class to
PNode
. -
mass:0.0f
A mass of zero means that forces or collisions with other objects do not affect the object. It can be static or, as in this case, kinematic (since you move the paddle using touches).
-
convex:YES
The paddle is also a convex object.
-
tag:kPaddleTag
You’re just marking it with the special tag you prepared earlier.
-
mass:0.0f
Border is a static body in Bullet terminology, since it has zero mass, and this means forces and collisions don’t move it. In addition, you won’t move it from the code.
-
convex:NO
Note that the border has a concave rather than convex shape, and thus you have to pass
NO
toPNode
’sinit
so that it can create the border’s shape another way. -
tag:kBorderTag
Use the border tag.
- The brick is a kinematic object. It is not affected by forces or collisions, but you’ll rotate it in the code.
- The brick has a convex shape.
-
The brick’s tag is
kBrickTag
. It’s very important to set the correct tag for the brick, so double-check this. -
Enumerate all manifolds.
Note: A contact manifold is a cache that contains all contact points between pairs of collision objects.
- Take each manifold object from the internal manifolds array by index.
-
Get the number of contacts and check that there is at least one contact between the pair of bodies.
This seems tricky, at least to me. The existence of a manifold holding a pair of bodies doesn't mean they are in contact right now. This is why you should check the number of contacts.
- In this game, only the ball collides with things, so at this point you know for sure that the ball just hit something and thus you need to play the corresponding sound effect.
- Next you need to understand which object the ball hit. Get the collision object from the manifold.
-
Remember this line from
PNode
’sinit
method?_body->setUserPointer((__bridge void*)self);
In this line, you've saved a reference to the
PNode
object in the body object property and here you're going to get it back to be able to check thetag
property. - & 8. Check if any of the objects is a brick. That would mean that the ball hit the brick, since the ball is the only moving object. In that case, you need to destroy the brick. The method to destroy the brick is just below.
- It removes the brick node from the scene and the bricks array.
- It removes the brick body from the world.
- Finally, it checks for a victory. If there are no bricks left, then the player just won the game! Yee-haw!
- Start with the setter method. You need to override it to allow rotating both drawable bodies and physics bodies when you set the rotation in the code.
- Save the value in case the node doesn't have a body for some reason.
- Get the current transform. Since you don't want to change the position, you will only modify the rotation part of the transform and send it back.
- Here’s the tricky part. You need to rotate the existing quaternion to the delta angle to achieve the required rotation. To do so, you calculate the angle of difference and rotate the current quaternion along the x-axis (
btVector3(1,0,0)
) by multiplying the new quaternion by the existing one. Please note that the order of multiplication matters! - Send back the updated transform.
- It’s time for a getter method. You need to override the property's getter to get the rotation from the physics object when rendering the node.
- Here you take the body’s current rotation quaternion and convert it to a rotation matrix.
btQuaternion
doesn't have methods to get Euler angles (at least I couldn't find any) butbtMatrix3x3
does. So you're being a little lazy and converting tobtMatrix3x3
to get Euler angles. - In cases where the node doesn't have a body, you use the default implementation of the property.
-
[/spoiler]
Then rename RWPaddle.m to RWPaddle.mm.
Switch to RWPaddle.mm and change the call to super initWithShader:
to use PNode
’s init
method with physics parameters.
Set the new parameters as follows and leave the existing parameters untouched:
[spoiler title=”RWPaddle.mm initWithShader:”]
- (id)initWithShader:(GLKBaseEffect *)shader {
if ((self = [super initWithName:"Paddle"
mass:0.0f //1
convex:YES //2
tag:kPaddleTag //3
shader:shader
vertices:(Vertex *)Paddle_Cube_paddle_Vertices
vertexCount:sizeof(Paddle_Cube_paddle_Vertices)/sizeof(Paddle_Cube_paddle_Vertices[0])
textureName:@"paddle.png"
specularColor:Paddle_Cube_paddle_specular
diffuseColor:Paddle_Cube_paddle_diffuse
shininess:Paddle_Cube_paddle_shininess])) {
self.width = 5.0;
self.height = 1.0;
}
return self;
}
[/spoiler]
Finally, switch to paddle.h and fix the variable declarations by adding the extern
keyword at the start of each line with variable declarations.
[spoiler title=”paddle.h – fixed version”]
extern const GLKVector4 Paddle_Cube_paddle_ambient;
extern const GLKVector4 Paddle_Cube_paddle_diffuse;
extern const GLKVector4 Paddle_Cube_paddle_specular;
extern const float Paddle_Cube_paddle_shininess;
extern const Vertex Paddle_Cube_paddle_Vertices[36];
[/spoiler]
I hope you’ve had no issues and haven’t needed to look at the spoilers. :]
There is one final thing missing: Adding the paddle body to the world.
Switch to RWGameScene.mm and find this line:
[self.children addObject:_paddle];
Add the following line right below it:
_world->addRigidBody(_paddle.body);
Build and Run the project. You should see the ball bouncing nicely on the paddle.
Limiting Movement with a Border
Bouncing the ball on the paddle is fun, but your goal is a bit different. Let’s get one step closer to it by adding the border that will limit the ball’s movement when you launch it in the next section.
The changes you need to make here are once again familiar: converting the RWBorder
to use PNode
instead of RWNode
. I believe you can do them yourself.
Switch to RWBorder.h and change the parent class of RWBorder
to PNode
:
[spoiler title=”RWBorder.h after changes”]
#import "PNode.h"
@interface RWBorder : PNode
- (id)initWithShader:(GLKBaseEffect *)shader;
@end
[/spoiler]
Rename RWBorder.m to RWBorder.mm.
Switch to RWBorder.mm and change the call to super initWithShader:
to use PNode
’s version of this method.
Set additional parameters as follows, leaving the existing parameters untouched:
[spoiler title=”RWBorder.mm initWithShader:”]
- (id)initWithShader:(GLKBaseEffect *)shader {
if ((self = [super initWithName:"Border"
mass:0.0f //1
convex:NO //2
tag:kBorderTag //3
shader:shader
vertices:(Vertex *)Border_Cube_Border_Vertices
vertexCount:sizeof(Border_Cube_Border_Vertices)/sizeof(Border_Cube_Border_Vertices[0])
textureName:@"border.png"
specularColor:Border_Cube_Border_specular
diffuseColor:Border_Cube_Border_diffuse
shininess:Border_Cube_Border_shininess])) {
self.width = 27.0;
self.height = 48.0;
}
return self;
}
[/spoiler]
Fix the variable declarations in border.h by adding the extern
keyword at the start of each line with variable declarations.
[spoiler title=”border.h – fixed version”]
extern const GLKVector4 Border_Cube_Border_ambient;
extern const GLKVector4 Border_Cube_Border_diffuse;
extern const GLKVector4 Border_Cube_Border_specular;
extern const float Border_Cube_Border_shininess;
extern const Vertex Border_Cube_Border_Vertices[132];
[/spoiler]
To add the border to the world, switch to RWGameScene.mm and find this line:
[self.children addObject:_border];
Add the following line right below it:
_world->addRigidBody(_border.body);
This will add the border’s body to the world.
Build and Run the project. You will still see the ball bouncing on the paddle, but you will change that very soon!
Launching the Ball
Right now the ball’s bouncing is kind of wimpy. It’s time to man up and launch him into the sky!
Well, in manner of speaking. I hope it doesn’t actually launch into the sky because that would mean the border you added in previous section doesn’t work. :]
Switch to RWGameScene.mm and find this line:
_world->addRigidBody(_ball.body);
Add the following line of code right below it:
_ball.body->setLinearVelocity(btVector3(15,15,0));
Here you set the ball’s velocity. You set the x- and y-components of the velocity vector because you want to set the ball moving along both directions – diagonally, since you give x and y the same values.
There’s one more thing. Currently you have gravity enabled, but in the original Breakout game, there is none. You want the ball to keep moving in the direction it’s heading, not be slowed down by the effect of gravity, so you need to disable the gravity.
Switch to RWGameScene.mm and scroll to the bottom of initPhysics
.
Replace the line that sets the gravity:
_world->setGravity(btVector3(0, -9.8, 0));
With this:
_world->setGravity(btVector3(0, 0, 0));
Build and run the game. The ball should launch to the top and right, bounce off the border and go through the bricks. Now that’s progress! As for the ghostly bricks, you’ll fix them in a minute.
Adding Bricks to the Physics World
Bricks are the ball’s natural enemies. Wait… who turned on Discovery channel? :]
Converting bricks to physical nodes should be straightforward – it’s the same process you’ve already completed with the ball, paddle and border.
Switch to RWBrick.h and change its parent class to PNode
. Don’t forget to import PNode.h.
[spoiler title=”RWBrick.h after changes”]
#import "PNode.h"
@interface RWBrick : PNode
- (id)initWithShader:(GLKBaseEffect *)shader;
@end
[/spoiler]
Then rename RWBrick.m to RWBrick.mm.
Switch to RWBrick.mm and change initWithShader:
to use PNode
’s init
method. Try to guess the correct values for the following brick properties (if in doubt, check the spoiler):
[spoiler title=”RWBrick.mm initWithShader:”]
- (id)initWithShader:(GLKBaseEffect *)shader {
if ((self = [super initWithName:"Brick"
mass:0.0f //1
convex:YES //2
tag:kBrickTag //3
shader:shader
vertices:(Vertex *)Cube_brick_Vertices
vertexCount:sizeof(Cube_brick_Vertices)/sizeof(Cube_brick_Vertices[0])
textureName:@"brick.png"
specularColor:Cube_brick_specular
diffuseColor:Cube_brick_diffuse
shininess:Cube_brick_shininess])) {
self.width = 2.0;
self.height = 1.0;
}
return self;
}
[/spoiler]
Fix the extern
-related issue in brick.h.
[spoiler title=”brick.h – fixed version:”]
extern const GLKVector4 Cube_brick_ambient;
extern const GLKVector4 Cube_brick_diffuse;
extern const GLKVector4 Cube_brick_specular;
extern const float Cube_brick_shininess;
extern const Vertex Cube_brick_Vertices[36];
[/spoiler]
And add all brick bodies to the world.
Switch to RWGameScene.mm and find the line where bricks are added to an array – this one:
[_bricks addObject:brick];
Add the following line right below it:
_world->addRigidBody(brick.body);
Build and Run the project. You have all the physics bodies in place and the ball interacts with all of them.
There is only one issue: You cannot win (since the bricks no longer get destroyed on collision), and although the ball falls down, you can’t lose, either!
Maybe someone would settle for this draw situation, but you won’t!
Winning and Losing with Physics
Losing is easy, so let’s start with that. :]
Switch to RWGameScene.mm and find updateWithDelta
.
Add the following code to the bottom of the method:
if (_ball.position.y < 0)
{
[RWDirector sharedInstance].scene = [[RWGameOverScene alloc] initWithShader:self.shader win:NO];
return;
}
This code is pretty simple. You check if the ball is below the zero line and switch to the Game Over scene if it is.
Build and Run the project. Let the ball fall and you should see the Game Over scene.
After the sad experience of losing, you must get revenge on those bricks and win the game!
Switch to RWGameScene.mm and import PNode.h at the top of the file, right after all other imports and includes:
#import "PNode.h"
Scroll down to updateWithDelta:
and add the following code to the end of the method, right after the code block where you check if the player lost:
//1
int numManifolds = _world->getDispatcher()->getNumManifolds();
for (int i=0;i<numManifolds;i++)
{
//2
btPersistentManifold* contactManifold = _world->getDispatcher()->getManifoldByIndexInternal(i);
//3
int numContacts = contactManifold->getNumContacts();
if (numContacts > 0)
{
//4
[[RWDirector sharedInstance] playPopEffect];
//5
const btCollisionObject* obA = contactManifold->getBody0();
const btCollisionObject* obB = contactManifold->getBody1();
//6
PNode* pnA = (__bridge PNode*)obA->getUserPointer();
PNode* pnB = (__bridge PNode*)obB->getUserPointer();
//7
if (pnA.tag == kBrickTag) {
[self destroyBrickAndCheckVictory:pnA];
}
//8
if (pnB.tag == kBrickTag){
[self destroyBrickAndCheckVictory:pnB];
}
}
}
As advised in the official Bullet wiki here:
The best way to determine if collisions happened between existing objects in the world is to iterate over all contact manifolds.
This is exactly what are you going to do. Here’s your plan:
Add the brick destruction method right below updateWithDelta
:
- (void)destroyBrickAndCheckVictory:(PNode*)brick
{
//1
[self.children removeObject:brick];
[_bricks removeObject:brick];
//2
_world->removeRigidBody(brick.body);
//3
if (_bricks.count == 0) {
[RWDirector sharedInstance].scene = [[RWGameOverScene alloc] initWithShader:self.shader win:YES];
}
}
This method does three simple things:
Build and run the game. It should now be fully playable.
Maintaining a Constant Velocity
And yet, as you’ll notice, the gameplay experience is not quite as it was in the original. For one thing, the ball begins to slow down after several collisions. This can be due to friction or some slight variance with the physics calculations. Here is the fix for that.
Switch to RWGameScene.mm and add an instance variable called _desiredVelocity
.
@implementation RWGameScene {
//...
btScalar _desiredVelocity;
}
Then go to initWithShader:
, also in RWGameScene.mm, and find this line:
_ball.body->setLinearVelocity(btVector3(15,15,0));
Add the following line right below it:
_desiredVelocity = _ball.body->getLinearVelocity().length();
This way, you save the magnitude of the vector without the direction, since the direction changes as the ball bounces off of different objects.
Scroll down to updateWithDelta:
and add the following code at the end of the method:
btVector3 currentVelocityDirection =_ball.body->getLinearVelocity();
btScalar currentVelocty = currentVelocityDirection.length();
if (currentVelocty < _desiredVelocity)
{
currentVelocityDirection *= _desiredVelocity/currentVelocty;
_ball.body->setLinearVelocity(currentVelocityDirection);
}
Now you’re using the saved magnitude to keep the ball’s velocity constant.
It is also time to remove the NSLog
from the method. Do that now.
Here is the complete version of updateWithDelta:
, in case you want to check if everything is correct.
[spoiler title="RWGameScene.mm final updateWithDelta:"]
- (void)updateWithDelta:(GLfloat)aDelta {
[super updateWithDelta:aDelta];
_world->stepSimulation(aDelta);
if (_ball.position.y < 0)
{
[RWDirector sharedInstance].scene = [[RWGameOverScene alloc] initWithShader:self.shader win:NO];
return;
}
int numManifolds = _world->getDispatcher()->getNumManifolds();
for (int i=0;i<numManifolds;i++)
{
btPersistentManifold* contactManifold = _world->getDispatcher()->getManifoldByIndexInternal(i);
int numContacts = contactManifold->getNumContacts();
if (numContacts > 0)
{
[[RWDirector sharedInstance] playPopEffect];
const btCollisionObject* obA = contactManifold->getBody0();
const btCollisionObject* obB = contactManifold->getBody1();
PNode* pnA = (__bridge PNode*)obA->getUserPointer();
PNode* pnB = (__bridge PNode*)obB->getUserPointer();
if (pnA.tag == kBrickTag) {
[self destroyBrickAndCheckVictory:pnA];
}
if (pnB.tag == kBrickTag){
[self destroyBrickAndCheckVictory:pnB];
}
}
}
btVector3 currentVelocityDirection =_ball.body->getLinearVelocity();
btScalar currentVelocty = currentVelocityDirection.length();
if (currentVelocty < _desiredVelocity)
{
currentVelocityDirection *= _desiredVelocity/currentVelocty;
_ball.body->setLinearVelocity(currentVelocityDirection);
}
}
[/spoiler]
Build and run, and now your ball should move at a much better (constant) speed!
Syncing a Physics Body with a Drawn Object: Rotation
I know it looks like the bricks are rotating as they were in the initial project, but it’s a lie! You are rotating drawable bricks, but the physical bricks are not rotating, and that is why the ball always bounces back the same way.
You're going to fix this. The easiest way to do it is to override the rotation properties (rotationX
, rotationY
, rotationZ
) of the PNode
class, just as you did with the position property, and keep them in sync with the physics body.
Disclaimer: You may remember that Bullet uses quaternions to represent rotation, while the original project uses Euler angles. In this tutorial, you're going to write some quick code to convert from Euler to quaternions and back. In a real-world project, you should stick with one or the other and use either quaternions or Euler angles everywhere in the code.
Switch to PNode.mm and add the following methods somewhere near the end of the file:
//1
-(void)setRotationX:(float)rotationX
{
//2
[super setRotationX:rotationX];
if (_body)
{
//3
btTransform trans = _body->getWorldTransform();
btQuaternion rot = trans.getRotation();
//4
float angleDiff = rotationX - self.rotationX;
btQuaternion diffRot = btQuaternion(btVector3(1,0,0), angleDiff);
rot = diffRot * rot;
//5
trans.setRotation(rot);
_body->setWorldTransform(trans);
}
}
//6
-(float)rotationX
{
if (_body)
{
//7
btMatrix3x3 rotMatrix = btMatrix3x3(_body->getWorldTransform().getRotation());
float z,y,x;
rotMatrix.getEulerZYX(z,y,x);
return x;
}
//8
return [super rotationX];
}
This requires an explanation:
Now it’s time to implement the same for the y- and z-rotations. Simply paste the following code below the x-rotation property implementation in PNode.mm:
-(void)setRotationY:(float)rotationY
{
[super setRotationY:rotationY];
if (_body)
{
btTransform trans = _body->getWorldTransform();
btQuaternion rot = trans.getRotation();
float angleDiff = rotationY - self.rotationY;
btQuaternion diffRot = btQuaternion(btVector3(0,1,0), angleDiff);
rot = diffRot * rot;
trans.setRotation(rot);
_body->setWorldTransform(trans);
}
}
-(float)rotationY
{
if (_body)
{
btMatrix3x3 rotMatrix = btMatrix3x3(_body->getWorldTransform().getRotation());
float z,y,x;
rotMatrix.getEulerZYX(z,y,x);
return y;
}
return [super rotationY];
}
-(void)setRotationZ:(float)rotationZ
{
[super setRotationZ:rotationZ];
if (_body)
{
btTransform trans = _body->getWorldTransform();
btQuaternion rot = trans.getRotation();
float angleDiff = rotationZ - self.rotationZ;
btQuaternion diffRot = btQuaternion(btVector3(0,0,1), angleDiff);
rot = diffRot * rot;
trans.setRotation(rot);
_body->setWorldTransform(trans);
}
}
-(float)rotationZ
{
if (_body)
{
btMatrix3x3 rotMatrix = btMatrix3x3(_body->getWorldTransform().getRotation());
float z,y,x;
rotMatrix.getEulerZYX(z,y,x);
return z;
}
return [super rotationZ];
}
Build and run the game.
Ah, that’s better. The ball now bounces more naturally off of the bricks. Of course it doesn’t look 100% realistic since you've restricted the ball’s movement to the (x,y) plane but it’s still much better.
You might also notice that the ball now spins. Before the physics body spun due to fiction, but the visual representation didn't. Now that you've overridden the rotation methods, the visual representation now matches!
Where to Go from Here?
Here is the final project with all the source code from this Bullet physics tutorial.
Congratulations, you have successfully integrated the Bullet physics engine into a 3D game. I hope this tutorial inspires you to continue learning and create your own 3D games using Bullet.
If you have any questions or comments, please post them below!
-
[/spoiler]
-
mass:0.0f
A mass of zero means that forces or collisions with other objects do not affect the object. It can be static or, as in this case, kinematic (since you move the paddle using touches).
-
convex:YES
The paddle is also a convex object.
-
tag:kPaddleTag
You’re just marking it with the special tag you prepared earlier.
-
mass:0.0f
Border is a static body in Bullet terminology, since it has zero mass, and this means forces and collisions don’t move it. In addition, you won’t move it from the code.
-
convex:NO
Note that the border has a concave rather than convex shape, and thus you have to pass
NO
toPNode
’sinit
so that it can create the border’s shape another way. -
tag:kBorderTag
Use the border tag.
- The brick is a kinematic object. It is not affected by forces or collisions, but you’ll rotate it in the code.
- The brick has a convex shape.
-
The brick’s tag is
kBrickTag
. It’s very important to set the correct tag for the brick, so double-check this. -
Enumerate all manifolds.
Note: A contact manifold is a cache that contains all contact points between pairs of collision objects.
- Take each manifold object from the internal manifolds array by index.
-
Get the number of contacts and check that there is at least one contact between the pair of bodies.
This seems tricky, at least to me. The existence of a manifold holding a pair of bodies doesn't mean they are in contact right now. This is why you should check the number of contacts.
- In this game, only the ball collides with things, so at this point you know for sure that the ball just hit something and thus you need to play the corresponding sound effect.
- Next you need to understand which object the ball hit. Get the collision object from the manifold.
-
Remember this line from
PNode
’sinit
method?_body->setUserPointer((__bridge void*)self);
In this line, you've saved a reference to the
PNode
object in the body object property and here you're going to get it back to be able to check thetag
property. - & 8. Check if any of the objects is a brick. That would mean that the ball hit the brick, since the ball is the only moving object. In that case, you need to destroy the brick. The method to destroy the brick is just below.
- It removes the brick node from the scene and the bricks array.
- It removes the brick body from the world.
- Finally, it checks for a victory. If there are no bricks left, then the player just won the game! Yee-haw!
- Start with the setter method. You need to override it to allow rotating both drawable bodies and physics bodies when you set the rotation in the code.
- Save the value in case the node doesn't have a body for some reason.
- Get the current transform. Since you don't want to change the position, you will only modify the rotation part of the transform and send it back.
- Here’s the tricky part. You need to rotate the existing quaternion to the delta angle to achieve the required rotation. To do so, you calculate the angle of difference and rotate the current quaternion along the x-axis (
btVector3(1,0,0)
) by multiplying the new quaternion by the existing one. Please note that the order of multiplication matters! - Send back the updated transform.
- It’s time for a getter method. You need to override the property's getter to get the rotation from the physics object when rendering the node.
- Here you take the body’s current rotation quaternion and convert it to a rotation matrix.
btQuaternion
doesn't have methods to get Euler angles (at least I couldn't find any) butbtMatrix3x3
does. So you're being a little lazy and converting tobtMatrix3x3
to get Euler angles. - In cases where the node doesn't have a body, you use the default implementation of the property.
Then rename RWPaddle.m to RWPaddle.mm.
Switch to RWPaddle.mm and change the call to super initWithShader:
to use PNode
’s init
method with physics parameters.
Set the new parameters as follows and leave the existing parameters untouched:
[spoiler title=”RWPaddle.mm initWithShader:”]
- (id)initWithShader:(GLKBaseEffect *)shader {
if ((self = [super initWithName:"Paddle"
mass:0.0f //1
convex:YES //2
tag:kPaddleTag //3
shader:shader
vertices:(Vertex *)Paddle_Cube_paddle_Vertices
vertexCount:sizeof(Paddle_Cube_paddle_Vertices)/sizeof(Paddle_Cube_paddle_Vertices[0])
textureName:@"paddle.png"
specularColor:Paddle_Cube_paddle_specular
diffuseColor:Paddle_Cube_paddle_diffuse
shininess:Paddle_Cube_paddle_shininess])) {
self.width = 5.0;
self.height = 1.0;
}
return self;
}
[/spoiler]
Finally, switch to paddle.h and fix the variable declarations by adding the extern
keyword at the start of each line with variable declarations.
[spoiler title=”paddle.h – fixed version”]
extern const GLKVector4 Paddle_Cube_paddle_ambient;
extern const GLKVector4 Paddle_Cube_paddle_diffuse;
extern const GLKVector4 Paddle_Cube_paddle_specular;
extern const float Paddle_Cube_paddle_shininess;
extern const Vertex Paddle_Cube_paddle_Vertices[36];
[/spoiler]
I hope you’ve had no issues and haven’t needed to look at the spoilers. :]
There is one final thing missing: Adding the paddle body to the world.
Switch to RWGameScene.mm and find this line:
[self.children addObject:_paddle];
Add the following line right below it:
_world->addRigidBody(_paddle.body);
Build and Run the project. You should see the ball bouncing nicely on the paddle.
Limiting Movement with a Border
Bouncing the ball on the paddle is fun, but your goal is a bit different. Let’s get one step closer to it by adding the border that will limit the ball’s movement when you launch it in the next section.
The changes you need to make here are once again familiar: converting the RWBorder
to use PNode
instead of RWNode
. I believe you can do them yourself.
Switch to RWBorder.h and change the parent class of RWBorder
to PNode
:
[spoiler title=”RWBorder.h after changes”]
#import "PNode.h"
@interface RWBorder : PNode
- (id)initWithShader:(GLKBaseEffect *)shader;
@end
[/spoiler]
Rename RWBorder.m to RWBorder.mm.
Switch to RWBorder.mm and change the call to super initWithShader:
to use PNode
’s version of this method.
Set additional parameters as follows, leaving the existing parameters untouched:
[spoiler title=”RWBorder.mm initWithShader:”]
- (id)initWithShader:(GLKBaseEffect *)shader {
if ((self = [super initWithName:"Border"
mass:0.0f //1
convex:NO //2
tag:kBorderTag //3
shader:shader
vertices:(Vertex *)Border_Cube_Border_Vertices
vertexCount:sizeof(Border_Cube_Border_Vertices)/sizeof(Border_Cube_Border_Vertices[0])
textureName:@"border.png"
specularColor:Border_Cube_Border_specular
diffuseColor:Border_Cube_Border_diffuse
shininess:Border_Cube_Border_shininess])) {
self.width = 27.0;
self.height = 48.0;
}
return self;
}
[/spoiler]
Fix the variable declarations in border.h by adding the extern
keyword at the start of each line with variable declarations.
[spoiler title=”border.h – fixed version”]
extern const GLKVector4 Border_Cube_Border_ambient;
extern const GLKVector4 Border_Cube_Border_diffuse;
extern const GLKVector4 Border_Cube_Border_specular;
extern const float Border_Cube_Border_shininess;
extern const Vertex Border_Cube_Border_Vertices[132];
[/spoiler]
To add the border to the world, switch to RWGameScene.mm and find this line:
[self.children addObject:_border];
Add the following line right below it:
_world->addRigidBody(_border.body);
This will add the border’s body to the world.
Build and Run the project. You will still see the ball bouncing on the paddle, but you will change that very soon!
Launching the Ball
Right now the ball’s bouncing is kind of wimpy. It’s time to man up and launch him into the sky!
Well, in manner of speaking. I hope it doesn’t actually launch into the sky because that would mean the border you added in previous section doesn’t work. :]
Switch to RWGameScene.mm and find this line:
_world->addRigidBody(_ball.body);
Add the following line of code right below it:
_ball.body->setLinearVelocity(btVector3(15,15,0));
Here you set the ball’s velocity. You set the x- and y-components of the velocity vector because you want to set the ball moving along both directions – diagonally, since you give x and y the same values.
There’s one more thing. Currently you have gravity enabled, but in the original Breakout game, there is none. You want the ball to keep moving in the direction it’s heading, not be slowed down by the effect of gravity, so you need to disable the gravity.
Switch to RWGameScene.mm and scroll to the bottom of initPhysics
.
Replace the line that sets the gravity:
_world->setGravity(btVector3(0, -9.8, 0));
With this:
_world->setGravity(btVector3(0, 0, 0));
Build and run the game. The ball should launch to the top and right, bounce off the border and go through the bricks. Now that’s progress! As for the ghostly bricks, you’ll fix them in a minute.
Adding Bricks to the Physics World
Bricks are the ball’s natural enemies. Wait… who turned on Discovery channel? :]
Converting bricks to physical nodes should be straightforward – it’s the same process you’ve already completed with the ball, paddle and border.
Switch to RWBrick.h and change its parent class to PNode
. Don’t forget to import PNode.h.
[spoiler title=”RWBrick.h after changes”]
#import "PNode.h"
@interface RWBrick : PNode
- (id)initWithShader:(GLKBaseEffect *)shader;
@end
[/spoiler]
Then rename RWBrick.m to RWBrick.mm.
Switch to RWBrick.mm and change initWithShader:
to use PNode
’s init
method. Try to guess the correct values for the following brick properties (if in doubt, check the spoiler):
[spoiler title=”RWBrick.mm initWithShader:”]
- (id)initWithShader:(GLKBaseEffect *)shader {
if ((self = [super initWithName:"Brick"
mass:0.0f //1
convex:YES //2
tag:kBrickTag //3
shader:shader
vertices:(Vertex *)Cube_brick_Vertices
vertexCount:sizeof(Cube_brick_Vertices)/sizeof(Cube_brick_Vertices[0])
textureName:@"brick.png"
specularColor:Cube_brick_specular
diffuseColor:Cube_brick_diffuse
shininess:Cube_brick_shininess])) {
self.width = 2.0;
self.height = 1.0;
}
return self;
}
[/spoiler]
Fix the extern
-related issue in brick.h.
[spoiler title=”brick.h – fixed version:”]
extern const GLKVector4 Cube_brick_ambient;
extern const GLKVector4 Cube_brick_diffuse;
extern const GLKVector4 Cube_brick_specular;
extern const float Cube_brick_shininess;
extern const Vertex Cube_brick_Vertices[36];
[/spoiler]
And add all brick bodies to the world.
Switch to RWGameScene.mm and find the line where bricks are added to an array – this one:
[_bricks addObject:brick];
Add the following line right below it:
_world->addRigidBody(brick.body);
Build and Run the project. You have all the physics bodies in place and the ball interacts with all of them.
There is only one issue: You cannot win (since the bricks no longer get destroyed on collision), and although the ball falls down, you can’t lose, either!
Maybe someone would settle for this draw situation, but you won’t!
Winning and Losing with Physics
Losing is easy, so let’s start with that. :]
Switch to RWGameScene.mm and find updateWithDelta
.
Add the following code to the bottom of the method:
if (_ball.position.y < 0)
{
[RWDirector sharedInstance].scene = [[RWGameOverScene alloc] initWithShader:self.shader win:NO];
return;
}
This code is pretty simple. You check if the ball is below the zero line and switch to the Game Over scene if it is.
Build and Run the project. Let the ball fall and you should see the Game Over scene.
After the sad experience of losing, you must get revenge on those bricks and win the game!
Switch to RWGameScene.mm and import PNode.h at the top of the file, right after all other imports and includes:
#import "PNode.h"
Scroll down to updateWithDelta:
and add the following code to the end of the method, right after the code block where you check if the player lost:
//1
int numManifolds = _world->getDispatcher()->getNumManifolds();
for (int i=0;i<numManifolds;i++)
{
//2
btPersistentManifold* contactManifold = _world->getDispatcher()->getManifoldByIndexInternal(i);
//3
int numContacts = contactManifold->getNumContacts();
if (numContacts > 0)
{
//4
[[RWDirector sharedInstance] playPopEffect];
//5
const btCollisionObject* obA = contactManifold->getBody0();
const btCollisionObject* obB = contactManifold->getBody1();
//6
PNode* pnA = (__bridge PNode*)obA->getUserPointer();
PNode* pnB = (__bridge PNode*)obB->getUserPointer();
//7
if (pnA.tag == kBrickTag) {
[self destroyBrickAndCheckVictory:pnA];
}
//8
if (pnB.tag == kBrickTag){
[self destroyBrickAndCheckVictory:pnB];
}
}
}
As advised in the official Bullet wiki here:
The best way to determine if collisions happened between existing objects in the world is to iterate over all contact manifolds.
This is exactly what are you going to do. Here’s your plan:
Add the brick destruction method right below updateWithDelta
:
- (void)destroyBrickAndCheckVictory:(PNode*)brick
{
//1
[self.children removeObject:brick];
[_bricks removeObject:brick];
//2
_world->removeRigidBody(brick.body);
//3
if (_bricks.count == 0) {
[RWDirector sharedInstance].scene = [[RWGameOverScene alloc] initWithShader:self.shader win:YES];
}
}
This method does three simple things:
Build and run the game. It should now be fully playable.
Maintaining a Constant Velocity
And yet, as you’ll notice, the gameplay experience is not quite as it was in the original. For one thing, the ball begins to slow down after several collisions. This can be due to friction or some slight variance with the physics calculations. Here is the fix for that.
Switch to RWGameScene.mm and add an instance variable called _desiredVelocity
.
@implementation RWGameScene {
//...
btScalar _desiredVelocity;
}
Then go to initWithShader:
, also in RWGameScene.mm, and find this line:
_ball.body->setLinearVelocity(btVector3(15,15,0));
Add the following line right below it:
_desiredVelocity = _ball.body->getLinearVelocity().length();
This way, you save the magnitude of the vector without the direction, since the direction changes as the ball bounces off of different objects.
Scroll down to updateWithDelta:
and add the following code at the end of the method:
btVector3 currentVelocityDirection =_ball.body->getLinearVelocity();
btScalar currentVelocty = currentVelocityDirection.length();
if (currentVelocty < _desiredVelocity)
{
currentVelocityDirection *= _desiredVelocity/currentVelocty;
_ball.body->setLinearVelocity(currentVelocityDirection);
}
Now you’re using the saved magnitude to keep the ball’s velocity constant.
It is also time to remove the NSLog
from the method. Do that now.
Here is the complete version of updateWithDelta:
, in case you want to check if everything is correct.
[spoiler title="RWGameScene.mm final updateWithDelta:"]
- (void)updateWithDelta:(GLfloat)aDelta {
[super updateWithDelta:aDelta];
_world->stepSimulation(aDelta);
if (_ball.position.y < 0)
{
[RWDirector sharedInstance].scene = [[RWGameOverScene alloc] initWithShader:self.shader win:NO];
return;
}
int numManifolds = _world->getDispatcher()->getNumManifolds();
for (int i=0;i<numManifolds;i++)
{
btPersistentManifold* contactManifold = _world->getDispatcher()->getManifoldByIndexInternal(i);
int numContacts = contactManifold->getNumContacts();
if (numContacts > 0)
{
[[RWDirector sharedInstance] playPopEffect];
const btCollisionObject* obA = contactManifold->getBody0();
const btCollisionObject* obB = contactManifold->getBody1();
PNode* pnA = (__bridge PNode*)obA->getUserPointer();
PNode* pnB = (__bridge PNode*)obB->getUserPointer();
if (pnA.tag == kBrickTag) {
[self destroyBrickAndCheckVictory:pnA];
}
if (pnB.tag == kBrickTag){
[self destroyBrickAndCheckVictory:pnB];
}
}
}
btVector3 currentVelocityDirection =_ball.body->getLinearVelocity();
btScalar currentVelocty = currentVelocityDirection.length();
if (currentVelocty < _desiredVelocity)
{
currentVelocityDirection *= _desiredVelocity/currentVelocty;
_ball.body->setLinearVelocity(currentVelocityDirection);
}
}
[/spoiler]
Build and run, and now your ball should move at a much better (constant) speed!
Syncing a Physics Body with a Drawn Object: Rotation
I know it looks like the bricks are rotating as they were in the initial project, but it’s a lie! You are rotating drawable bricks, but the physical bricks are not rotating, and that is why the ball always bounces back the same way.
You're going to fix this. The easiest way to do it is to override the rotation properties (rotationX
, rotationY
, rotationZ
) of the PNode
class, just as you did with the position property, and keep them in sync with the physics body.
Disclaimer: You may remember that Bullet uses quaternions to represent rotation, while the original project uses Euler angles. In this tutorial, you're going to write some quick code to convert from Euler to quaternions and back. In a real-world project, you should stick with one or the other and use either quaternions or Euler angles everywhere in the code.
Switch to PNode.mm and add the following methods somewhere near the end of the file:
//1
-(void)setRotationX:(float)rotationX
{
//2
[super setRotationX:rotationX];
if (_body)
{
//3
btTransform trans = _body->getWorldTransform();
btQuaternion rot = trans.getRotation();
//4
float angleDiff = rotationX - self.rotationX;
btQuaternion diffRot = btQuaternion(btVector3(1,0,0), angleDiff);
rot = diffRot * rot;
//5
trans.setRotation(rot);
_body->setWorldTransform(trans);
}
}
//6
-(float)rotationX
{
if (_body)
{
//7
btMatrix3x3 rotMatrix = btMatrix3x3(_body->getWorldTransform().getRotation());
float z,y,x;
rotMatrix.getEulerZYX(z,y,x);
return x;
}
//8
return [super rotationX];
}
This requires an explanation:
Now it’s time to implement the same for the y- and z-rotations. Simply paste the following code below the x-rotation property implementation in PNode.mm:
-(void)setRotationY:(float)rotationY
{
[super setRotationY:rotationY];
if (_body)
{
btTransform trans = _body->getWorldTransform();
btQuaternion rot = trans.getRotation();
float angleDiff = rotationY - self.rotationY;
btQuaternion diffRot = btQuaternion(btVector3(0,1,0), angleDiff);
rot = diffRot * rot;
trans.setRotation(rot);
_body->setWorldTransform(trans);
}
}
-(float)rotationY
{
if (_body)
{
btMatrix3x3 rotMatrix = btMatrix3x3(_body->getWorldTransform().getRotation());
float z,y,x;
rotMatrix.getEulerZYX(z,y,x);
return y;
}
return [super rotationY];
}
-(void)setRotationZ:(float)rotationZ
{
[super setRotationZ:rotationZ];
if (_body)
{
btTransform trans = _body->getWorldTransform();
btQuaternion rot = trans.getRotation();
float angleDiff = rotationZ - self.rotationZ;
btQuaternion diffRot = btQuaternion(btVector3(0,0,1), angleDiff);
rot = diffRot * rot;
trans.setRotation(rot);
_body->setWorldTransform(trans);
}
}
-(float)rotationZ
{
if (_body)
{
btMatrix3x3 rotMatrix = btMatrix3x3(_body->getWorldTransform().getRotation());
float z,y,x;
rotMatrix.getEulerZYX(z,y,x);
return z;
}
return [super rotationZ];
}
Build and run the game.
Ah, that’s better. The ball now bounces more naturally off of the bricks. Of course it doesn’t look 100% realistic since you've restricted the ball’s movement to the (x,y) plane but it’s still much better.
You might also notice that the ball now spins. Before the physics body spun due to fiction, but the visual representation didn't. Now that you've overridden the rotation methods, the visual representation now matches!
Where to Go from Here?
Here is the final project with all the source code from this Bullet physics tutorial.
Congratulations, you have successfully integrated the Bullet physics engine into a 3D game. I hope this tutorial inspires you to continue learning and create your own 3D games using Bullet.
If you have any questions or comments, please post them below!
-
mass:0.0f
A mass of zero means that forces or collisions with other objects do not affect the object. It can be static or, as in this case, kinematic (since you move the paddle using touches).
-
convex:YES
The paddle is also a convex object.
-
tag:kPaddleTag
You’re just marking it with the special tag you prepared earlier.
-
mass:0.0f
Border is a static body in Bullet terminology, since it has zero mass, and this means forces and collisions don’t move it. In addition, you won’t move it from the code.
-
convex:NO
Note that the border has a concave rather than convex shape, and thus you have to pass
NO
toPNode
’sinit
so that it can create the border’s shape another way. -
tag:kBorderTag
Use the border tag.
- The brick is a kinematic object. It is not affected by forces or collisions, but you’ll rotate it in the code.
- The brick has a convex shape.
-
The brick’s tag is
kBrickTag
. It’s very important to set the correct tag for the brick, so double-check this.
-
Enumerate all manifolds.
Note: A contact manifold is a cache that contains all contact points between pairs of collision objects.
- Take each manifold object from the internal manifolds array by index.
-
Get the number of contacts and check that there is at least one contact between the pair of bodies.
This seems tricky, at least to me. The existence of a manifold holding a pair of bodies doesn't mean they are in contact right now. This is why you should check the number of contacts.
- In this game, only the ball collides with things, so at this point you know for sure that the ball just hit something and thus you need to play the corresponding sound effect.
- Next you need to understand which object the ball hit. Get the collision object from the manifold.
-
Remember this line from
PNode
’sinit
method?_body->setUserPointer((__bridge void*)self);
In this line, you've saved a reference to the
PNode
object in the body object property and here you're going to get it back to be able to check thetag
property. - & 8. Check if any of the objects is a brick. That would mean that the ball hit the brick, since the ball is the only moving object. In that case, you need to destroy the brick. The method to destroy the brick is just below.
- It removes the brick node from the scene and the bricks array.
- It removes the brick body from the world.
- Finally, it checks for a victory. If there are no bricks left, then the player just won the game! Yee-haw!
- Start with the setter method. You need to override it to allow rotating both drawable bodies and physics bodies when you set the rotation in the code.
- Save the value in case the node doesn't have a body for some reason.
- Get the current transform. Since you don't want to change the position, you will only modify the rotation part of the transform and send it back.
- Here’s the tricky part. You need to rotate the existing quaternion to the delta angle to achieve the required rotation. To do so, you calculate the angle of difference and rotate the current quaternion along the x-axis (
btVector3(1,0,0)
) by multiplying the new quaternion by the existing one. Please note that the order of multiplication matters! - Send back the updated transform.
- It’s time for a getter method. You need to override the property's getter to get the rotation from the physics object when rendering the node.
- Here you take the body’s current rotation quaternion and convert it to a rotation matrix.
btQuaternion
doesn't have methods to get Euler angles (at least I couldn't find any) butbtMatrix3x3
does. So you're being a little lazy and converting tobtMatrix3x3
to get Euler angles. - In cases where the node doesn't have a body, you use the default implementation of the property.
The best way to determine if collisions happened between existing objects in the world is to iterate over all contact manifolds.
Note: A contact manifold is a cache that contains all contact points between pairs of collision objects.
Disclaimer: You may remember that Bullet uses quaternions to represent rotation, while the original project uses Euler angles. In this tutorial, you're going to write some quick code to convert from Euler to quaternions and back. In a real-world project, you should stick with one or the other and use either quaternions or Euler angles everywhere in the code.
- (id)initWithShader:(GLKBaseEffect *)shader {
if ((self = [super initWithName:"Paddle"
mass:0.0f //1
convex:YES //2
tag:kPaddleTag //3
shader:shader
vertices:(Vertex *)Paddle_Cube_paddle_Vertices
vertexCount:sizeof(Paddle_Cube_paddle_Vertices)/sizeof(Paddle_Cube_paddle_Vertices[0])
textureName:@"paddle.png"
specularColor:Paddle_Cube_paddle_specular
diffuseColor:Paddle_Cube_paddle_diffuse
shininess:Paddle_Cube_paddle_shininess])) {
self.width = 5.0;
self.height = 1.0;
}
return self;
}
extern const GLKVector4 Paddle_Cube_paddle_ambient;
extern const GLKVector4 Paddle_Cube_paddle_diffuse;
extern const GLKVector4 Paddle_Cube_paddle_specular;
extern const float Paddle_Cube_paddle_shininess;
extern const Vertex Paddle_Cube_paddle_Vertices[36];
[self.children addObject:_paddle];
_world->addRigidBody(_paddle.body);
#import "PNode.h"
@interface RWBorder : PNode
- (id)initWithShader:(GLKBaseEffect *)shader;
@end
- (id)initWithShader:(GLKBaseEffect *)shader {
if ((self = [super initWithName:"Border"
mass:0.0f //1
convex:NO //2
tag:kBorderTag //3
shader:shader
vertices:(Vertex *)Border_Cube_Border_Vertices
vertexCount:sizeof(Border_Cube_Border_Vertices)/sizeof(Border_Cube_Border_Vertices[0])
textureName:@"border.png"
specularColor:Border_Cube_Border_specular
diffuseColor:Border_Cube_Border_diffuse
shininess:Border_Cube_Border_shininess])) {
self.width = 27.0;
self.height = 48.0;
}
return self;
}
extern const GLKVector4 Border_Cube_Border_ambient;
extern const GLKVector4 Border_Cube_Border_diffuse;
extern const GLKVector4 Border_Cube_Border_specular;
extern const float Border_Cube_Border_shininess;
extern const Vertex Border_Cube_Border_Vertices[132];
[self.children addObject:_border];
_world->addRigidBody(_border.body);
_world->addRigidBody(_ball.body);
_ball.body->setLinearVelocity(btVector3(15,15,0));
_world->setGravity(btVector3(0, -9.8, 0));
_world->setGravity(btVector3(0, 0, 0));
#import "PNode.h"
@interface RWBrick : PNode
- (id)initWithShader:(GLKBaseEffect *)shader;
@end
- (id)initWithShader:(GLKBaseEffect *)shader {
if ((self = [super initWithName:"Brick"
mass:0.0f //1
convex:YES //2
tag:kBrickTag //3
shader:shader
vertices:(Vertex *)Cube_brick_Vertices
vertexCount:sizeof(Cube_brick_Vertices)/sizeof(Cube_brick_Vertices[0])
textureName:@"brick.png"
specularColor:Cube_brick_specular
diffuseColor:Cube_brick_diffuse
shininess:Cube_brick_shininess])) {
self.width = 2.0;
self.height = 1.0;
}
return self;
}
extern const GLKVector4 Cube_brick_ambient;
extern const GLKVector4 Cube_brick_diffuse;
extern const GLKVector4 Cube_brick_specular;
extern const float Cube_brick_shininess;
extern const Vertex Cube_brick_Vertices[36];
[_bricks addObject:brick];
_world->addRigidBody(brick.body);
if (_ball.position.y < 0)
{
[RWDirector sharedInstance].scene = [[RWGameOverScene alloc] initWithShader:self.shader win:NO];
return;
}
#import "PNode.h"
//1
int numManifolds = _world->getDispatcher()->getNumManifolds();
for (int i=0;i<numManifolds;i++)
{
//2
btPersistentManifold* contactManifold = _world->getDispatcher()->getManifoldByIndexInternal(i);
//3
int numContacts = contactManifold->getNumContacts();
if (numContacts > 0)
{
//4
[[RWDirector sharedInstance] playPopEffect];
//5
const btCollisionObject* obA = contactManifold->getBody0();
const btCollisionObject* obB = contactManifold->getBody1();
//6
PNode* pnA = (__bridge PNode*)obA->getUserPointer();
PNode* pnB = (__bridge PNode*)obB->getUserPointer();
//7
if (pnA.tag == kBrickTag) {
[self destroyBrickAndCheckVictory:pnA];
}
//8
if (pnB.tag == kBrickTag){
[self destroyBrickAndCheckVictory:pnB];
}
}
}
_body->setUserPointer((__bridge void*)self);
- (void)destroyBrickAndCheckVictory:(PNode*)brick
{
//1
[self.children removeObject:brick];
[_bricks removeObject:brick];
//2
_world->removeRigidBody(brick.body);
//3
if (_bricks.count == 0) {
[RWDirector sharedInstance].scene = [[RWGameOverScene alloc] initWithShader:self.shader win:YES];
}
}
@implementation RWGameScene {
//...
btScalar _desiredVelocity;
}
_ball.body->setLinearVelocity(btVector3(15,15,0));
_desiredVelocity = _ball.body->getLinearVelocity().length();
btVector3 currentVelocityDirection =_ball.body->getLinearVelocity();
btScalar currentVelocty = currentVelocityDirection.length();
if (currentVelocty < _desiredVelocity)
{
currentVelocityDirection *= _desiredVelocity/currentVelocty;
_ball.body->setLinearVelocity(currentVelocityDirection);
}
- (void)updateWithDelta:(GLfloat)aDelta {
[super updateWithDelta:aDelta];
_world->stepSimulation(aDelta);
if (_ball.position.y < 0)
{
[RWDirector sharedInstance].scene = [[RWGameOverScene alloc] initWithShader:self.shader win:NO];
return;
}
int numManifolds = _world->getDispatcher()->getNumManifolds();
for (int i=0;i<numManifolds;i++)
{
btPersistentManifold* contactManifold = _world->getDispatcher()->getManifoldByIndexInternal(i);
int numContacts = contactManifold->getNumContacts();
if (numContacts > 0)
{
[[RWDirector sharedInstance] playPopEffect];
const btCollisionObject* obA = contactManifold->getBody0();
const btCollisionObject* obB = contactManifold->getBody1();
PNode* pnA = (__bridge PNode*)obA->getUserPointer();
PNode* pnB = (__bridge PNode*)obB->getUserPointer();
if (pnA.tag == kBrickTag) {
[self destroyBrickAndCheckVictory:pnA];
}
if (pnB.tag == kBrickTag){
[self destroyBrickAndCheckVictory:pnB];
}
}
}
btVector3 currentVelocityDirection =_ball.body->getLinearVelocity();
btScalar currentVelocty = currentVelocityDirection.length();
if (currentVelocty < _desiredVelocity)
{
currentVelocityDirection *= _desiredVelocity/currentVelocty;
_ball.body->setLinearVelocity(currentVelocityDirection);
}
}
//1
-(void)setRotationX:(float)rotationX
{
//2
[super setRotationX:rotationX];
if (_body)
{
//3
btTransform trans = _body->getWorldTransform();
btQuaternion rot = trans.getRotation();
//4
float angleDiff = rotationX - self.rotationX;
btQuaternion diffRot = btQuaternion(btVector3(1,0,0), angleDiff);
rot = diffRot * rot;
//5
trans.setRotation(rot);
_body->setWorldTransform(trans);
}
}
//6
-(float)rotationX
{
if (_body)
{
//7
btMatrix3x3 rotMatrix = btMatrix3x3(_body->getWorldTransform().getRotation());
float z,y,x;
rotMatrix.getEulerZYX(z,y,x);
return x;
}
//8
return [super rotationX];
}
-(void)setRotationY:(float)rotationY
{
[super setRotationY:rotationY];
if (_body)
{
btTransform trans = _body->getWorldTransform();
btQuaternion rot = trans.getRotation();
float angleDiff = rotationY - self.rotationY;
btQuaternion diffRot = btQuaternion(btVector3(0,1,0), angleDiff);
rot = diffRot * rot;
trans.setRotation(rot);
_body->setWorldTransform(trans);
}
}
-(float)rotationY
{
if (_body)
{
btMatrix3x3 rotMatrix = btMatrix3x3(_body->getWorldTransform().getRotation());
float z,y,x;
rotMatrix.getEulerZYX(z,y,x);
return y;
}
return [super rotationY];
}
-(void)setRotationZ:(float)rotationZ
{
[super setRotationZ:rotationZ];
if (_body)
{
btTransform trans = _body->getWorldTransform();
btQuaternion rot = trans.getRotation();
float angleDiff = rotationZ - self.rotationZ;
btQuaternion diffRot = btQuaternion(btVector3(0,0,1), angleDiff);
rot = diffRot * rot;
trans.setRotation(rot);
_body->setWorldTransform(trans);
}
}
-(float)rotationZ
{
if (_body)
{
btMatrix3x3 rotMatrix = btMatrix3x3(_body->getWorldTransform().getRotation());
float z,y,x;
rotMatrix.getEulerZYX(z,y,x);
return z;
}
return [super rotationZ];
}