patternMinor
Dirty but Efficient Texture Atlas in Spritekit
Viewed 0 times
butefficientatlasspritekitdirtytexture
Problem
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
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
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
Solution
Just some quick minor complaints...
If braces are absolutely necessary in a
But this seems unnecessary altogether. We can refactor the
Now, I'm not sure I actually particularly like cramming all that inside the brackets, and the extra variable doesn't hurt, so we could keep it in the switch, but still, let's combine these cases and fix our brackets:
Our code has some borderline magic numbers:
I'll deal with the latter first. It's defined as the first line of a function, it's only used within the function, and it's used as a constant.
The other variable is a little hard to say. It may be that it's simply out of context, and in reality, might be different values, as this number is passed into the function that actually uses it. I'll let you use your best judgment on that one, but you should think about it.
This is an example of why I default to
If braces are absolutely necessary in a
switch, there's no reason why they shouldn't also be Egyptian-style like everywhere else in your code:case BlockTypeDirt: {
int amount = (type * numImagesEach) + damageLevel;
return _tilesFromAtlas[amount];
}But this seems unnecessary altogether. We can refactor the
switch statement as such:switch(type) {
case BlockTypeDirt:
case BlockTypeRock:
return _tilesFromAtlas[(type * numImagesEach) + damageLevel];
default:
return nil;
}Now, I'm not sure I actually particularly like cramming all that inside the brackets, and the extra variable doesn't hurt, so we could keep it in the switch, but still, let's combine these cases and fix our brackets:
switch(type) {
case BlockTypeDirt:
case BlockTypeRock: {
int amount = (type * numImagesEach) + damageLevel;
return _tilesFromAtlas[amount];
}
default:
return nil;
}Our code has some borderline magic numbers:
int numFormats = 3;
int numImagesEach = 4;I'll deal with the latter first. It's defined as the first line of a function, it's only used within the function, and it's used as a constant.
static int const kNumberOfImagesEach = 4;The other variable is a little hard to say. It may be that it's simply out of context, and in reality, might be different values, as this number is passed into the function that actually uses it. I'll let you use your best judgment on that one, but you should think about it.
int numImages = (int)atlas.textureNames.count;This is an example of why I default to
NSInteger rather than int in all of my Objective-C code--that's what Apple has defaulted to, and I'd rather not have to cast all of my returns from Apple's methods, which you'll probably find that on balance, you're using Apple methods more than any other single developer's methods.Code Snippets
case BlockTypeDirt: {
int amount = (type * numImagesEach) + damageLevel;
return _tilesFromAtlas[amount];
}switch(type) {
case BlockTypeDirt:
case BlockTypeRock:
return _tilesFromAtlas[(type * numImagesEach) + damageLevel];
default:
return nil;
}switch(type) {
case BlockTypeDirt:
case BlockTypeRock: {
int amount = (type * numImagesEach) + damageLevel;
return _tilesFromAtlas[amount];
}
default:
return nil;
}int numFormats = 3;
int numImagesEach = 4;static int const kNumberOfImagesEach = 4;Context
StackExchange Code Review Q#67059, answer score: 3
Revisions (0)
No revisions yet.