Most performant way to load textures from a Spritekit texture atlas - ios

I cannot figure out how to load my textures in a brief few seconds. It is taking a monster 20+ seconds.
Initially I had my animations in separate texture atlases. They are named something like sprite_002#2x~iphone.png. When they were separate I used code similar to that in the Apple documentation:
SKTextureAtlas *atlas = [SKTextureAtlas atlasNamed:#"monster"];
SKTexture *f1 = [atlas textureNamed:#"monster-walk1.png"];
SKTexture *f2 = [atlas textureNamed:#"monster-walk2.png"];
SKTexture *f3 = [atlas textureNamed:#"monster-walk3.png"];
SKTexture *f4 = [atlas textureNamed:#"monster-walk4.png"];
NSArray *monsterWalkTextures = #[f1,f2,f3,f4];
My code was:
let names = myAtlas.textureNames as [String];
for name in names {
myTextureArray.append(myAtlas.textureNamed(name as String));
}
For 6 animations in 6 different texture atlases w/ 250 frames each, it took about 6 seconds to load.
I was able to eliminate 60-70% of those frames and cram everything into a single texture atlas, but now I cannot get it to load in a reasonable time frame.
I have been trying code along these lines:
for i in 1...124 {
let name = String(format: "sprite_%03d", i);
let texture = imagesAtlas.textureNamed(name);
spriteTextures.append(texture);
}
Most games iPhone games don't have a 20 second load time, so I know I am doing something wrong here.

Your load time will be dependent upon the size of your atlases. So if you happen to have a large number of them and they are large, it will take some time.
I've seen variations of this type of question here before.
My suggestion would be to actually change your approach to the problem. This is breaking apart the workload and doing it in the background over time. This is why many games have loading screens.
Depending on your game, you may be able to be extra sneaky and load textures (or any asset for that matter) in the background while the game is playing. More often than not, you don't need to load all your textures at the start of a game.
When I make my games, one of the first things I do is create a background loader, if I don't already have one. I then build the game around the premise of async loading of assets.
20 seconds is pretty long. But based on your description, it sounds like it would take a non trivial amount of time.

Related

Preloading textures in SpriteKit

I've done some research and I can't seem to find anything that clearly explains how to go about preloading both single textures and textures within animations. I'm currently using Atlas's in Assets.xcassets to group related animation images. Does having my images in the Atlas mean that they are preloaded? As far as single images, does it make sense to declare the texture before GameScene like this: let laserImage = SKTexture(imageNamed: "Sprites/laser.jpg") and then (for example) within one of my SKSpriteNode subclass I can just pass laserImage through?
I ultimately wanted to know if there was a well defined way of going about this or if I should just store each texture as a constant before GameScene. Any advice on the proper (and most efficient) way of going about this would be great.
I tried to implement appzYourLife answer but it increased the time to load every texture. So I ended up putting all the most used Sprites in one single atlas and puting it in a singleton.
class Assets {
static let sharedInstance = Assets()
let sprites = SKTextureAtlas(named: "Sprites")
func preloadAssets() {
sprites.preloadWithCompletionHandler {
print("Sprites preloaded")
}
}
}
I call Assets.sharedInstance.preloadAssets() in menuScene. And:
let bg1Texture = Assets.sharedInstance.sprites.textureNamed("background1")
bg1 = SKSpriteNode(texture: bg1Texture)
to reference a texture already loaded in memory.
One Single Texture Atlas
Put all your assets into a single Sprite Atlas. If they don't fit, try at least to put all the assets of a single scene into a single Sprite Atlas
Preloading
If you want you can preload the texture atlas in memory with this code
SKTextureAtlas(named: "YourTextureAtlasName").preloadWithCompletionHandler {
// Now everything you put into the texture atlas has been loaded in memory
}
Automatic caching
You don't need to save a reference to the texture atlas, SpriteKit has an internal caching system. Let it do it's job.
Which name should I use to reference my image?
Forget the name of the file image, the name you assign to the image into Asset Catalog is the only name you will need.
How can I create a sprite from an image into a texture atlas?
let texture = SKTextureAtlas(named:"croc").textureNamed("croc_walk01")
let croc = SKSpriteNode(texture: texture)

SpriteKit Load Texture First Time Lag Even With Preloading

Here is my problem: Despite doing these:
1) Preloading all of my texture atlases
2) Having my atlases stored in Singletons so they are not disposed of by ARC
3) Having all my animation arrays stored in Singleton
SpriteKit STILL has a short "lag jitter" when adding enemies to my scene ONLY the first time they are added. After the texture is drawn once, it never lags again when that texture is added to the scene. Memory can be seen going up when the textures are drawn on the screen finally, which looks like they aren't getting preloaded.
I am currently preloading ALL of my texture atlases in my AppDelegate on startup using...
SKTextureAtlas.preloadTextureAtlases(primaryAtlas, withCompletionHandler: { () -> Void in
})
primaryAtlas is an array of all 4 of my atlases which are stored in a Singleton.
Let me point out some things I have already tried and what my current setup is.
1) The array of frames for my animations are stored in Singletons and get their images from the Atlases which are in a different singleton. They used to be in the same Singleton.
2) I've tried storing ALL my textures in a Singleton which were all loaded from the atlases. But currently my animations are stored, while my single textures are functions that fetch that texture from the atlases.
3) I've turned off all animations for enemies (don't run the SKAction) and the jitter still happens when they're added and not animating.
I've been trying to fix this for a very long time and there honestly doesn't seem to be a solution.
There seems to be a similar StackOverflow question I found here with no answer: Spritekit: First texture draw slow (preloaded)
Here is a picture of some analysis I did with Instruments.
Make sure that your texture atlases are used as singleton class properties, because if you create variables of texture atlases inside method scope which do preloading, they will be deallocated after method returns

