1

I am writing an OS X SpriteKit app. I am transitioning from my main menu scene to the main scene, but the transition takes about 3 seconds to start. In my main scene, I have an onscreen piano that is generated programmatically. There are about 55 sprites that need to be loaded. Is there any way to speed this up?

In my main SKScene LevelScene, this is what the didMoveToView method is calling:

-(void)didMoveToView:(SKView *)view {
    _piano = [[Keyboard alloc] init]; //creates on screen piano keyboard
    [self addChild:_piano]; 
    [_piano setPosition:[[self childNodeWithName:@"piano"] position]];
    [_piano setAnchorPoint:CGPointMake(1, 0)];

    _midiController = [[MIDIController alloc] initWithAudio:YES];

    [self initGameStateMachine];
    [self initLevel]; //this involves loading a couple more sprites onto the screen
}
02fentym
  • 1,762
  • 2
  • 16
  • 29
  • Show some code? Loading 55 sprites should be near instantaneous and how big is the image texture of the sprite? – Wraithseeker Jul 09 '15 at 06:17
  • All of the objects that are being loaded are in several different classes so that would be a lot of code to post here. The image texture of half of the sprites is 110x478 pixels. The other sprite is smaller than that. I'll put my `didMoveToView` method up top since that's where everything is being created from. – 02fentym Jul 09 '15 at 06:49
  • Difficult to make any sound suggestions without detailed knowledge of your code. If you absolutely need everything to load before game play starts, add a "loading" message. If you do not need everything loaded beforehand, load only what you need for the first few seconds of game play and then laid the rest in the background. – sangony Jul 09 '15 at 15:23
  • I agree with this approach. If I were to use this method of loading resources, would I just use GCD to load all of the other scene resources on a separate thread and load the main menu resources on the main thread? Perhaps I'm way off base, but I think I remember seeing code like this before. Also, I just realized that every time I call `didMoveToView` for each scene it's reloading al of the resources I think. For some reason, it's only the first time a SKScene is loaded that it is slow. – 02fentym Jul 10 '15 at 00:24

1 Answers1

2

Load all of your textures once and keep a reference to them. You can store all of your textures in a shared array or dictionary. In fact you can create a class to manage this. This way all of your textures will already be loaded into memory so creating nodes should be much faster which will result in fast scene transitions.

Also there is a bug on Yosemite, loading a sprite by name from an atlas takes an extremely long time (and actually will stall the game) when the texture is part of an atlas. I'm guessing it's a bug related to inefficiently searching the path of the atlas. Manually loading from the atlas is a workaround. Not sure if this bug effects you but I figure I mention it anyway. Regardless, doing what I said above should fix your problem.


Update to answer to include code:
I wrote some code real quick to show you what I mean by loading and unloading assets. In this example we have a singleton class, the SharedAssetsManager, that is in charge of loading and unloading your assets from memory. It's best practice to keep all of your textures in an atlas when you can for performance reasons. If you have some textures that aren't (which I included in the sample code) you can see that it must be manually added to the dictionary (although you probably could come up with a faster solution such as grouping the files or using a plist to describe the names of the images). In the code below you can see an example of loading the assets then unloading them. If you game is small enough you can load them once in the AppDelegate or some equivalent area, but if your game it too large you will need to dynamically load between scenes possibly with a loading screen which you can see an example of here: Lastly, you can see I use constants to refer to the filenames instead of hard-coding them. You shouldn't do this for sprite frame animations, such as Walk_0,Walk1_1,Walk_2 etc. Instead you can come up with another class for managing the animations. Hopefully this code provides a good starting point.

import SpriteKit

struct FileNameConstants {
    static let LEVEL1_ATLAS = "Level1_Atlas"
    static let SOME_TEXTURE1 = "Some_Texture_In_Atlas"
    static let SOME_TEXTURE2 = "Some_Texture_Not_In_Atlas"
}

class SharedAssetsManager {
    static let sharedInstance = SharedAssetsManager()

    //Keep these private for safety.
    private init() {}
    private(set) var level1Assets: [String : SKTexture]!

    func getAssetsDictionaryFromAtlasNamed(atlasNamed: String) -> [String : SKTexture] {
        let atlas = SKTextureAtlas(named: atlasNamed)
        var textures: [String : SKTexture] = Dictionary(minimumCapacity: atlas.textureNames.count)
        for textureName in atlas.textureNames as [String] {
            textures[textureName.stringByDeletingPathExtension] = atlas.textureNamed(textureName)
        }
        return textures
    }

    func loadLevel1Assets() {
        level1Assets = getAssetsDictionaryFromAtlasNamed(FileNameConstants.LEVEL1_ATLAS)
        //Textures that are not part of the atlas but should be part of level1 assets can be added here:
        level1Assets[FileNameConstants.SOME_TEXTURE2] = SKTexture(imageNamed: FileNameConstants.SOME_TEXTURE2)

    }
    func unloadLevel1Assets() {
        level1Assets = nil
    }
}

