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 5 of 5 of this article. Click here to view the first page.

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.

  1. 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.
  2. Call a super implementation, in case for some reason you don’t have the body yet.
  3. 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 with btTransform, which holds both the position and rotation of the object. Here is the definition of a transform from the Bullet wiki:
  4. 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.
  5. Getting the position is easy. You get the transform of the body and take only the position part.
  6. In case for some reason PNode doesn’t have the body, just use the default property implementation.

Build and Run the project. Your ball should fall down through the paddle.

ball_falling_through_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.

ball_bouncing

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.

ball_hits_border

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.

Ball bouncing off brick

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.

You Lose screen

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.

ball_bounces_angle

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.

Rotating bricks

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!

  1. Import PNode.h instead of RWNode.h.
  2. Change the parent class to PNode.
    1. [/spoiler]
      1. mass:0.0f
      2. convex:YES
      3. tag:kPaddleTag
      - (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
      
      1. mass:0.0f
      2. convex:NO
      3. tag:kBorderTag
      - (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
      
      1. The brick is a kinematic object. It is not affected by forces or collisions, but you’ll rotate it in the code.
      2. The brick has a convex shape.
      3. The brick’s tag is kBrickTag. It’s very important to set the correct tag for the brick, so double-check this.
      - (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];
                  }
      	}
          }
      
      1. Enumerate all manifolds.
      2. Take each manifold object from the internal manifolds array by index.
      3. Get the number of contacts and check that there is at least one contact between the pair of bodies.
      4. 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.
      5. Next you need to understand which object the ball hit. Get the collision object from the manifold.
      6. Remember this line from PNode’s init method?
        _body->setUserPointer((__bridge void*)self);
        
      7. & 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.
      - (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];
          }
      }
      
      1. It removes the brick node from the scene and the bricks array.
      2. It removes the brick body from the world.
      3. Finally, it checks for a victory. If there are no bricks left, then the player just won the game! Yee-haw!
      @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];
      }
      
      1. 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.
      2. Save the value in case the node doesn't have a body for some reason.
      3. 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.
      4. 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!
      5. Send back the updated transform.
      6. 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.
      7. 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) but btMatrix3x3 does. So you're being a little lazy and converting to btMatrix3x3 to get Euler angles.
      8. In cases where the node doesn't have a body, you use the default implementation of the property.
      -(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];
      }
      
    [/spoiler]
    1. mass:0.0f
    2. convex:YES
    3. tag:kPaddleTag
    - (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
    
    1. mass:0.0f
    2. convex:NO
    3. tag:kBorderTag
    - (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
    
    1. The brick is a kinematic object. It is not affected by forces or collisions, but you’ll rotate it in the code.
    2. The brick has a convex shape.
    3. The brick’s tag is kBrickTag. It’s very important to set the correct tag for the brick, so double-check this.
    - (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];
                }
    	}
        }
    
    1. Enumerate all manifolds.
    2. Take each manifold object from the internal manifolds array by index.
    3. Get the number of contacts and check that there is at least one contact between the pair of bodies.
    4. 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.
    5. Next you need to understand which object the ball hit. Get the collision object from the manifold.
    6. Remember this line from PNode’s init method?
      _body->setUserPointer((__bridge void*)self);
      
    7. & 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.
    - (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];
        }
    }
    
    1. It removes the brick node from the scene and the bricks array.
    2. It removes the brick body from the world.
    3. Finally, it checks for a victory. If there are no bricks left, then the player just won the game! Yee-haw!
    @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];
    }
    
    1. 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.
    2. Save the value in case the node doesn't have a body for some reason.
    3. 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.
    4. 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!
    5. Send back the updated transform.
    6. 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.
    7. 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) but btMatrix3x3 does. So you're being a little lazy and converting to btMatrix3x3 to get Euler angles.
    8. In cases where the node doesn't have a body, you use the default implementation of the property.
    -(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];
    }
    
  1. mass:0.0f
  2. convex:YES
  3. tag:kPaddleTag
  1. mass:0.0f
  2. convex:NO
  3. tag:kBorderTag
  1. The brick is a kinematic object. It is not affected by forces or collisions, but you’ll rotate it in the code.
  2. The brick has a convex shape.
  3. The brick’s tag is kBrickTag. It’s very important to set the correct tag for the brick, so double-check this.
  1. Enumerate all manifolds.
  2. Take each manifold object from the internal manifolds array by index.
  3. Get the number of contacts and check that there is at least one contact between the pair of bodies.
  4. 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.
  5. Next you need to understand which object the ball hit. Get the collision object from the manifold.
  6. Remember this line from PNode’s init method?
    _body->setUserPointer((__bridge void*)self);
    
  7. & 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.
  1. It removes the brick node from the scene and the bricks array.
  2. It removes the brick body from the world.
  3. Finally, it checks for a victory. If there are no bricks left, then the player just won the game! Yee-haw!
  1. 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.
  2. Save the value in case the node doesn't have a body for some reason.
  3. 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.
  4. 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!
  5. Send back the updated transform.
  6. 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.
  7. 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) but btMatrix3x3 does. So you're being a little lazy and converting to btMatrix3x3 to get Euler angles.
  8. In cases where the node doesn't have a body, you use the default implementation of the property.
- (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];
}
Kirill Muzykov

Contributors

Kirill Muzykov

Author

Over 300 content creators. Join our team.