Initially I just implemented plain bounding box collision detection between my main game player and Tiled Map objects (obstacles on the map), but realized it is not perfect solution since obstacles are not always tailored by tile size. So, I decided to do it using Box2D. When I started working on it and searching for similar implementations or tutorials, I realized that there hard to find any explaining this very common requirement which motivated me to write this tutorial. Note that in my case collision means main game player hits the obstacles on the Map and explodes, not collision in sense of moving on some kind of platform. Also, Tiled Map is placed on top of Parallax, but should not be much of difference in case your Tiled Map is child of main scene layer whatsoever.
This tutorial builds upon basics of using Box2D for collision detection covered in great Ray Wenderlich tutorial, so I suggest reading it first. I will not talk here again about Box2D bodies and ContactListeners.
First, you need to define your collision objects on Tiled Map using Object Layer (I am calling it CollisionOL).
On Tiled Map initialization, we need to create Box2D bodies for collision objects on Tiled Map we have just created on our Object Layer. Note that in my case Tiled Map is not sized over complete scene, and I have more then 1 Tiled Map on it, so needed to do some more calculations (getWorldPosition). If you are not using it on such way, your _point should be defined with x, y as got from Object Layer.
-
–(void) drawCollisionTiles:(CCTMXTiledMap *)tiledMap
-
{
-
CCTMXObjectGroup *collisionObjects = [tiledMap objectGroupNamed:@"CollisionOL"];
-
NSMutableDictionary * objPoint;
-
-
int x, y, w, h;
-
for (objPoint in [collisionObjects objects]) {
-
x = [[objPoint valueForKey:@"x"] intValue];
-
y = [[objPoint valueForKey:@"y"] intValue];
-
w = [[objPoint valueForKey:@"width"] intValue];
-
h = [[objPoint valueForKey:@"height"] intValue];
-
-
if (y < 0) y = 0;
-
-
CGPoint _point = [self getWorldPosition:x withY:y withTiledMap:tiledMap];
-
CGPoint _size = ccp(w, h);
-
_point = ccpAdd(_point, ccp(_size.x/2, _size.y/2));
-
-
[self makeBox2dObjAt:_point withSize:_size dynamic:false];
-
}
-
}
-
-
–(void) makeBox2dObjAt:(CGPoint)p withSize:(CGPoint)size dynamic:(BOOL)d
-
{
-
b2BodyDef bodyDef;
-
-
if(d) bodyDef.type = b2_dynamicBody;
-
bodyDef.position.Set(p.x/PTM_RATIO, p.y/PTM_RATIO);
-
-
CollisionGameObject *obstacle = [[CollisionGameObject alloc] init];
-
[obstacle setType:kGameObjectObstacle];
-
obstacle.position = p;
-
bodyDef.userData = obstacle;
-
-
b2Body *body = _world–>CreateBody(&bodyDef);
-
-
// Define another box shape for our dynamic body.
-
b2PolygonShape dynamicBox;
-
dynamicBox.SetAsBox(size.x/2/PTM_RATIO, size.y/2/PTM_RATIO);
-
-
// Define the dynamic body fixture.
-
b2FixtureDef fixtureDef;
-
fixtureDef.shape = &dynamicBox;
-
fixtureDef.density = 0.0f;
-
fixtureDef.friction = 1.5f;
-
fixtureDef.restitution = 0;
-
fixtureDef.isSensor = true;
-
-
body–>CreateFixture(&fixtureDef);
-
}
That’s it. Now, the tricky part. You need to assure that your collision objects (obstacles) are moving with your scene layer, or in my case Parallax Node. So in your tick method, together with scene movement, we should move Box2D objects accordingly.
-
// parallax movement
-
CGFloat velx = backgroundXVelocity * aDelta;
-
CGPoint newPos = ccp(currentBackgroundPos.x + velx, currentBackgroundPos.y);
-
[parallaxBackground setPosition: newPos];
-
-
// moving collision objects as well
-
[self updateBoxBodyPosition:aDelta vel:velx];
Here I am distinguishing my main Game player (Manta) from obstacles. For obstacles, I am updating its world position according to scene movement and then updating the position of all Box2D objects based on the position of the Cocos2D sprites as is nicely explained in Ray tutorial. Note that my world has only X movement. You will need to take care about Y axe as well.
-
–(void) updateBoxBodyPosition:(float)dt vel:(float)v
-
{
-
int32 velocityIterations = 10;
-
int32 positionIterations = 10;
-
-
_world–>Step(dt, velocityIterations, positionIterations);
-
-
for(b2Body *b = _world–>GetBodyList(); b; b=b–>GetNext()) {
-
if (b–>GetUserData() != NULL) {
-
CCSprite *sprite = (CCSprite *)b–>GetUserData();
-
if (sprite != nil) {
-
-
// Handling obstacle position according to Parallax movement
-
if (sprite.tag != LevelLayerNodeTagManta) {
-
sprite.position = ccpAdd(sprite.position, ccp(v, 0));
-
}
-
-
b2Vec2 b2Position = b2Vec2(sprite.position.x/PTM_RATIO, sprite.position.y/PTM_RATIO);
-
float32 b2Angle = -1 * CC_DEGREES_TO_RADIANS(sprite.rotation);
-
b–>SetTransform(b2Position, b2Angle);
-
}
-
}
-
}
-
}
I am sure there are other, maybe better, ways of doing it, so if you have any suggestion of improvement, or this tutorial is not clear enough for you, just write the comment.
I will not include sample project including this code since you could get complete iUridium source from here 😉
.