//When loading level 1:
let sam = SharedAssetsManager.sharedInstance
sam.loadLevel1Assets()

//When assigning textures:
let someNode1 = SKSpriteNode(texture: sam.level1Assets[FileNameConstants.SOME_TEXTURE1]!)
let someNode2 = SKSpriteNode(texture: sam.level1Assets[FileNameConstants.SOME_TEXTURE2]!)

//When cleaning up (if needed).
sam.unloadLevel1Assets()
Community
  • 1
  • 1
Epic Byte
  • 33,840
  • 12
  • 45
  • 93
  • What would be the best practice for loading them in that shared class? Is hardcoding the file names appropriate or is there a better and more flexible way of doing this? – 02fentym Jul 10 '15 at 00:22
  • @02fentym Depends on your implementation. You can map the file names to a key value then use the key to retrieve the texture from a dictionary. If you only use atlases you can actually build your dictionary at run time from the atlas by iterating through the atlas textures and use the name as the key. I actually don't have one large dictionary but instead many dictionaries because I have so many assets the device would run out of memory. So I have methods for loading and unloading each dictionary. I organize the assets by where they appear. – Epic Byte Jul 10 '15 at 01:58
  • @02fentym For example menu assets, level 1 assets, global assets, etc. – Epic Byte Jul 10 '15 at 01:59
  • @02fentym If your game is small enough then you might be able to load everything into memory and not worry about dynamically loading. – Epic Byte Jul 10 '15 at 02:00
  • That bug that you said was in Yosemite for loading sprites by name, what do you mean by that? Also, when you say `You can map the file names to a key value then use the key to retrieve the texture from a dictionary` can you provide a little more detail? What does that look like? As my projects get bigger and bigger they just get messier sometimes and I want to make sure I'm using best practices. – 02fentym Jul 11 '15 at 02:18
  • @02fentym I'll edit my answer to include some sample code when I get a chance. – Epic Byte Jul 11 '15 at 12:53
  • Hmm...I can more or less read Swift code, but I only code in objective C. I'm not sure about that `private init()` part that you wrote. What would that portion of your code be in Obj C? Also, for all of the textures that are loaded in the level 1 dictionary, their texture names are generated via their file names correct? When you are mapping the textures to a sprite how do you know what sprite gets what texture? Is this what you would use a plist for? Other than hardcoding the names, what would be the method for making this pairing for each sprite? – 02fentym Jul 13 '15 at 01:07
  • Does that `private init()` portion of code ensure that the dictionary of textures is only instantiated once or am I completely off? – 02fentym Jul 13 '15 at 01:15
  • 1
    @02fentym Oops, so use to Swift I forgot your post was in Obj-C. Every method is public in Obj-C so you can ignore the access control. Yes the names in the atlas are used as the keys in the dictionary. When you are mapping the texture to a sprite, you should be able to determine what the texture should be. See this really depends on how you game is setup. If you have a lot of nodes in the world and you want to be able to pair them you will certainly need to use a plist. You can make a plist that contains all of the nodes positions, rotation, the texture keys etc. Then parse this and – Epic Byte Jul 13 '15 at 01:19
  • create all the nodes dynamically. – Epic Byte Jul 13 '15 at 01:20
  • If you are using Xcode 7, you can create the nodes in the editor and set a custom class on each node and then set the textures on all of the nodes in the custom class. If you are using something like Tiled level editor then you can include the textures data with the ties. If you are creating all of the nodes programmatically then you can simply just set the texture as I have shown above in the code. It really depends on the type of game. All of these solutions work nicely, just depends really what you are looking for. – Epic Byte Jul 13 '15 at 01:23
  • You are correct about the private init. It's so that the class can only exist once. You can still achieve the same safety by overriding -(id)init and raising exception if the class is getting initialized again. – Epic Byte Jul 13 '15 at 01:26
  • Ok, this is starting to make a lot of sense. No prob about the Swift code, I was able to figure out most of it. I have one part that I'm not sure about. In a singleton class using the `dispatch_once` scheme from GCD, how would I create the `level1Assets` dictionary? Is it static? Is it an instance variable? Where would it go in the code? – 02fentym Jul 13 '15 at 04:02
  • 1
    It is simply an instance variable. You can make it a read-only property for safety. Because the SharedAssetsManager is a singleton instance. Everything you write will be part of the instance. – Epic Byte Jul 13 '15 at 14:44
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/83161/discussion-between-02fentym-and-epic-byte). – 02fentym Jul 14 '15 at 01:00
  • "Also there is a bug on Yosemite" I've been having this issue for an extremely long time and cannot fix it at all. Loading the textures into objects into a a singleton did not fix anything. No matter what it still lags when creating the nodes when creating the scene and transitioning right away. It just can't keep up and somehow lags and shoots to the next scene. If any kind of breakpoint pauses it while its on its way to the transition code, the transition will show smoothly. I've given up... – datWooWoo Aug 19 '15 at 00:05