Box2D C++ tutorials - Setting up (iPhone)

Last edited: July 14 2013

Chinese version -> 中文

Intro


This article looks at how to set up a basic project for the iOS using Box2D and OpenGL. Since it does not use the testbed like the rest of the 'Setting up' topics, I have kept it separate from the tutorial pages, but it could be useful for you if you are in the situation I was 6 months ago, which is:
  • you want to make physics games using Box2D for the iOS
  • you know C++ well but can't be bothered learning much Obj-C
  • you know OpenGL and want to use it for your games
  • you prefer to reuse your own code rather than learning libraries such as cocos2d
  • you've never used XCode (or Mac either for that matter)
This page will go through an 'absolute beginners' guide to getting Box2D running in a basic XCode project for the iPhone, with the assumption that the reader is new to XCode and Obj-C. A simple scene with some falling boxes will be set up as an example.

The project will use OpenGL ES 1.1 only, and is made with XCode 3.2.4. and Box2D 2.1.2.


Setting up a project with Box2D support


Open XCode and create a new project, choosing "OpenGL ES Application".

Create project

Choose OpenGL ES

This project sets up a basic OpenGL view and a timer to use for animation.

OpenGL ES default project

Download the Box2D source code from here (v2.1.2), unzip it and place it alongside the existing project source files, or anywhere convenient.

Box2D downloaded

Add the Box2D engine part of the source code tree to the project.

Add Box2D source code

Specifically, the engine part of the source code is the folder which contains Box2D.h

Box2D engine source code folder

It's convenient to create groups recursively for the added files.

Box2D source add recursively

After adding, the source can be easily browsed in the project files. (The cmake files are not necessary, you can remove those.)

Create project

Try building the project. If you get an error saying that a valid provisioning profile could not be found, you can get one from the provisioning portal at Apple's iOS Dev Center, but I'm not gonna cover that here so just change the build type to Simulator to continue.

iPhone Box2D compile errors iPhone Box2D compile errors

ah... first attempt at building results in thousands of errors due to the Box2D headers not being found.

iPhone Box2D compile errors

In the project settings...

iPhone Box2D open project settings

...double click 'Header Search Paths' (type 'header' in the search field to filter the settings to make it easier to find).

iPhone Box2D project settings header

Specify one folder above where the file Box2D.h is. The reason for this is because the Box2D files are intended to be included like this:
#include <Box2D/Box2d.h>

iPhone Box2D project settings header

Building should succeed now.


Adding a Box2D world to the OpenGL view


Take a look at the following functions in the <projectname>ViewController.m file:
  • -(void) awakeFromNib
  • -(void) drawFrame
  • -(void) viewDidUnload
awakeFromNib
This function is called once when the view is loaded. Any initialization which only needs to be done once can be put here. Currently this function creates the OpenGL context and animation timer.
drawFrame
This function is called repeatedly to update the view while it is running. The refresh rate can be adjusted in the startAnimation function. This function is called as part of a run loop so it is not called from another thread and you don't need to worry about concurrency issues.
viewDidUnload
This is called when the view is closed, which in this default project is never. However if you have a more complex app where you are loading and unloading views you will need to free any resources you are using in this function. Currently it just tears down the OpenGL context.

Now take a look at the header file <projectname>ViewController.h
This class holds the OpenGL context and a few other things as member variables. Include the Box2D header and add a Box2D world pointer as a class member variable:
1
2
3
4
  #include <Box2D/Box2d.h>
  
  //inside the class declaration (the @interface part)
  b2World* m_world;

That will again cause masses of errors because we are trying to put C++ in an Obj-C file and that doesn't go down very well. However this is all magically taken care of by changing the file extension for the .m file to .mm - if you do this in XCode's Groups & Files panel it conveniently(??) changes the name of the file on disk as well. You will also need to change the file extension of the <projectname>AppDelegate.m file too, because it includes the view controller header. The .mm extension means the file is taken to be mixed C/C++/Obj-C and makes life much easier.

Now in the awakeFromNib function we can create a Box2D world:
1
  m_world = new b2World( b2Vec2(0,-10), true );
and delete it in viewDidUnload:
1
  delete m_world;


Making a debug draw class to render the Box2D world


