I have spent quite a bit of time trying to optimize the performance of my 2D mining game. After a lot of research, experimentation, and testing, I finally found something very interesting that improved my performance pretty dramatically when rendering lots of tiles on the screen.
Initially I divided my textures up into texture atlases based on the way that I had seen many examples of texture organization for animations. I put all the textures for each tile in one directory with a name like dirtimages.atlas
, and then I loaded those textures into the game by iterating over the images in the directory and accounting for the number of formats I provided images for (there are different formats for smaller versus larger resolution devices). I had a folder for each set of textures, so dirtimages.atlas
, rockimages.atlas
, etc. This provided very clear organization in the code, and I liked it very much.
Unfortunately when profiling the number of draws of the scene, I was seeing pretty much one draw for every single tile (hundreds of draws). I was hoping that the draws would not exceed the number of atlases, but for some reason, splitting the images up into different atlases caused the number of draws to increase by a huge amount.
Here is my workaround for this problem. I consider it a dirty hack, but I think that this approach is the best one for performance. I do remember seeing a mention somewhere on the internet about putting all of the images in one texture atlas, but I cannot find that link. I did find this link on stack overflow about efficient texture atlas setups and it recommends pretty much what I have implemented here.
First I load up ALL of the textures into memory like this:
int numFormats = 3;
_tilesFromAtlas = textureAtlas(@"tileimages", @"tile", numFormats);
NSArray* textureAtlas(NSString *name, NSString *prefix, int numFormats) {
NSMutableArray *atlasImages = [[NSMutableArray alloc]init];
SKTextureAtlas *atlas = [SKTextureAtlas atlasNamed:name];
if (atlas) {
int numImages = (int)atlas.textureNames.count;
for (int i = 1; i <= numImages/numFormats; i++) {
SKTexture *texture = [atlas textureNamed:[NSString stringWithFormat:@"%@%d", prefix, i]];
[atlasImages addObject:texture];
}
}
return atlasImages;
}
Now when I am assigning a texture to a sprite in the scene, I have to keep track of the number of images that I have for each different tile. Also, the way I have this set up, they have to have the same number of images:
-(SKTexture *) textureForType:(BlockType)type damageLevel:(int)damageLevel {
int numImagesEach = 4;
switch (type) {
case BlockTypeDirt:
{
int amount = (type * numImagesEach) + damageLevel;
return _tilesFromAtlas[amount];
}
case BlockTypeRock:
{
int amount = (type * numImagesEach) + damageLevel;
return _tilesFromAtlas[amount];
}
default:
break;
}
return nil;
}
I would love to get feedback on this approach. I think that it suffers from readability problems, but there is no denying the huge performance increase. After making this change the number of draws is usually somewhere between 1 and 2, and sometimes goes up to 10 or so if I zoom out a ways.