How to use pixel art in an app?

I have an iOS app that uses sprite kit, and I am ready to add my artwork. The artwork is pixel-art and is inherently very small. I am trying to find the best way to display this in way where:
All of the art is the same size, meaning that one image pixel takes up exactly the amount of real world pixels as in all the other images.
There is no blurring in an attempt to make the textures look smoother, which often happens when scaling images up.
I have tried solving the second one like so:
self = [super init];
if(self){
self.size=size;
self.texture = [SKTexture textureWithImageNamed:#"ForestTree1.png"];
self.texture.filteringMode = SKTextureFilteringNearest;
[self.texture size];
}
return self;
The above code is in the initialization of the SKSpriteNode which will have the texture.
This is my original image (scaled up for easy reference):
The problem is that my result always looks like this:
(The bottom of the trunk being offset is not part of this question.) I am not using any motion blur or anything like it. I'm not sure why it isn't displaying correctly.
Edit 1:
I failed to mention above that the trees were constantly animating when the screenshots were taken. When they are still they look like this:
The image above is of two trees overlapping with one flipped caused because of a bug to be fixed later. My question is now how can I prevent the image from blurring while animation is occurring?
Edit 2:
I am adding multiple instances of the tree, each one loading the same texture. I know it as nothing to do with the animation because I changed the code to add just one tree and animate it, and it was pixelated perfectly.
You need to use "nearest" filtering:
self.texture.filteringMode = SKTextureFilteringNearest;
The pixels in your image must correspond with pixels on the screen perfectly.
If your image is 100x100, and you display it over a whole screen that is 105x105, it will do interpolation to figure out how to do it.
If you display it at a scaled resolution of some multiple of 2 (which should work properly), I think you still have to tell the renderer not to interpolate pixels when it does the scaling.
I've solved the problem...but its really a hack. I have a SKScene which is the parent node to all of the "trees" (SKSpriteNodes). This scene will be adding multiple trees to itself. At first I thought that this was some sort of problem because if I only added one tree, it would display the image correctly. The answer to this question led me to believe that I would need to programmatically create a SKTextureAtlas singleton in the (the texture is in a SKTextureAtlas) and pass it to the tree class to get the texture from on an init method. I made a property in the SKScene to hold the texture atlas so that I could pass it to the tree class every time I made a new one. I tried loading the texture from texture atlas (in the tree class) using the textureNamed: method. This still did not work. I switched back to loading the texture with SKTexture's textureWithImageNamed: method and it worked. Further more I changed to code back so that the tree subclass would not be sent the SKTextureAtlas singleton at all and it still worked.
In the SKScene I get the texture atlas using:
[SKTextureAtlas atlasNamed:#"Textures"]; //Textures is the atlas name.
and set the return value to be the SKTextureAtlas property described above. I thought that maybe the atlas just had to initialized at some point in the code, so I tried this:
SKTextureAtlas *myAtlas = [SKTextureAtlas atlasNamed:#"Textures"];
and the following alone on one line:
[SKTextureAtlas atlasNamed:#"Textures"]
but neither worked. Apparently I need to have a property in my tree's parent class which is the SKTextureAtlas which holds the texture which the tree uses without any reference to a SKTextureAtlas whatsoever... Is this a glitch or something? It's working now but it feels like a hack.
[self setScaleMode:SKSceneScaleModeAspectFill];
SKTexture* texture = [SKTexture textureWithImageNamed:#"image"];
[texture setFilteringMode:SKTextureFilteringNearest];
SKSpriteNode* imageNode = [SKSpriteNode spriteNodeWithTexture:texture];
[self addChild:imageNode];
Works perfectly for me. There's no blur with animation

Preloading Sprite Sheets with cocos2d

I have a game which has many unique units, and each unit has its own sprite sheet (because each unit has several animations), meaning there may be upwards of a dozen sprite sheets used by the game on any given scene. When the scene with the units is created, the initialization of all these assets takes a lot of time (a couple seconds). Specifically, the UI is locked while this code is run:
NSString *unitSheetName = [self getUnitSheetName];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:unitSheetName];
CCSprite *frame = [CCSprite spriteWithSpriteFrameName:kDefaultFrameName];
// etc...
However, if I then pop the scene, and then create a new version of the same scene (and push it), the loading takes a fraction of the time (the UI does not lock). Thus, it seems the UI locking is due to the initial caching of the sprite sheets...
My hope was that I'd be able to avoid this lag by calling the CCSpriteFrameCache's addSpriteFramesWithFile method while the game is starting up, but this does not seem to work. Despite doing this, the initial load still takes a long time.
Is there any good, effective way to preload the assets? I don't mind adding a second or two to my startup loading screen, if only I can be sure that the UI will later not be locked when the scene is pushed...
Per #LearnCocos2D's reply, above, here's how to properly and fully pre-load a sprite sheet, (fpSheet is the file path of the sheet):
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:fpSheet];
NSDictionary *spriteSheet = [NSDictionary dictionaryWithContentsOfFile:fpSheet];
NSString *fnTexture = spriteSheet[#"metadata"][#"textureFileName"];
[[CCTextureCache sharedTextureCache] addImage:fnTexture];

How to load animation in background in cocos2d?

I am currently developing one cocos2d game for iPad, in that game lot of animations is there. I have previously used zwoptex for creating spritesheet and adding animations in my project.
In my game 10 Levels is there, and after each level completed one animation will play, the animation is different for each level and animation image size is same as device screen size. so i am not create spritesheet file, instead of i am loading image directly. My problem is while animation it takes too much time for animation playing.
How to i fix it? please any one guide me. is it possible to load full screensize image(1024x768) in plist, because total 10 levels each level has 20 frames for animation, so 10X20 = 200 images need to load spritesheet.
I am using this code for animation
CCAnimation *animation = [CCAnimation animation];
for(int i=1;i<=20;i++)
{
[animation addFrameWithFile:[NSString stringWithFormat:#"Level%dAni%d.png",level,i];
}
animation.delayPerUnit = 0.3f;
animation.restoreOriginalFrame = YES;
id action = [CCAnimate actionWithAnimation:animation];
My Question is it possible to load full screen animations with spritesheet? and animation loading time is differ and it takes too much time how to fix it?
Please help me..
Do the math and figure out the amount of memory you would need for 200 pics at 1024x768 pixels. Way too much memory for any iSomething device.
If you have a performance problem (ie are you running this on a device?), then there are two things you can do to improve image load speed:
Convert your images to .pvr.gz (i recommend TexturePacker for this). They load significantly faster than .png.
Use an RGBA4444 pixel format (again you can do this with TexturePacker), and set the texture format in cocos just prior to loading the images. Images will be smaller, take much less memory.
In your code, where you do the anim
[CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGBA4444];
// load your images and do your anim
...
// at the completion of the anim
[CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGBA8888];
By Using Cocos2D you can animate sprite sheet images easily.
Try following examples using Cocos2D
http://www.smashious.com/cocos2d-sprite-sheet-tutorial-animating-a-flying-bird/309/ http://www.raywenderlich.com/1271/how-to-use-animations-and-sprite-sheets-in-cocos2d
Try following examples without using Cocos2D
http://www.dalmob.org/2010/12/07/ios-sprite-animations-with-notifications/
http://developer.glitch.com/blog/2011/11/10/avatar-animations-in-ios/
Use NSThread and load your animation in this thread.
NSThread *thread = [[[NSThread alloc] initWithTarget:self selector:#selector(preloadFireWorkAnimation) object:nil] autorelease];
[thread start];
-(void)preloadFireWorkAnimation
{
// load your animation here;
}

Resources