3

For a game I'm creating, I want to be able to create a lot of custom levels that can be loaded easily. Each level should have a .sks interface file and its own SKScene subclass .swift file. XCode

Right now, this is working:

extension SKScene {
    class func unarchiveFromFile(file : NSString, theClass : AnyClass!) -> SKScene? {
        if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
            var sceneData = NSData.dataWithContentsOfFile(path, options: .DataReadingMappedIfSafe, error: nil)
            var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)

            archiver.setClass(theClass, forClassName: "SKScene")
            let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as SKScene
            archiver.finishDecoding()
            return scene
        } else {
            return nil
        }
    }
}

Where I create the scene like this:

if let scene = SKScene.unarchiveFromFile("Level1", theClass: Level1.classForKeyedUnarchiver())

But this doesn't work for large collections because I can't iterate over each level in, say, a folder of them. I need something like this

if let scene = SKScene.unarchiveFromFile("Level1", className:"Level1")

I tried using this answer to get the class from the string like this, but the scene loaded as if it were just an SKScene, not a Level1 object.

let classFromString: AnyClass! = NSObject.swiftClassFromString("Level1")
    if let scene = SKScene.unarchiveFromFile("Level1", theClass: classFromString)

For now, I'll work with this, but I don't think this is the best approach:

let levels = [
    ("Level1", Level1.classForKeyedUnarchiver()),
    ("Level2", Level2.classForKeyedUnarchiver())]

let level1 = levels[0]
let (fileName : String, theClass: AnyClass!) = classes[0]
if let scene = SKScene.unarchiveFromFile(fileName, theClass: theClass)

I just want to be able to make a large collection of easily loadable SKScene subclasses each with their own interface file.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
michaelsnowden
  • 6,031
  • 2
  • 38
  • 83
  • 2
    you should have a common class for all levels (unless gameplay changes heavily in each level the game code for each level ought to be the same, ie data-driven with optional plugin code supplied by child nodes providing level specific logic), and you can enumerate files in bundle with nsfilemanager/nsbundle – CodeSmile Sep 07 '14 at 07:39
  • @LearnCocos2D Thanks. I would like to use NSFileManger/NSBundle, but the gameplay does change heavily from level-to-level. I will try your approach though. If I understand it, I should have custom SKNodes in my .sks files if I ever need custom logic in the level, which the single `Level` class will look for when it needs to (i.e. during a collision, it could tell a subclassed SKNode object `a` that it just collided, and `a` will handle the logic). One question then, could you provide a simple example of iterating over each .sks file in a folder, like the one in my picture above? Thanks. – michaelsnowden Sep 07 '14 at 17:21
  • @LearnCocos2D I've asked a [followup question](http://stackoverflow.com/questions/25713326/get-all-files-in-xcode-folder) here. If you have the time, I'd really appreciate an answer. – michaelsnowden Sep 07 '14 at 18:18

1 Answers1

4

This can be done using NSClassFromString:

For Swift 2:

extension SKScene {
    static func sceneWithClassNamed(className: String, fileNamed fileName: String) -> SKScene? {
        guard let SceneClass = NSClassFromString("Your_App_Name.\(className)") as? SKScene.Type,
              let scene = SceneClass.init(fileNamed: fileName) else { 
            return nil 
        }

        return scene
    }
}

Or Swift 1:

extension SKScene {
    static func sceneWithClassNamed(className: String, fileNamed fileName: String) -> SKScene? {
        if let SceneClass = NSClassFromString("Your_App_Name.\(className)") as? SKScene.Type,
           let scene = SceneClass(fileNamed: fileName) {
            return scene
        }

        return nil
    }
}

Usage:

if let scene = SKScene.sceneWithClassNamed("MyScene", fileNamed: "MyScene") {
    view.presentScene(scene)
}

Its important to note that for this to work correctly your SKScene subclass must implement init(coder aDecoder: NSCoder), for example:

required init?(coder aDecoder: NSCoder) {
    // ...
    super.init(coder: aDecoder)
}
ABakerSmith
  • 22,759
  • 9
  • 68
  • 78