2
votes

I am making a SpriteKit framework using swift 4.2 and want to include some .sks files for scenes and actions. I have tried to load the scene from the bundle using the code below:

class func newGameScene() -> GameScene {

guard let gameScenePath = Bundle(for: self).path(forResource: "GameScene", ofType: "sks") else { assert(false) }

guard let gameSceneData = FileManager.default.contents(atPath: gameScenePath) else { assert(false) }

let gameSceneCoder = NSKeyedUnarchiver(forReadingWith: gameSceneData)

guard let scene = GameScene(coder: gameSceneCoder) else { assert(false) }


// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill

return scene

}

I load the scene and present it. (This code is mostly from Apple's template for SpriteKit as Im testing this issue.)

guard let view = view else {
      return nil
    }

    let scene = GameScene.newGameScene()

    view.presentScene(scene)

    view.ignoresSiblingOrder = true
    view.showsFPS = true
    view.showsNodeCount = true
    return nil

The GameScene.sks and the code is unchanged from Apples template in this case. This code and the .sks assets are in the dynamic framework and imported into another project.

When having the framework load the scene into a view I pass it, it shows the fps and node count but not the "Hello, World!" text.

In the code below, also copied from the template, a break point shows that these are not called when mousing down.

    #if os(OSX)
// Mouse-based event handling
extension GameScene {

  override func mouseDown(with event: NSEvent) {
    if let label = self.label {
      label.run(SKAction.init(named: "Pulse")!, withKey: "fadeInOut")
    }
    self.makeSpinny(at: event.location(in: self), color: SKColor.green)
  }

  override func mouseDragged(with event: NSEvent) {
    self.makeSpinny(at: event.location(in: self), color: SKColor.blue)
  }

  override func mouseUp(with event: NSEvent) {
    self.makeSpinny(at: event.location(in: self), color: SKColor.red)
  }

}
#endif

I know it must have to do with how SpritKit loads the scene but cannot find a solution. I have to use an NSKeyedUnarchiver becuase SpritKit's built in file initializer:

GameScene(fileNamed: "GameScene")

Only loads from the Main Bundle.

Now in the above I assumed that the file can be loaded by using a coder but Tomato made the point that sks most likely was not saved using a coder. In that case, It may be impossible to load an sks file from another bundle in sprite-kit using the provided api from apple. The answer may not include coders.

2
"I know it must have to do with how SpritKit loads the scene but cannot find a solution." Take a good look at the very first several lines of code you have presented. Why are you reading a file from the bundle? You are using FileManager for that? Unarchive a file for what purpose? GameScene.sks is a Data-archived file? - El Tomato
ok that would be my bad. Im not sure why I assumed as much. I assumed thats how they loaded the file because they offer no other public api to load an sks file except by using the function that loads by name from the main bundle only. - GlorySaber
@ElTomato I was thinking that was the issue, but then why are the asserts not failing - Knight0fDragon
@Knight0fDragon I would like to believe that apple built it with optionals when looking for the keys so if the keys arent found it just moves on. Thats probably why its non interactive once finished loading, it never finished loading in the first place. - GlorySaber
Stephen Kac optionals is not the issue, somehow your "coder" is an empty coder but Gamescene is fine with it and is letting you create a brand new GameScene instance with it - Knight0fDragon

2 Answers

4
votes

I have compiled the above discussion/solution into a single extension function on SKScene. Hope this helps someone!

import SpriteKit

extension SKScene {
    static func fromBundle(fileName: String, bundle: Bundle?) -> SKScene? {
        guard let bundle = bundle else { return nil }
        guard let path = bundle.path(forResource: fileName, ofType: "sks") else { return nil }
        if let data = FileManager.default.contents(atPath: path) {
            return NSKeyedUnarchiver.unarchiveObject(with: data) as? SKScene
        }
        return nil
    }
}
3
votes

Just as I thought let gameSceneCoder = NSKeyedUnarchiver(forReadingWith: gameSceneData) was not creating a proper coder for you.

Just do

guard let scene = NSKeyedUnarchiver.unarchiveObject(with: gameSceneData) as? SKScene
        else{
            assert(false)
    }

This will unarchive the file properly for you.

Note, if you want to use GameScene, make sure GameScene is set in the custom class of the SKS file