0

I use JSTileMap to draw a level. I changed it a little bit to add SKPhysicsBody to every tile but sometimes when I apply some impulse to main character and he hits the wall/ground/ceiling he is acting weird. He reflects from surfaces with the way that is contrary to the principles of physics. I think that it happens because the player hits the point where two physics bodies (for example two physics bodies of the ground) connects. SKPhysicsBody class provides a method to create one physics body from different physics bodies + (SKPhysicsBody *)bodyWithBodies:(NSArray *)bodies; Can I use this method to create one physics body from ale the tiles physics bodies?

Here's a method from JSTileMap where I add physics bodies:

+(id) layerWithTilesetInfo:(NSArray*)tilesets layerInfo:(TMXLayerInfo*)layerInfo mapInfo:(JSTileMap*)mapInfo
{
    TMXLayer* layer = [TMXLayer node];
  layer.map = mapInfo;

    layer.tilesByColumnRow = [NSMutableDictionary dictionary];
    // basic properties from layerInfo
    layer.layerInfo = layerInfo;
    layer.layerInfo.layer = layer;
    layer.mapTileSize = mapInfo.tileSize;
    layer.alpha = layerInfo.opacity;
    layer.position = layerInfo.offset;

    // recalc the offset if we are isometriic
    if (mapInfo.orientation == OrientationStyle_Isometric)
    {
        layer.position = CGPointMake((layer.mapTileSize.width / 2.0) * (layer.position.x - layer.position.y),
                                     (layer.mapTileSize.height / 2.0) * (-layer.position.x - layer.position.y));
    }

    NSMutableDictionary* layerNodes = [NSMutableDictionary dictionaryWithCapacity:tilesets.count];

    //MY CODE
    NSMutableArray *arrayOfSprites = [[NSMutableArray alloc] init];
    SKNode *theSprite;
    //----||----

    // loop through the tiles
    for (NSInteger col = 0; col < layerInfo.layerGridSize.width; col++)
    {
        for (NSInteger row = 0; row < layerInfo.layerGridSize.height; row++)
        {
            // get the gID
            NSInteger gID = layerInfo.tiles[col + (NSInteger)(row * layerInfo.layerGridSize.width)];

            // mask off the flip bits and remember their result.
            bool flipX = (gID & kTileHorizontalFlag) != 0;
            bool flipY = (gID & kTileVerticalFlag) != 0;
            bool flipDiag = (gID & kTileDiagonalFlag) != 0;
            gID = gID & kFlippedMask;

            // skip 0 GIDs
            if (!gID)
                continue;

            // get the tileset for the passed gID.  This will allow us to support multiple tilesets!
            TMXTilesetInfo* tilesetInfo = [mapInfo tilesetInfoForGid:gID];
            [layer.tileInfo addObject:tilesetInfo];

            if (tilesetInfo)    // should never be nil?
            {
                SKTexture* texture = [tilesetInfo textureForGid:gID];
                SKSpriteNode* sprite = [SKSpriteNode spriteNodeWithTexture:texture];

                sprite.name = [NSString stringWithFormat:@"%ld",(long)(col + row * layerInfo.layerGridSize.width)];

                // make sure it's in the right position.
                if (mapInfo.orientation == OrientationStyle_Isometric)
                {
                    sprite.position = CGPointMake((layer.mapTileSize.width / 2.0) * (layerInfo.layerGridSize.width + col - row - 1),
                                                  (layer.mapTileSize.height / 2.0) * ((layerInfo.layerGridSize.height * 2 - col - row) - 2) );
                }
                else
                {
                    sprite.position = CGPointMake(col * layer.mapTileSize.width + layer.mapTileSize.width/2.0,
                                                  (mapInfo.mapSize.height * (tilesetInfo.tileSize.height)) - ((row + 1) * layer.mapTileSize.height) + layer.mapTileSize.height/2.0);
                }

                // flip sprites if necessary
                if(flipDiag)
                {
                    if(flipX)
                        sprite.zRotation = -M_PI_2;
                    else if(flipY)
                        sprite.zRotation = M_PI_2;
                }
                else
                {
                    if(flipY)
                        sprite.yScale *= -1;
                    if(flipX)
                        sprite.xScale *= -1;
                }

                // add sprite to correct node for this tileset
                SKNode* layerNode = layerNodes[tilesetInfo.name];
                if (!layerNode) {
                    layerNode = [[SKNode alloc] init];
                    layerNodes[tilesetInfo.name] = layerNode;
                }

                //adding physicsbody to every tile
                //sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(sprite.frame.size.width, sprite.frame.size.height)];
                //sprite.physicsBody.dynamic = NO;
                //sprite.physicsBody.categoryBitMask = mapCategory;

                //[arrayOfSprites addObject:sprite.physicsBody];

                [layerNode addChild:sprite];
                NSUInteger indexes[] = {col, row};
                NSIndexPath *indexPath = [NSIndexPath indexPathWithIndexes:indexes length:2];
                [layer.tilesByColumnRow setObject:sprite forKey:indexPath];

                //MY CODE
                sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.frame.size];
                sprite.physicsBody.dynamic = NO;
                [arrayOfSprites addObject:sprite.physicsBody];
                //-----||------

#ifdef DEBUG
//              CGRect textRect = [texture textureRect];
//              NSLog(@"atlasNum %2d (%2d,%2d), gid (%d,%d), rect (%f, %f, %f, %f) sprite.pos (%3.2f,%3.2f) flipx%2d flipy%2d flipDiag%2d", gID+1, row, col, [tilesetInfo rowFromGid:gID], [tilesetInfo colFromGid:gID], textRect.origin.x, textRect.origin.y, textRect.size.width, textRect.size.height, sprite.position.x, sprite.position.y, flipX, flipY, flipDiag);
#endif

            }
        }
    }

    //MY CODE
    NSArray *array = [arrayOfSprites copy];
    theSprite = [SKNode node];
    theSprite.physicsBody = [SKPhysicsBody bodyWithBodies:array];
    theSprite.physicsBody.dynamic = NO;
    theSprite.position = CGPointMake(layer.position.x+16, layer.position.y+16);
    [layer addChild:theSprite];
    //-----||------

    // add nodes for any tilesets that were used in this layer
    for (SKNode* layerNode in layerNodes.allValues) {
        if (layerNode.children.count > 0) {
            [layer addChild:layerNode];
        }
    }

    [layer calculateAccumulatedFrame];

    return layer;
}

