snorge snorge - 9 months ago 155
Swift Question

SpriteKit tilemaps collision with curved or sloped floor tiles

I am planning a platforming game for iOS using SpriteKit and Swift. I did some research on how to handle collisions of the player sprite and stumbled upon this article.

That article advices you not to use the build-in physics engine of SpriteKit but to implement things like moving, jumping and collision handling on your own. The platform tutorial on Ray Wenderlichs site suggests a similar approach.

So far so good but let's talk about floor tiles on which the player can stand upon. A custom physics implementation would be easy as long as the tiles are rectangular and have a flat surface (like in the tutorial from Ray Wenderlich), since you would use CGRectIntersectsRect to detect a collision. But how would you check the player collision on curved or sloped tiles? From what I have read, CGRectIntersectsRect does not account for tranparent pixel inside a sprites rect.

Look at the above tile for example. The white area (upper left) would be transparent. Now, if the player sprite would drop on this tile, the collision would be detected at the upper border of the tiles rectangle, although there are no ground pixels there (blue area, lower right). So ultimately the player would hover in mid-air above this tile. I could push the player sprite down a few pixels but that is a bit hacky and gets harder if the curved floor tiles have different slope angles.

So the question is, how can I handle these types of collision with SpriteKit alone (no extra frameworks like Cocos2D, Kobold Kit, ...)? Or is this approach entirely wrong and collisions in platformer with SpriteKit should be done fundamentally different?

Any help is very much appreciated!

Answer Source

I disagree with not using physics to handle collisions and contacts. You are really just trying to reinvent the wheel here by not using physics and implementing your own custom code.

If you are using the Tiled app then assigning a physics body is a simple task. Use the Objects in Tiled to assign various body types. In your code you can then go about creating a specific body for each object type.

For example:

enter image description here

In the above image I have created a 45 degree right side sloped floor. The object I added is called floor45R.

The next step is to parse your map objects. In case of the 45floorR, you create a physics body like this:

NSArray *arrayObjects = [group objectsNamed:@"floor45R"];
    for (NSDictionary *dicObj in arrayObjects) {
        CGFloat x = [dicObj[@"x"] floatValue];
        CGFloat y = [dicObj[@"y"] floatValue];
        CGFloat w = [dicObj[@"width"] floatValue];
        CGFloat h = [dicObj[@"height"] floatValue];

        SKSpriteNode *myNode = [SKSpriteNode spriteNodeWithColor:[SKColor clearColor] size:CGSizeMake(w, h)];
        myNode.position = CGPointMake(x, y);
        myNode.zPosition = 100;
        CGMutablePathRef trianglePath = CGPathCreateMutable();
        CGPathMoveToPoint(trianglePath, nil, -myNode.size.width/2, myNode.size.height/2);
        CGPathAddLineToPoint(trianglePath, nil, myNode.size.width/2, -myNode.size.height/2);
        CGPathAddLineToPoint(trianglePath, nil, -myNode.size.width/2, -myNode.size.height/2);
        CGPathAddLineToPoint(trianglePath, nil, -myNode.size.width/2, myNode.size.height/2);
        myNode.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:trianglePath];
        myNode.physicsBody.dynamic = NO;
        myNode.physicsBody.restitution = 0;
        myNode.physicsBody.friction = 0.0;
        myNode.physicsBody.categoryBitMask = CategoryFloor45;
        myNode.physicsBody.collisionBitMask = 0x00000000;

        [worldNode addChild:myNode];

This works for any degree floor slope. Remember to set your player's and any other node's collision bit mask to collide with the floor.

In order for your player move smoothly over sloped floor, I recommend building the player's physics body in 2 pieces. A circle at the bottom and a rectangle at the top. The circle will prevent getting stuck in any potential cracks caused by 2 joining physics bodies.

SKPhysicsBody *firstBody = [SKPhysicsBody bodyWithCircleOfRadius:10 center:CGPointMake(0, 0)];
SKPhysicsBody *secondBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(5, 50) center:CGPointMake(0, 10)];
self.physicsBody = [SKPhysicsBody bodyWithBodies:@[firstBody, secondBody]];

You will have to adjust the position and center coordinates to match your sprite's image.