Using Box2D Physics Engine with Cocos2D iPhone

Starting work on my new project, I’ve found Box2D to be a far superior physics engine than chipmunk. It is more mature, the API more flexible, and it seems to even perform faster. However, the cocos2d code for it was rather space, so here is a sort of helper file I had to create to make it work with my game.

Keep in mind this game is in progress and I’ve only been using this for a few days, so it may have issues that might pop up later on. It seems to be working very well for now though, with 40-50 objects on screen moving around with ~40 FPS.

Here is the header

//
//  Box2DEngine.h
//
//  Created by EricH on 7/22/09.
//  Copyright 2009 __MyCompanyName__. All rights reserved.
//
 
#import "Box2D.h"
 
// made this an extern constant to avoid obj c lookup overhead
extern b2World *bb_world;
 
@interface Box2DEngine : CocosNode {
}
 
+ (Box2DEngine *)sharedSingleton;
- (void)createWorld:(CGSize)size;
- (void)deleteWorld;
- (void)runSimulation;
@end

The .mm file

//
//  Box2DEngine.mm
//
//  Created by EricH on 7/22/09.
//  Copyright 2009 __MyCompanyName__. All rights reserved.
//
 
#import "HookActor.h"
 
 
#import "Box2DEngine.h"
#import "SuperBox2DActor.h"
 
b2World* bb_world;
 
const float32 timeStep = 1.0f / 60.0f;
const int32 velocityIterations = 10;
const int32 positionIterations = 10;
 
#define MAX_NUM_COLLISIONS 2048 // TODO make sure this buffer is the right size
b2ContactResult contactResultCache[MAX_NUM_COLLISIONS];
int32 contactResultCount = 0;
 
class MyContactListener : public b2ContactListener {
public:
	void Add(const b2ContactPoint* point) {
	}
 
	void Persist(const b2ContactPoint* point) {
	}
 
	void Remove(const b2ContactPoint* point) {
	}
 
	void Result(const b2ContactResult* point) {
		// TODO we are making a deep copy of every contact point here
		// check the box2d contact masks to make sure we minimize unwanted contact results
		contactResultCache[contactResultCount++] = *point; 
	}
};
 
void handleCachedContactResults() {
	b2ContactResult contactResult;
	for(int i = 0; i < contactResultCount; i++) {
#ifdef BBDEBUG
		if(contactResultCount >= MAX_NUM_COLLISIONS) {
			NSLog(@"RAN OUT OF BUFFER");
			assert(NO);
		}
#endif
		contactResult = contactResultCache[i];
		SuperBox2DActor* actorOne = (SuperBox2DActor*)contactResult.shape1->GetBody()->GetUserData();
		SuperBox2DActor* actorTwo = (SuperBox2DActor*)contactResult.shape2->GetBody()->GetUserData();
		if(!actorOne.isDead && !actorTwo.isDead) {
			[actorOne collisionResultOne:&contactResult withActor:actorTwo];
			[actorTwo collisionResultTwo:&contactResult withActor:actorOne];
		}
	}
 
	// clear the collision cache
	contactResultCount = 0;
}
 
void removeDeadActors() {
	b2Body* node = bb_world->GetBodyList();
	while (node)	{
		b2Body* b = node;
		node = node->GetNext();
 
		SuperBox2DActor* actor = (SuperBox2DActor*)b->GetUserData();
		if (actor.isDead) {
 
			// remove from physics  engine
			bb_world->DestroyBody(b);
 
			// remove from game engine (cocos2d)
			[[StandardGameController sharedSingleton] removeGameActor:actor];
		}
	}
}
 
 
@implementation Box2DEngine
+ (Box2DEngine*)sharedSingleton {
	static Box2DEngine* sharedSingleton;
	if (!sharedSingleton)
		sharedSingleton = [[Box2DEngine alloc] init];
 
	return sharedSingleton;
}
 
- (void)createWorld:(CGSize)size {
	b2AABB worldAABB;
	worldAABB.lowerBound.Set(0, 0);
	worldAABB.upperBound.Set(size.width * BOX2D_SCALE_FACTOR_INVERSE, size.height * BOX2D_SCALE_FACTOR_INVERSE); // TODO this shouldnt be inverse?
 
	b2Vec2 gravity(0.0f, -30.0f);
	bool doSleep = true;
 
	bb_world = new b2World(worldAABB, gravity, doSleep);
	bb_world->SetContactListener(new MyContactListener());
}
 
- (void)deleteWorld {
	[self unschedule:@selector(step:)];
	delete bb_world;
	bb_world = NULL;
}
 
- (void)runSimulation {
	[self schedule:@selector(step:)];
}
 
- (void)step:(ccTime)dt {
 
	// step the world
	bb_world->Step(dt, velocityIterations, positionIterations); // TODO do i use timestep or dt here?
 
	//BBLog(@"Num of contact results is %d",contactResultCount);
	// do stuff with collisions
	handleCachedContactResults();
 
	// remove all actors that are marked as dead
	removeDeadActors();
 
	// update cocosnode positions
	for (b2Body* b = bb_world->GetBodyList(); b; b = b->GetNext()) {
		if (b->GetUserData() != NULL) {
			SuperBox2DActor *actor = (SuperBox2DActor*)b->GetUserData();
			b2Vec2 position = b->GetPosition();
			actor.position = b2toCGPoint(position);
			actor.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
			//NSLog(@"obj %@ %4.2f %4.2f\n",actor, position.x, position.y);
		}
	}
 
}
 
