8

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.

Calm Turtle
  • 292
  • 3
  • 18
  • 1
    Just wondering how big is your tile map? I am running a 75 by 75 with 32 pixel tiles with no issues. I tried using JSTileMap back when I started working with Tiled, but found it better to roll my own using the exported JSON file. May not be super helpful but my project is located here https://github.com/Urthstripe29/Old-Frank It won't be as easy to use as JSTileMap but you might find something useful in the Map.h/.m file. I got my load time down to .2 seconds and that is with adding CoreData to store the map after first creation. – Skyler Lauren Apr 05 '15 at 20:06
  • When you say segments, do you mean you want to load in chunks (20 tiles - width at a time) or do you mean load whenever a tile comes into view? – sangony Apr 05 '15 at 20:07
  • Thanks for the comments. When the tile comes within view. Map is approx. 400x300 tiles at 32 pixels. I have done a segmented approach before. Just seems sloppy, and would prefer the memory savings of just in view. – Calm Turtle Apr 06 '15 at 00:07
  • Wow now that is big =) I have tried doing a just in view +1 on each side, but it seemed I had too many issues popping and pushing sprites onto the scene. By issues I mean I got got a noticeable fps loss. Although this was before I was reusing textures correctly so that was likely the reason. Also I was likely creating new and remove old instead of just repositioning and changing textures. Oh the good old days. Best of luck I favorited and hope you find a good solution. – Skyler Lauren Apr 06 '15 at 01:12
  • @BigE It has been a while but if you are still working on this you may be interested in this and its culling feature. https://github.com/SpriteKitAlliance/SKAToolKit I had another individual with a very large map and got the performance he wanted out of this. If you get a chance to try it out shoot me an email and let me know what you think. – Skyler Lauren Jun 21 '15 at 16:12

2 Answers2

2

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

slycrel
  • 4,275
  • 2
  • 30
  • 30
  • 1
    It looks like FattJake is going in the right direction, but his mod only shows the bottom most layer (smallest z-value). With your original class I see all three layers, with his mods I only see the bottom most. I will keep looking at it to see if I can identify the reason. – Calm Turtle May 10 '14 at 16:49
  • I have spoken with Jake about his mod of JSTileMap. He confirmed that his use case only had 1 tile layer and it was only tested as such. He does plan to take a look at it soon. Using the sample project I have tested the implementation and it is bugged for multiple layers. – Calm Turtle May 14 '14 at 14:48
  • Thanks for the update. It's on my list (but hasn't made it to the top obviously) to check into and integrate this into the main code branch. I'm very interested in the results you find here. – slycrel May 14 '14 at 16:33
  • @slycrel - In the JSTileMap.m +(id)layerWithTilesetInfo:(NSArray*)tilesets layerInfo:(TMXLayerInfo*)layerInfo mapInfo:(JSTileMap*)mapInfo method, [layerNode addChild:sprite]; adds the actual SKSpriteNode. Is there already a NSArray class property to access these individual nodes? – sangony Apr 05 '15 at 21:47
  • @sangony Have a look at the tileAt: and tileAtCoord: methods for getting individual tiles. – slycrel Apr 07 '15 at 13:59
  • @slycrel - Thanks for the reply. I looked at those two methods. They seem to only return an individual node for the specified coordinates. I am looking to either access or created a array with all the tiles (of all layers). Reason being that with such an array, I could check each node's position in relation to a specific node (e.g. player node) and then remove or add to parent accordingly. I've looked but I don't think you have such a class property array. I tried creating one but got lost in the process. Where would I declare and add to such an array? – sangony Apr 07 '15 at 14:10
  • FYI, this has been discussed outside of SO here: https://github.com/slycrel/JSTileMap/issues/28 – slycrel Apr 07 '15 at 22:11
2

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];
        }
    }
}
sangony
  • 11,636
  • 4
  • 39
  • 55
  • Awarding the bounty here, even if it's not exactly what I'm looking for. While I have done a similar implementation before, there currently isn't a solution available. At least this answer helps anyone looking for a good stopgap. Thanks for the efforts @sangony. – Calm Turtle Apr 11 '15 at 19:30
  • @BigE - Appreciate that! I spent a couple hours with the JSTileMap class and tried to get some additional help from Slycrel but I just couldn't get a class property array set up. I see where each actual tile is created in the class but I don't know of any way to get the data outside of that method. I tried the github project but it did not work for me. The segmented map loading helped me out a lot. If you combine that with the physical bodies objects add & remove, you can actually get a significant FPS speed boost. If I ever come across a way to do what you asked, I will post another answer. – sangony Apr 11 '15 at 19:46