Search code examples
macossprite-kitsktransition

SKTransitions from scene to scene slow


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
}

Solution

  • 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()