After adding physics bodies to every tile and adding those physics bodies to NSMutableArray I assign a copy of this NSMutableArray to the NSArray and try to create one physics body out of it like this:

//MY CODE
        NSArray *array = [arrayOfSprites copy];
        theSprite = [SKNode node];
        theSprite.physicsBody = [SKPhysicsBody bodyWithBodies:array];
        theSprite.physicsBody.dynamic = NO;
        theSprite.position = CGPointMake(layer.position.x+16, layer.position.y+16);
        [layer addChild:theSprite];
        //-----||------

In result, one physics body with the height and width of one tile is added.

mfker
  • 21
  • 4

1 Answers1

1

If you want to use [SKPhysicsBody bodyWithBodies:array], then you need to make sure that all the bodies in the array are relative to the parent node.

sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.frame.size]; means that your physics body position is relative to the sprite node. You need the body relative to the parent node.

The only way I know how to do this is with center:

sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.frame.size center:sprite.position];

This should place the SKPhysicsBody at the location of the sprite when it is added to the parent node.

Knight0fDragon
  • 16,609
  • 2
  • 23
  • 44
  • Thanks, I can create one physics body made from all tiles but the weird behaviour didn't disappear. I thought that bodyWithBodies method will somehow connect all the bodies and remove the connections between them but it only helps you to treat all the bodies as one (like moving, positioning, etc.), my bad. – mfker Nov 11 '16 at 11:26
  • What weird behavior – Knight0fDragon Nov 11 '16 at 11:28
  • When I apply some impulse to main character and he hits the wall/ground/ceiling he is acting weird. He reflects from surfaces with the way that is contrary to the principles of physics. For example when he's moving right (according to the impulse applied) and he reflects from the ceiling sometimes he starts to move backward (left). – mfker Nov 11 '16 at 11:32
  • Mfker ok you are confusing me, joining the bodies does make it one body, so you no longer have gap issues. Are we now using one body (the combined body) for your tiles? – Knight0fDragon Nov 11 '16 at 11:35
  • Please look at the picture [link](https://s12.postimg.org/a9008jjj1/explanation.png), I apply some impulse and when my character comes to the "point of weird behaviour" it sometimes acts OK but sometimes it stops, sometimes starts to move backward if the impulse force was big. – mfker Nov 11 '16 at 11:53
  • I tried it when it was one body, and if they were separated bodies, it still acts the same. – mfker Nov 11 '16 at 11:56