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

Fixing Architecture Configuration Errors

As mentioned in the previous section, you may see one or more errors that look like this:

build_warnings

These only appear when you build targeting a device, but since sooner or later you will want to build your project for devices (archiving for App Store submission also counts), you’ll have to fix them.

You are getting these errors because Bullet projects, which you imported as sub-projects, have outdated architecture settings. When you target a device the libraries cannot be built, and this means that you don’t actually get the libraries as a result of building those projects and cannot use them.

For now these are only warnings, because you haven’t written any code that uses Bullet in your project yet. But as soon as you do, the warnings will become errors, because the linker won’t be able to use the libraries you’re referencing.

Follow these steps to fix those errors:

  1. Click on the BulletCollision.xcodeproj project in Navigator.
  2. Select the BulletCollision target if it is not already selected by default.
  3. Make sure the Build Settings tab is opened.
  4. Click on the Architectures list item and change the armv7 setting to Standard architectures (armv7, armv7s) from the drop-down list.
  5. Then double-click on armv7 on the Valid Architectures line.
  6. In the dialog that opens, add lines for armv7s and arm64.

fixing_errors

Now follow the same steps for the BulletDynamics.xcodeproj and LinearMath.xcodeproj projects.

Once you have changed the architecture settings for all three libraries, build the project. This time,
you should get no errors or warnings on your project (you may get some warnings from the Bullet code itself, but that is OK).

got_libraries

Physics Worlds

Very soon now, you’ll actually write some code. And in so doing, you’ll create a whole new world!

You’re not going to use anyone’s rib, or fly on a magic carpet like Aladdin. :] You’re going to create a world for your physics objects.

A whole new (physics) world!

A whole new (physics) world!

When speaking about a physics engine, the world itself is an object, one that plays a special role in the simulation. You can think of the world as a container for the other physics objects. In addition to holding all the objects, the world is responsible for simulating the physics of how the objects interact.

The usual approach for this simulation is the so-called stepping the world technique. Real physical time seems to be infinitely divisible – given any two moments, you can imagine describing a third moment in between them. But since no computer can simulate an infinite number of moments, your physics engine will simulate a regular sequence of moments, just like the ticking second hand of a clock.

This simply amounts to implementing a method, update:(float)deltaTime, that updates the positions and orientations of all the objects in the world to reflect how they moved over deltaTime.

In essence, you are passing delta time, the time since the last update, to the world and asking it to simulate what happened during that interval. Each such call is called a step. During a step, many things can happen: an object might change position or rotation due to some force applied, several objects might collide and so on.

Enough theory – it’s time to create the world!

Creating the World

Rename the RWGameScene.m file to RWGameScene.mm – that is, change the extension to .mm. You’re going use some C++ in your code and renaming the file to .mm will notify Xcode to compile this file using Objective-C++ and support your C++ code.

Add the btBulletDynamicsCommon.h header at the top of RWGameScene.mm:

#include "btBulletDynamicsCommon.h"

In the @implementation part, add several instance variables. You’ll find out what they’re for later on.

@implementation RWGameScene {
    //Skipped...
 
    //New variables
    btBroadphaseInterface*                  _broadphase;
    btDefaultCollisionConfiguration*        _collisionConfiguration;
    btCollisionDispatcher*                  _dispatcher;
    btSequentialImpulseConstraintSolver*    _solver;
    btDiscreteDynamicsWorld*                _world;
}

Build your project now. If the project builds without any other errors, continue adding code. If you find errors other than the ones mentioned above, please check that you’ve followed all the steps above correctly. At this point you should only get other errors if you haven’t properly included the Bullet libraries or didn’t specify search paths.

Note: It is important to build projects as frequently as you can to detect errors as soon as possible. For instance, right now you might see a couple of warnings regarding Conversion from string literal to ‘char *’ is deprecated’. These are from code from the starter project. You can ignore them, or you can fix them by changing two method parameters and an instance variable declaration from char * to const char *.

Add the following function somewhere in RWGameScene.mm:

-(void)initPhysics
{
    //1
    _broadphase = new btDbvtBroadphase();
    
    //2
    _collisionConfiguration = new btDefaultCollisionConfiguration();
    _dispatcher = new btCollisionDispatcher(_collisionConfiguration);
    
    //3
    _solver = new btSequentialImpulseConstraintSolver();
    
    //4
    _world = new btDiscreteDynamicsWorld(_dispatcher, _broadphase, _solver, _collisionConfiguration);
    
    //5
    _world->setGravity(btVector3(0, -9.8, 0));
}

Here is an explanation of what this method does:

Collision detection is done in two phases: broad and narrow. In the broad phase, the physics engine quickly eliminates objects that cannot collide. For example, it can run a quick check to using the bounding boxes of objects, eliminating those that don’t collide. It then passes only a small list of objects that can collide to the narrow phase, which is much slower, since it checks actual shapes for collision.

Bullet has several built-in implementations of the broad phase. In this tutorial, you’re using the dynamic AABB tree implementation – i.e. btDbvtBroadphase.

You can read more about the broad phase here.

This is what causes the objects to interact properly, taking into account gravity, game logic supplied forces, collisions, and hinge constraints. It does a good job as long as you don’t push it to extremes, and is one of the bottlenecks in any high performance simulation. There are parallel versions available for some threading models.

You’re going to use the btSequentialImpulseConstraintSolver solver, which the “Hello World” sample uses.

  1. You instantiate the broad phase algorithm implementation.
  2. collisionConfiguration is responsible for full, not broad, collision detection. In other words, this is where the more fine-grained and accurate collision detection code runs. You could create your own implementation, but for now you’re using the built-in configuration again.
  3. Here is an excerpt from the Bullet “Hello World” sample, which has a good explanation of _solver:
  4. Finally you create the world, passing in the configuration options you created earlier.
  5. The last step is to set the world’s gravity. For now, it will be same gravity as we have here on Earth. The y-vector points up, so btVector3(0, -9.8, 0), which is a 3-component vector (x,y,z), will point down with a magnitude of 9.8.

That’s almost it for the world. Fortunately it isn’t going to require seven days. :]

Now you need to add a call to your initPhysics method. Add it somewhere at the beginning of the initWithShader: function in RWGameScene.mm:

- (instancetype)initWithShader:(GLKBaseEffect *)shader {
    if ((self = [super initWithName:"RWGameScene" shader:shader vertices:nil vertexCount:0])) {
        
        // Add call here
        [self initPhysics];

        //Rest of the function is skipped....
}

And as a final step, you need to free the storage of everything created with the C++ new operator. Remember, things you allocate with new are not managed by ARC – it’s up to you! So add a dealloc method somewhere in RWGameScene.mm:

- (void)dealloc
{    
    delete _world;
    delete _solver;
    delete _collisionConfiguration;
    delete _dispatcher;
    delete _broadphase;
}

Build and run the game. You won’t see any changes, but if the game doesn’t crash then everything works fine. You’ve created your world!

kid_world

Kirill Muzykov

Contributors

Kirill Muzykov

Author

Over 300 content creators. Join our team.