@end

22 comments

  1. Hmm. GCC fails to compile on the line:

    contactResultCache[contactResultCount++] = *point;

    Is SuperBox2DActor a subclass of (Atlas)Sprite or a custom CocosNode?

    • What is the error?

      SuperBox2DActor is a subclass of CocosNode, as I only make it a node to allow to to have a cocos2d timer by adding it to my game scene object.

  2. Hello, thanks for the example.
    I am having a problem however… in the step function at the point inside the condition that userData is not NULL, if userData happens to be something other than the desired Actor type it drops into the debugger on the first line to access something specific to that class. Is there some way to filter by class before accessing the class?

    I tried this…

    void *uData = b->GetUserData();
    if (uData) {
    if(![uData isKindOfClass:[ActorBox2D class]]){

    But as soon as you try to call “isKindOfClass” on uData (which apparently isn’t a class that responds to this selector) it dumps to debugger again.

    Thanks again :)

    • Instead of using

      void *uData = b->GetUserData();

      do this instead

      id uData = b->GetUserData();

      Though I would suggest making all objects stored in the physics engine a subclass of a class the implements the various functions, even if it doesn’t do anything with them. This way you can avoid some crazy code hacks that might happen later on as you add in more objects with various behaviors.

      That’s more of a design decision though, and both methods will work (I used a lot of IsKindOfClass in StickWars, and the code gets VERY ugly, error prone, and hard to debug..but it works).

      For example, I have a single Box2DActor that is nothing but all the static shapes that make up the boundaries for the map, and when my function updates that object’s position nothing happens in the function, since it is simply a CocosNode that isn’t added to any parent. It still exists in the physics engine, but the visual representation of it (CocosNode) isn’t added to the environment so it doesn’t do anything.

    • Yea, what is probably happening is Box2D allows objects to ’sleep’ if they stop moving for a bit. This is great because it allows saves the CPU processing on inactive objects, but if you enable this feature sometimes you need to make sure that objects aren’t sleeping when you don’t want them to.

  3. Thanks for giving so much back to the developer community! As a newbie iphone dev i really appreciate it. I was wondering, are you using cocos2d version 0.8 with its “experimental” support of Box2D still? Or have you found it worthwhile to push into the Beta 0.8.1 (with theoretically better Box2D support)?

    • In StickWars, I’m working with 0.8 as I started out using Chipmunk and don’t want to change it over.

      For my other project, which I’m using Box2D, I will definitely push into beta 0.8.1 as soon as I get back to working on it. When using cocos2d, I’d suggest paying very close attention to updates and the new features coming out, because often there will be some awesome new feature that will save you a lot of time on your own.

      For example…I pretty much wrote all the code I needed to get Box2D support working well, about 2 weeks before 0.8.1 was released with better out-of-the-box support ;) .

      • Thanks for the quick reply!
        Haha, yeah, from your initial post date here it looked like you were all synched up with 0.8 (guessing from the changelog dates) so I wondered if 0.8.1 didn’t make some of your hard-earned foundation work redundant. :-P Just goes to show how quickly cocos2d is progressing!

  4. Hi Eric,
    Thanks for your articles, they r very helpful.
    We r making a game called 9000 BC and we nearly finish the game. However, we have some problem in the sound engine. When we play the bg music, the game doesn’t run smoothly. Can u tell me which sound engine you use in stick war? Your sound engine seems to work very well.

    Thanks :>
    9000 BC development team

    • Check out CocosDenshion from the latest download of cocos2d-iphone. It has a nice simple sound engine that will allow you to use the hardware mp3 decoder. Follow the instructions to make sure you aren’t using software decoding of compressed sound files (only playing 1 mp3 file at a time, using .wav or something uncompressed for short sound files).

  5. Hi Eric,

    Can you please post the project source code , so that it is easy for beginners to just compile and run it to see what is happening.

    Thanks
    Sridhar

    • I would like to, but I’m afraid I don’t have time to build a working ‘demo’ game to publish to help people out. The only code base I have are games I’m working on to release soon and I can’t release the code for those right now.

      If I find some time I’d try to make a ‘demo game’ that goes beyond the standard cocos2d template to show how I actually organize and use all the various frameworks (admob, cocos2d, box2d, openfeint) together. I’ll hopefully able to include a lot of tricks to make things work that are common questions people ask.

      • Hey Eric,

        I am newbie in cocos2d framework. i need some solution of acceleration with rotation i can do it bt it not smooth can you pls give me the solution pls.

        Thanks in advance.

      • Hey Eric,

        I am newbie in cocos2d framework. i need some solution of acceleration with rotation i can do it bt it not smooth can you pls give me the solution pls.

        Thanks in advance.

        Regards
        Evana

  6. Your blog is pretty interesting to me and your subject matter is very relevant. I was browsing around and came across something you might find interesting. I was guilty of 3 of them with my sites. “99% of blog owners are committing these 5 errors”. http://bit.ly/ts0ScL You will be suprised how simple they are to fix.

  7. It¡¦s actually a great and useful piece of information. I am happy that you shared this useful info with us. Please stay us informed like this. Thanks for sharing.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">