(For more resources on Cocos2d, see here.)
In our first physics recipe, we will explore the basics of creating a Box2D project and setting up a Box2D world. The example creates a scene that allows the user to create realistic 2D blocks.
Please refer to the project RecipeCollection02 for full working code of this recipe.
The first thing we need to do is create a Box2D project using the built-in Box2D project template:
Now, execute the following code:
#import "Box2D.h"
#import "GLES-Render.h"
//32 pixels = 1 meter
#define PTM_RATIO 32
@implementation Ch4_BasicSetup
-(CCLayer*) runRecipe {
[super runRecipe];
/* Box2D Initialization */
//Set gravity
b2Vec2 gravity;
gravity.Set(0.0f, -10.0f);
//Initialize world
bool doSleep = YES;
world = new b2World(gravity, doSleep);
world->SetContinuousPhysics(YES);
//Initialize debug drawing
m_debugDraw = new GLESDebugDraw( PTM_RATIO );
world->SetDebugDraw(m_debugDraw);
uint32 flags = 0;
flags += b2DebugDraw::e_shapeBit;
m_debugDraw->SetFlags(flags);
//Create level boundaries
[self addLevelBoundaries];
//Add batch node for block creation
CCSpriteBatchNode *batch = [CCSpriteBatchNode
batchNodeWithFile:@"blocks.png" capacity:150];
[self addChild:batch z:0 tag:0];
//Add a new block
CGSize screenSize = [CCDirector sharedDirector].winSize;
[self addNewSpriteWithCoords:ccp(screenSize.width/2, screenSize.
height/2)];
//Schedule step method
[self schedule:@selector(step:)];
return self;
}
/* Adds a polygonal box around the screen */
-(void) addLevelBoundaries {
CGSize screenSize = [CCDirector sharedDirector].winSize;
//Create the body
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0, 0);
b2Body *body = world->CreateBody(&groundBodyDef);
//Create a polygon shape
b2PolygonShape groundBox;
//Add four fixtures each with a single edge
groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(screenSize.width/PTM_
RATIO,0));
body->CreateFixture(&groundBox,0);
groundBox.SetAsEdge(b2Vec2(0,screenSize.height/PTM_RATIO),
b2Vec2(screenSize.width/PTM_RATIO,screenSize.height/PTM_RATIO));
body->CreateFixture(&groundBox,0);
groundBox.SetAsEdge(b2Vec2(0,screenSize.height/PTM_RATIO),
b2Vec2(0,0));
body->CreateFixture(&groundBox,0);
groundBox.SetAsEdge(b2Vec2(screenSize.width/PTM_RATIO,screenSize.
height/PTM_RATIO), b2Vec2(screenSize.width/PTM_RATIO,0));
body->CreateFixture(&groundBox,0);
}
/* Adds a textured block */
-(void) addNewSpriteWithCoords:(CGPoint)p {
CCSpriteBatchNode *batch = (CCSpriteBatchNode*) [self
getChildByTag:0];
//Add randomly textured block
int idx = (CCRANDOM_0_1() > .5 ? 0:1);
int idy = (CCRANDOM_0_1() > .5 ? 0:1);
CCSprite *sprite = [CCSprite spriteWithBatchNode:batch
rect:CGRectMake(32 * idx,32 * idy,32,32)];
[batch addChild:sprite];
sprite.position = ccp( p.x, p.y);
//Define body definition and create body
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(p.x/PTM_RATIO, p.y/PTM_RATIO);
bodyDef.userData = sprite;
b2Body *body = world->CreateBody(&bodyDef);
//Define another box shape for our dynamic body.
b2PolygonShape dynamicBox;
dynamicBox.SetAsBox(.5f, .5f);//These are mid points for our 1m box
//Define the dynamic body fixture.
b2FixtureDef fixtureDef;
fixtureDef.shape = &dynamicBox;
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.3f;
body->CreateFixture(&fixtureDef);
}
/* Draw debug data */
-(void) draw {
//Disable textures
glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
//Draw debug data
world->DrawDebugData();
//Re-enable textures
glEnable(GL_TEXTURE_2D);
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}
/* Update graphical positions using physical positions */
-(void) step: (ccTime) dt {
//Set velocity and position iterations
int32 velocityIterations = 8;
int32 positionIterations = 3;
//Steo the Box2D world
world->Step(dt, velocityIterations, positionIterations);
//Update sprite position and rotation to fit physical bodies
for (b2Body* b = world->GetBodyList(); b; b = b->GetNext()) {
if (b->GetUserData() != NULL) {
CCSprite *obj = (CCSprite*)b->GetUserData();
obj.position = CGPointMake( b->GetPosition().x * PTM_RATIO,
b->GetPosition().y * PTM_RATIO);
obj.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
}
}
}
/* Tap to add a block */
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for( UITouch *touch in touches ) {
CGPoint location = [touch locationInView: [touch view]];
location = [[CCDirector sharedDirector] convertToGL: location];
[self addNewSpriteWithCoords: location];
}
}
@end
The Box2D sample project is a simple way to understand what a physics system looks like.
void SetGravity(const b2Vec2& gravity);
In addition to storing a pointer to the main b2World instance, we also usually store a pointer to an instance of GLESDebugDraw.
b2BodyDef bodyDef;
bodyDef.position.Set(0, 0);
b2Body *body = world->CreateBody(&bodyDef);
b2PolygonShape poly;
poly.SetAsEdge(b2Vec2(0,0), b2Vec2(480/PTM_RATIO,0));
body->CreateFixture(&poly,0);
Because these edges have no corresponding visual components (they are invisible), we do not need to set the bodyDef.userData pointer.
int32 velocityIterations = 8;
int32 positionIterations = 3;
world->Step(dt, velocityIterations, positionIterations);
The Box2D world Step method moves the physics engine forward one step. The Box2D constraint solver runs in two phases: the velocity phase and position phase. These determine how fast the bodies move and where they are in the game world. Setting these variables higher results in a more accurate simulation at the cost of speed. Setting velocityIterations to 8 and positionIterations to 3 is the suggested baseline in the Box2D manual. Using the dt variable syncs the logical timing of the application with the physical timing. If a game step takes an inordinate amount of time, the physics system will move forward quickly to compensate. This is referred to as a variable time step. An alternative to this would be a fixed time step set to 1/60th of a second. In addition to the physical step, we also reposition and re-orientate all CCSprites according to their respective b2Body positions and rotations:
for (b2Body* b = world->GetBodyList(); b; b = b->GetNext()) {
if (b->GetUserData() != NULL) {
CCSprite *obj = (CCSprite*)b->GetUserData();
obj.position = CGPointMake( b->GetPosition().x * PTM_RATIO,
b->GetPosition().y * PTM_RATIO);
obj.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
}
}
Taken together, these pieces of code sync the physical world with the visual.
At Packt, we are always on the lookout for innovative startups that are not only…
I remember deciding to pursue my first IT certification, the CompTIA A+. I had signed…
Key takeaways The transformer architecture has proved to be revolutionary in outperforming the classical RNN…
Once we learn how to deploy an Ubuntu server, how to manage users, and how…
Key-takeaways: Clean code isn’t just a nice thing to have or a luxury in software projects; it's a necessity. If we…
While developing a web application, or setting dynamic pages and meta tags we need to deal with…