Since I have no clue about how OpenGL ES 2.0 works, I will be forcing the app not to use it for the purposes of this article. I usually do that by altering the beginning of the awakeFromNib function so that instead of first attempting to create a 2.0 context, it simply creates a 1.1 context right away:
1
  EAGLContext aContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];

Replace the contents of the drawFrame function with a bare-bones implementation like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  [(EAGLView *)self.view setFramebuffer];
  
  glClearColor(0, 0, 0, 1);
  glClear(GL_COLOR_BUFFER_BIT); 
        
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  
  m_world->Step(1/60.0f, 8, 3);
  m_world->DrawDebugData();
  
  [(EAGLView *)self.view presentFramebuffer];
Since we have not set a debug draw handler in the world, calling DrawDebugData at this point will do nothing. That's all very well if you just wanted a black screen, but I think we want a bit more than that. To get the Box2D world showing up in the OpenGL view we will need to create a subclass of the b2DebugDraw class, and fill in it's functions with the necessary OpenGL ES drawing routines.

For details on how this works see the debug draw tutorial topic, but suffice to say you'll need something like this for each of the functions in b2DebugDraw:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    void DrawSolidPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color) 
    {
      //set up vertex array
      GLfloat glverts[16]; //allow for polygons up to 8 vertices
      glVertexPointer(2, GL_FLOAT, 0, glverts); //tell OpenGL where to find vertices
      glEnableClientState(GL_VERTEX_ARRAY); //use vertices in subsequent calls to glDrawArrays
      
      //fill in vertex positions as directed by Box2D
      for (int i = 0; i < vertexCount; i++) {
                glverts[i*2]   = vertices[i].x;
                glverts[i*2+1] = vertices[i].y;
      }
        
      glEnable(GL_BLEND);
      glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
      
      //draw solid area
      glColor4f( color.r, color.g, color.b, 0.5f );
      glDrawArrays(GL_TRIANGLE_FAN, 0, vertexCount);
        
      //draw lines
      glColor4f( color.r, color.g, color.b, 1 );
      glDrawArrays(GL_LINE_LOOP, 0, vertexCount);
    }
I will leave it as an exercise for the reader to do this for the other functions of the class. For the rest of this topic, please assume that I now have a header and implementation file for a class called MyDebugDraw (since this is pure C++ the implementation can have a .cpp extension).


Finishing off the loose ends


Now that we have a debug draw class to use, in the view controller header we can add:
1
2
3
4
  #include "MyDebugDraw.h"
  
  //inside the class declaration (the @interface part)
  MyDebugDraw m_debugDraw;
... and in the view controller implementation (.mm) after creating the b2World we can do:
1
2
  m_debugDraw.SetFlags( b2DebugDraw::e_shapeBit );
  m_world->SetDebugDraw( &m_debugDraw );

aaaargghhh still just a black screen! Well, the world is empty right? Let's put some bodies in it (there is nothing special about this section, add anything you like here just to have something to look at):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  //in awakeFromNib after creating world
  {
    //body definition
    b2BodyDef myBodyDef;
    myBodyDef.type = b2_dynamicBody;
    
    //shape definition
    b2PolygonShape polygonShape;
    polygonShape.SetAsBox(1, 1); //a 2x2 rectangle
    
    //fixture definition
    b2FixtureDef myFixtureDef;
    myFixtureDef.shape = &polygonShape;
    myFixtureDef.density = 1;
    
    //create dynamic bodies
    for (int i = 0; i < 10; i++) {
      myBodyDef.position.Set(0, 10);
      m_world->CreateBody(&myBodyDef)->CreateFixture(&myFixtureDef);
    }
    
    //a static body
    myBodyDef.type = b2_staticBody;
    myBodyDef.position.Set(0, 0);
    b2Body* staticBody = m_world->CreateBody(&myBodyDef);
    
    //add a fixture to the static body
    polygonShape.SetAsBox( 10, 1, b2Vec2(0, 0), 0);
    staticBody->CreateFixture(&myFixtureDef);
  }
aaaargghhh now it's just a green screen! Well, what part of the world is being shown? Specify where you want to look by adding something like this before calling DrawDebugData:
1
2
  glViewport(0, 0, 320, 480);
  glOrthof(-16, 16, -8, 40, -1, 1);
And then you should be seeing this...

iPhone Box2D OpenGL project

Source code


You can download the source code for this XCode project here.