Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSTilemap - Loading only visible tiles

Has anyone been successful in loading only the visible tiles using JSTilemap? The reason I ask is because my map size is much too large to load at once and I would either like to load segments individually or only dynamically load those tiles currently visible on screen.

I do not want to use KoboldKit.

Edit: Exciting update. Steffan and Marcus are working on a great looking toolkit over at TilemapKit.com. Can't wait to throw money their way.

like image 567
Calm Turtle Avatar asked May 04 '14 21:05

Calm Turtle


2 Answers

This has been done before. Have a look here on how to get JSTileMap to do what you are asking.

https://github.com/fattjake/JSTileMap/commit/01b5bacc8c5ccc1099e020276d204ba439a6d06c

Hope that helps!

Update from some time later: Initial research has suggested that this may not be the way to go, to let spriteKit handle optimizations for you. See this small discussion: https://github.com/slycrel/JSTileMap/issues/28

like image 128
slycrel Avatar answered Oct 20 '22 06:10

slycrel


I have used a segmented map approach in the past. This works for side scrolling maps where you simply add another piece onto the side. It also works for larger maps where side and bottom or top pieces are required.

An added benefit of using this approach is the ability, if desired, to randomize your map sections every time a new map is loaded. This eliminates the "same old map" for a specific level.

For the code example below I am using identical sized map sections with a 1280 width placed side by side. Here are the steps involved:

Create identical sized map sections in the Tiled app. Give each map section a unique name ending in a number. Something like this: MyMap-0, MyMap-1, MyMap-2 and so on.

Create a mutable array which will hold your map sections. Let's call it mapSectionsArray.

Create a float ivar called mapOffsetX and set its initial value to zero.

If you are looking to create a static map with 10 sections, use this code:

for (int i = 0; i < 10; i++) {
    NSString *myString = [NSString stringWithFormat:@"MyMap-%d.tmx",i];
    JSTileMap *tiledMap = [JSTileMap mapNamed:myString];
    tiledMap.position = CGPointMake(mapOffsetX, 0);
    tiledMap.zPosition = 100;

    [worldNode addChild:tiledMap];
    [mapSectionsArray addObject:tiledMap];
    mapOffsetX += 1280;
}

If you are looking to create a random map with 10 sections, use this code:

// create an array with the total number of sections created.
// in this example I have created 30 sections in total
// I make sure not to load the same section twice
NSMutableArray *myArray = [[NSMutableArray alloc] initWithObjects:@"0", @"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", @"10", @"11", @"12", @"13", @"14",@"15", @"16", @"17", @"18", @"19", @"20", @"21", @"22", @"23", @"24", @"25", @"26", @"27", @"28", @"29", nil];

for (int i = 0; i < 10; i++) {
    int r1 = arc4random() % [myArray count];

    NSString *myString = [NSString stringWithFormat:@"MyMap-%@.tmx",[myArray objectAtIndex:r1]];
    JSTileMap *tiledMap = [JSTileMap mapNamed:myString];
    tiledMap.position = CGPointMake(mapOffsetX, 0);
    tiledMap.zPosition = 100;

    [worldNode addChild:tiledMap];
    [mapSectionsArray addObject:tiledMap];
    mapOffsetX += 1280;

    [myArray removeObjectAtIndex:r1];
}

To load items in a map's object layer you can do this:

-(void)loadMapObjects {
    float xOffset;
    xOffset = 0;

    for (int i = 0; i < [mapSectionsArray count]; i++) {
        TMXObjectGroup *group = [[mapSectionsArray objectAtIndex:i] groupNamed:@"ObjectLayerName"];

        NSArray *arrayObjects = [group objectsNamed:@"objectName"];
        for (NSDictionary *dicObj in arrayObjects) {
            SKNode *myNode = [SKNode node.....];
            CGFloat x = [dicObj[@"x"] floatValue];
            CGFloat y = [dicObj[@"y"] floatValue];
            myNode.position = CGPointMake(x+xOffset, y);

            [worldNode addChild:myNode];
        }
    xOffset += 1280;
}

In the update method you can add or remove selected map section based on your player's position like this:

// the exact values are determined by your view's size
// and map section's width
left = player.position.x - 1500;
right = player.position.x + 1500;
up = player.position.y + 1000;
down = player.position.y - 1000;

    for(JSTileMap *object in mapSectionsArray) {

        if((object.position.x > left) && (object.position.x < right) && (object.position.y > down) && (object.position.y < up)) {
            if(object.parent == nil) {
                [worldNode addChild:object];
            }
        } else {
            if(object.parent != nil) {
                [object removeFromParent];
            }
        }
    }

Based on the player's position, a map section is either removed or added to the worldNode parent.

Another optimization is to also remove (from parent) objects with physical bodies when they are not in view. This might or might not be feasible depending on your game's design and logic.

You can handle this in the update method:

left = player.position.x - 1500;
right = player.position.x + 1500;
up = player.position.y + 1000;
down = player.position.y - 1000;

for(SKSpriteNode *object in mapItemsArray) {
    if((object.position.x > left) && (object.position.x < right) && (object.position.y > down) && (object.position.y < up)) {
        if(object.parent == nil) {
            [worldNode addChild:object];
        }
    } else {
        if(object.parent != nil) {
            [object removeFromParent];
        }
    }
}
like image 2
sangony Avatar answered Oct 20 '22 06:10

sangony