Hi,
Create a Box2D sample project (I am using 99.4 Cocos2D)
PhysicsSystem.h
#import "Box2D.h"
#import "cocos2d.h"
#import "CCPhysicsSprite.h"
const static float FIXED_TIME_STEP = 1.f/60.f;
class PhysicsSystem
{
protected:
float fixedTimestepAccumulator_;
float fixedTimestepAccumulatorRatio_;
float velocityIterations_, positionIterations_;
b2World* world_;
public:
b2World* getWorld(void);
void update (float dt);
void singleStep_ (float dt);
void smoothStates_ ();
void resetSmoothStates_ ();
//PhysicsSystem(float theInitialFixedTimeStepAccumulator, float theInitialFixedTimeStepAccumulatorRatio);
PhysicsSystem(void);
//virtual ~PhysicsSystem(void);
~PhysicsSystem(void);
};
PhysicsSystem.mm
#include "PhysicsSystem.h"
const float FIXED_TIMESTEP = 1.0f / 60.f;
PhysicsSystem::PhysicsSystem (): fixedTimestepAccumulator_ (0), fixedTimestepAccumulatorRatio_ (0),velocityIterations_(8), positionIterations_(1)
{
//int32 velocityIterations = 8;
//int32 positionIterations = 1;
// ...
// Define the gravity vector.
b2Vec2 gravity;
gravity.Set(0.0f, -10.0f);
// Do we want to let bodies sleep?
// This will speed up the physics simulation
bool doSleep = true;
world_ = new b2World(gravity, doSleep);
world_->SetAutoClearForces (false);
// ...
}
b2World* PhysicsSystem::getWorld(void) {
return world_;
}
PhysicsSystem::~PhysicsSystem (void) {
CCLOG(@"DESTRUCTING PHYSICS...");
delete world_;
}
void PhysicsSystem::update (float dt)
{
// Maximum number of steps, to avoid degrading to an halt.
const int MAX_STEPS = 5;
fixedTimestepAccumulator_ += dt;
const int nSteps = static_cast<int> (
std::floor (fixedTimestepAccumulator_ / FIXED_TIMESTEP)
);
// To avoid rounding errors, touches fixedTimestepAccumulator_ only
// if needed.
if (nSteps > 0)
{
fixedTimestepAccumulator_ -= nSteps * FIXED_TIMESTEP;
}
assert (
"Accumulator must have a value lesser than the fixed time step" &&
fixedTimestepAccumulator_ < FIXED_TIMESTEP + FLT_EPSILON
);
fixedTimestepAccumulatorRatio_ = fixedTimestepAccumulator_ / FIXED_TIMESTEP;
// This is similar to clamp "dt":
// dt = std::min (dt, MAX_STEPS * FIXED_TIMESTEP)
// but it allows above calculations of fixedTimestepAccumulator_ and
// fixedTimestepAccumulatorRatio_ to remain unchanged.
const int nStepsClamped = std::min (nSteps, MAX_STEPS);
for (int i = 0; i < nStepsClamped; ++ i)
{
// In singleStep_() the CollisionManager could fire custom
// callbacks that uses the smoothed states. So we must be sure
// to reset them correctly before firing the callbacks.
resetSmoothStates_ ();
singleStep_ (FIXED_TIMESTEP);
}
world_->ClearForces ();
// We "smooth" positions and orientations using
// fixedTimestepAccumulatorRatio_ (alpha).
smoothStates_ ();
}
void PhysicsSystem::singleStep_ (float dt)
{
// ...
//updateControllers_ (dt);
world_->Step (dt, velocityIterations_, positionIterations_);
//consumeContacts_ ();
// ...
}
void PhysicsSystem::smoothStates_ ()
{
b2Vec2 newSmoothedPosition;
const float oneMinusRatio = 1.f - fixedTimestepAccumulatorRatio_;
for (b2Body * b = world_->GetBodyList (); b != NULL; b = b->GetNext ())
{
if (b->GetType () == b2_staticBody)
{
continue;
}
CCPhysicsSprite *c = (CCPhysicsSprite*) b->GetUserData();
newSmoothedPosition = fixedTimestepAccumulatorRatio_ * b->GetPosition () + oneMinusRatio * c.previousPosition;
c.smoothedPosition = newSmoothedPosition;
c.smoothedAngle =
fixedTimestepAccumulatorRatio_ * b->GetAngle () +
oneMinusRatio * c.previousAngle;
}
}
void PhysicsSystem::resetSmoothStates_ ()
{
b2Vec2 newSmoothedPosition;
for (b2Body * b = world_->GetBodyList (); b != NULL; b = b->GetNext ())
{
if (b->GetType () == b2_staticBody)
{
continue;
}
CCPhysicsSprite *c = (CCPhysicsSprite*) b->GetUserData();
newSmoothedPosition = b->GetPosition ();
c.smoothedPosition = newSmoothedPosition;
c.previousPosition = newSmoothedPosition;
c.smoothedAngle = b->GetAngle ();
c.previousAngle = b->GetAngle();
}
}
CCPhysicsSprite.h
#import "cocos2d.h"
#import "Box2D.h"
@interface CCPhysicsSprite : CCSprite {
// special version of CCSprite which is used to support the fixed timestep implementation
float _previousAngle;
float _smoothedAngle;
b2Vec2 _previousPosition;
b2Vec2 _smoothedPosition;
}
@property (nonatomic) float32 previousAngle;
@property (nonatomic) float32 smoothedAngle;
@property (nonatomic) b2Vec2 previousPosition;
@property (nonatomic) b2Vec2 smoothedPosition;
@end
CCPhysicsSprite.mm
#import "CCPhysicsSprite.h"
@implementation CCPhysicsSprite
@synthesize previousAngle = _previousAngle;
@synthesize smoothedAngle = _smoothedAngle;
@synthesize previousPosition = _previousPosition;
@synthesize smoothedPosition = _smoothedPosition;
@end
Replace HelloWorldScene.h
#import "cocos2d.h"
#import "Box2D.h"
#import "GLES-Render.h"
#import "PhysicsSystem.h"
// HelloWorld Layer
@interface HelloWorld : CCLayer
{
PhysicsSystem* physics; // manages the fixed timestep implementation
b2World* world; // weak reference
GLESDebugDraw *m_debugDraw;
}
// returns a Scene that contains the HelloWorld as the only child
+(id) scene;
// adds a new sprite at a given coordinate
-(void) addNewSpriteWithCoords:(CGPoint)p;
@end
Replace HelloWorldScene.mm (replace with .mm extension as you are using C++):
//
// HelloWorldScene.mm
// FixedTimeStepBox2D_v2
//
// Created by Stephen Mackenzie on 21/08/2010.
// Copyright Open University 2010. All rights reserved.
//
// Import the interfaces
#import "HelloWorldScene.h"
//Pixel to metres ratio. Box2D uses metres as the unit for measurement.
//This ratio defines how many pixels correspond to 1 Box2D "metre"
//Box2D is optimized for objects of 1x1 metre therefore it makes sense
//to define the ratio so that your most common object type is 1x1 metre.
#define PTM_RATIO 32
// enums that will be used as tags
enum {
kTagTileMap = 1,
kTagSpriteSheet = 1,
kTagAnimation1 = 1,
};
// HelloWorld implementation
@implementation HelloWorld
+(id) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
HelloWorld *layer = [HelloWorld node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
// initialize your instance here
-(id) init
{
if( (self=[super init])) {
// enable touches
self.isTouchEnabled = YES;
// enable accelerometer
self.isAccelerometerEnabled = YES;
CGSize screenSize = [CCDirector sharedDirector].winSize;
CCLOG(@"Screen width %0.2f screen height %0.2f",screenSize.width,screenSize.height);
// Define the gravity vector.
b2Vec2 gravity;
gravity.Set(0.0f, -10.0f);
physics = new PhysicsSystem();
// Do we want to let bodies sleep?
// This will speed up the physics simulation
bool doSleep = true;
// Construct a world object, which will hold and simulate the rigid bodies.
//world = new b2World(gravity, doSleep);
world = physics->getWorld();
world->SetContinuousPhysics(true);
// Debug Draw functions
m_debugDraw = new GLESDebugDraw( PTM_RATIO );
world->SetDebugDraw(m_debugDraw);
uint32 flags = 0;
flags += b2DebugDraw::e_shapeBit;
// flags += b2DebugDraw::e_jointBit;
// flags += b2DebugDraw::e_aabbBit;
// flags += b2DebugDraw::e_pairBit;
// flags += b2DebugDraw::e_centerOfMassBit;
m_debugDraw->SetFlags(flags);
// Define the ground body.
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0, 0); // bottom-left corner
// Call the body factory which allocates memory for the ground body
// from a pool and creates the ground box shape (also from a pool).
// The body is also added to the world.
b2Body* groundBody = world->CreateBody(&groundBodyDef);
// Define the ground box shape.
b2PolygonShape groundBox;
// bottom
groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(screenSize.width/PTM_RATIO,0));
groundBody->CreateFixture(&groundBox,0);
// top
groundBox.SetAsEdge(b2Vec2(0,screenSize.height/PTM_RATIO), b2Vec2(screenSize.width/PTM_RATIO,screenSize.height/PTM_RATIO));
groundBody->CreateFixture(&groundBox,0);
// left
groundBox.SetAsEdge(b2Vec2(0,screenSize.height/PTM_RATIO), b2Vec2(0,0));
groundBody->CreateFixture(&groundBox,0);
// right
groundBox.SetAsEdge(b2Vec2(screenSize.width/PTM_RATIO,screenSize.height/PTM_RATIO), b2Vec2(screenSize.width/PTM_RATIO,0));
groundBody->CreateFixture(&groundBox,0);
//Set up sprite
CCSpriteSheet *sheet = [CCSpriteSheet spriteSheetWithFile:@"blocks.png" capacity:150];
[self addChild:sheet z:0 tag:kTagSpriteSheet];
[self addNewSpriteWithCoords:ccp(screenSize.width/2, screenSize.height/2)];
CCLabel *label = [CCLabel labelWithString:@"Tap screen" fontName:@"Marker Felt" fontSize:32];
[self addChild:label z:0];
[label setColor:ccc3(0,0,255)];
label.position = ccp( screenSize.width/2, screenSize.height-50);
[self schedule: @selector(tick:)];
}
return self;
}
-(void) draw
{
// Default GL states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY
// Needed states: GL_VERTEX_ARRAY,
// Unneeded states: GL_TEXTURE_2D, GL_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY
glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
world->DrawDebugData();
// restore default GL states
glEnable(GL_TEXTURE_2D);
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}
-(void) addNewSpriteWithCoords:(CGPoint)p
{
CCLOG(@"Add sprite %0.2f x %02.f",p.x,p.y);
CCSpriteSheet *sheet = (CCSpriteSheet*) [self getChildByTag:kTagSpriteSheet];
//We have a 64x64 sprite sheet with 4 different 32x32 images. The following code is
//just randomly picking one of the images
int idx = (CCRANDOM_0_1() > .5 ? 0:1);
int idy = (CCRANDOM_0_1() > .5 ? 0:1);
// Create a CCPhysicsSprite which has variables to support fixed timestep implementation
CCPhysicsSprite *sprite = [CCPhysicsSprite spriteWithSpriteSheet:sheet rect:CGRectMake(32 * idx,32 * idy,32,32)];
[sheet addChild:sprite];
sprite.position = ccp( p.x, p.y);
// Define the dynamic body.
//Set up a 1m squared box in the physics world
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);
// initialise the variables responsible for managing interpolation
// as part of fixed time step implementation
b2Vec2 bodyPosition;
sprite.previousAngle = body->GetAngle();
sprite.smoothedAngle = sprite.previousAngle;
bodyPosition = body->GetPosition();
sprite.previousPosition = bodyPosition;
sprite.smoothedPosition = bodyPosition;
}
-(void) tick: (ccTime) dt
{
//It is recommended that a fixed time step is used with Box2D for stability
//of the simulation, however, we are using a variable time step here.
//You need to make an informed choice, the following URL is useful
//http://gafferongames.com/game-physics/fix-your-timestep/
//int32 velocityIterations = 8;
//int32 positionIterations = 1;
// Instruct the world to perform a single step of simulation. It is
// generally best to keep the time step and iterations fixed.
//world->Step(dt, velocityIterations, positionIterations);
// we dont call world->step, but use the fixed time step implementation
physics->update(dt);
//Iterate over the bodies in the physics world
for (b2Body* b = world->GetBodyList(); b; b = b->GetNext())
{
if (b->GetUserData() != NULL) {
//Synchronize the AtlasSprites position and rotation with the corresponding body
CCSprite *myActor = (CCPhysicsSprite*)b->GetUserData();
myActor.position = CGPointMake( b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO);
myActor.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
}
}
}
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//Add a new body/atlas sprite at the touched location
for( UITouch *touch in touches ) {
CGPoint location = [touch locationInView: [touch view]];
location = [[CCDirector sharedDirector] convertToGL: location];
[self addNewSpriteWithCoords: location];
}
}
- (void)accelerometer:(UIAccelerometer*)accelerometer didAccelerate:(UIAcceleration*)acceleration
{
static float prevX=0, prevY=0;
//#define kFilterFactor 0.05f
#define kFilterFactor 1.0f // don't use filter. the code is here just as an example
float accelX = (float) acceleration.x * kFilterFactor + (1- kFilterFactor)*prevX;
float accelY = (float) acceleration.y * kFilterFactor + (1- kFilterFactor)*prevY;
prevX = accelX;
prevY = accelY;
// accelerometer values are in "Portrait" mode. Change them to Landscape left
// multiply the gravity by 10
b2Vec2 gravity( -accelY * 10, accelX * 10);
world->SetGravity( gravity );
}
// on "dealloc" you need to release all your retained objects
- (void) dealloc
{
// in case you have something to dealloc, do it in this method
//delete world;
//world = NULL;
// we dont delete the weak reference to the physics world
// but rather delete the PhysicsSystem which deletes the world in its destructor
delete physics;
delete m_debugDraw;
// don't forget to call "super dealloc"
[super dealloc];
}
@end
As a test I changed the animation interval of CCDirector to 1/10th second, while keeping a fixed time step for physics at 1/60th second.
I dont understand the technique, just hacked it to work in Cocos2D.
Regards
i