[cocos2D] 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

Update: This code is obsolete now. You can just do

Label *messageLabel = [Label labelWithString:message dimensions:CGSizeMake(380, 120) alignment:UITextAlignmentCenter fontName:@"your_custom_font" fontSize:26];

by using the new FontManager class. For example, run this once in your app delegate when your program first loads

[[FontManager sharedManager] loadFont:@"your_custom_font"];

It can take a long NSString and create multiple labels without breaking up a word. I’ll eventually use this for my help screen, replacing the current 6 different 480×320 png images that I load for each one  .

The code is simple, but hopefully it might save somebody the time it took me to write it. I had to look up some very basic elements of ObjC here, so if there is a much easier way to do this, please let me know but don’t make too much fun of me.

You can easily switch out the BitmapFontAtlas for just a normal Label and it would work just fine.

(void) setTipString:(NSString*)str {   NSInteger lineChars = 0; BOOL isSpace = NO; NSInteger index = 0; NSInteger numLines = 0;   NSMutableString *line = [NSMutableString stringWithCapacity:LINE_LENGTH];   while (index <= [str length]) { if(index == [str length]) { BitmapFontAtlas *tip = [[BitmapFontAtlas bitmapFontAtlasWithString:[NSString stringWithString:line] fntFile:@"text.fnt" alignment:UITextAlignmentLeft] retain]; [tip setPosition: cpv(30,210 - 20 * numLines)]; [self addChild:tip]; return; }     NSString *tmp = [str substringWithRange:NSMakeRange(index, 1)]; [line appendString:tmp];   if([tmp isEqual:@" "]) isSpace = YES; else isSpace = NO;   if(lineChars >= LINE_LENGTH && isSpace) { BitmapFontAtlas *tip = [[BitmapFontAtlas bitmapFontAtlasWithString:[NSString stringWithString:line] fntFile:@"text.fnt" alignment:UITextAlignmentLeft] retain]; [tip setPosition: cpv(30,210 - 20 * numLines)]; [self addChild:tip]; lineChars = -1; [line setString:@""]; numLines++; } lineChars++; index++; } }
Posted by 오늘마감

댓글을 달아 주세요