There were some good questions during my presentation last night and via email this morning about units in Box2D.
@jasonkrogh asked what kind of units Box2D uses. Here’s the relevant bit about units from the Box2D manual:
Box2D works with floating point numbers, so some tolerances have to be used to make Box2D perform well. These tolerance have been tuned to work well with meters-kilogram-second (MKS) units. In particular, Box2D has been tuned to work well with moving objects between 0.1 and 10 meters. So this means objects between soup cans and buses in size should work well.
When you’re working with on screen objects like balls and squares, what’s important is that the units are not pixels.
@satefan asked:
For my little demo I set the dimensions of the world to 320 meters wide by 480 meters high. This seems to work fine and I don’t have to do any coordinate translations. Just curious… was there a reason why you decided not to do this?
The short answer: the physics won’t look right because your bodies and shapes will be much larger, heavier and faster than Box2D is expecting. Here’s what the manual has to say:
Being a 2D physics engine it is tempting to use pixels as your units. Unfortunately this will lead to a poor simulation and possibly weird behavior. An object of length 200 pixels would be seen by Box2D as the size of a 45 story building. Imagine trying to simulate the movement of a high-rise building with an engine that is tuned to simulate ragdolls and barrels. It isn’t pretty.
Let’s do the math for the the balls in the example game on the iPhone. If we set the world size to match the screen, then the world would be 320 meters across, 480 meters tall and 1 pixel on screen would equal 1 meter in Box2D. If the ball had a diameter of 64 pixels (which is does), it would have a diameter of 64 meters in the physics engine – that’s pretty small on screen, but more than six times the size of Box2D’s recommend 10 meter maximum.
Here’s an example of where this becomes a problem: Box2D has setting for the maximum velocity any body can have and the default value is 200 meters per second (you can see all of Box2D’s settings in b2Settings.h). Because things are so big, they’re also very heavy (think about how much a 64 meter wide ball would weigh!). To make them move, you have to crank gravity way, way up and apply a lot of force. You quickly run into the limit and everything ends up moving around at the same speed (which looks terrible, trust me). Rather than fiddling with the (many) settings, the best thing to do is a simple translation between screen coordinates and world coordinates:
// MyGameViewController.h
#define SCREEN_TO_WORLD(n) ((n) / 30)
#define WORLD_TO_SCREEN(n) ((n) * 30)
In the macros above, 30 is an arbitrary value. The idea is to choose a value that will convert the size of an object in pixels to a value that falls in the 0.1 meter to 10 meter sweet spot. By dividing the ball’s 64 pixel screen diameter by 30 in the macro gives it a diameter of 2.13 meters in the world. What is a relatively small object on screen is now a relatively small object in the world – it also weighs less, so you can use smaller forces and don't run into the maximum velocity limit.
Just make sure that any time you set any linear value (position, size, force) in the world, convert from screen coordinates to world coordinates. When you read any linear value from the world, convert from world coordinates back to screen coordinates. I like to use custom accessors to make it easier:
// Ball.m
- (CGPoint)position {
if([self body]) {
return CGPointMake(
WORLD_TO_SCREEN(body->GetPosition().x),
WORLD_TO_SCREEN(body->GetPosition().y)
);
} else {
return CGPointMake(
WORLD_TO_SCREEN(bodyDef->position.x),
WORLD_TO_SCREEN(bodyDef->position.y)
);
}
}
- (void)setPosition:(CGPoint)aPosition {
NSAssert(![self body], @"Cannot set position");
[self bodyDef]->position.Set(
SCREEN_TO_WORLD(aPosition.x),
SCREEN_TO_WORLD(aPosition.y)
);
}