Cannot use unarchiveFromFile to set GameScene in SpriteKit
You should use the init(fileNamed:)
initialiser, which is available from iOS 8 onwards. For example:
if let gameOverScene = GameOverScene(fileNamed: "GameOverScene") {
// ...
}
It's important to note that init(fileNamed:)
is a convenience initialiser on SKNode
:
convenience init?(fileNamed filename: String)
Therefore, for GameOverScene
to automatically inherit init(fileNamed:)
, GameOverScene
must adhere to the following rules from The Swift Programming Language: Initialisation (rule 2 especially):
Assuming that you provide default values for any new properties you introduce in a subclass, the following two rules apply:
Rule 1 If your subclass doesn’t define any designated initializers, it
automatically inherits all of its superclass designated initializers.Rule 2 If your subclass provides an implementation of all of its
superclass designated initializers—either by inheriting them as per
rule 1, or by providing a custom implementation as part of its
definition—then it automatically inherits all of the superclass
convenience initializers.
SpriteKit unarchiveFromFile does not load textures from asset catalogues and atlases
[Answering my own question here]
As far as I can tell, at this moment, loading texture from asset catalogues - Images.xcassets
- while unarchiving or deserializing an SKScene
file does not work on OSX based on my attempts and the devforumns post referenced above.
Loading of the textures from image atlases, however, works by forcing SKTexture
to load the image. Forcing or 'touch'ing SKTexture
can be done via the preloadWithCompletionHandler(_:)
or preloadTextures(_:, withCompletionHandler:)
methods, or simply by printing the description of the sprite's SKTexture
as i have discovered.
For the benefit of anybody who might need further assistance, here is a code snippet that preloads the textures after unarchiving an SKS file:
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
for child in scene.children as [SKNode] {
if child is SKSpriteNode {
let sprite = child as SKSpriteNode
sprite.texture?.preloadWithCompletionHandler({ })
/* or even a simple debug log of the texture */
/* println(sprite.texture) */
}
}
/* Do your other stuff ... */
...
}
If I'm wrong please correct me. Until somebody does, or Apple fixes the discrepancy between SpriteKit's behavior between iOS and OSX, I will not be looking for another solution, and will follow Murphey's law:
If it works, don't fix it
How do I link a .sks file to a .swift file in SpriteKit
This method will unarchive an sks file and initialise it as whichever SKNode subclass it is called on. This means you can unarchive a file as an SKNode (so the root node will be an SKNode) and add it as a child to your scene. You can also unarchive a file as GameScene or any SKNode subclass.
extension SKNode {
class func unarchiveFromFile(file : NSString) -> SKNode? {
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
var sceneData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)!
var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as SKNode
archiver.finishDecoding()
return scene
} else {
return nil
}
}
}
Use it like this:
let scene = GameScene.unarchiveFromFile("GameScene")! as GameScene
or
let levelStuff = SKNode.unarchiveFromFile("Level 1")!
self.addChild(levelStuff)
Scene created in Sprite kit level editor is not working
It may be transition to a grey scene due to spelling errors or the file not even being in the mainBundle. I have tested this and it works. You need to make sure that you are writing the exact name of the .sks file when loading or else you get a grey screen.
Example
If the file is called GameScreen.sks
and the class is called GameScene
then if you put the class name when loading the .sks file you will get a grey screen.
let doors = SKTransition.doorwayWithDuration(1.0)
let archeryScene = GameScene(fileNamed: "GameScreen")
self.view?.presentScene(archeryScene, transition: doors)
So if we look at the second line of code, We see that it is loading the .sks file with the name GameScreen
opposed to the class name GameScene
. We also abort using the file extension because the compiler know by default it is a .sks file we are searching for in the mainBundle.
If you do keep getting a grey screen, Then it may be an error in spelling or ing you GameViewController, In the SKNode Extension, Be sure to change
extension SKNode {
class func unarchiveFromFile(file : NSString) -> SKNode? {
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
var sceneData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)!
var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as GameScene
archiver.finishDecoding()
return scene
} else {
return nil
}
}
}
to this..
extension SKNode {
class func unarchiveFromFile(file : NSString) -> SKNode? {
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
var sceneData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)!
var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as SKScene
archiver.finishDecoding()
return scene
} else {
return nil
}
}
}
We just edited this line of code,
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as GameScene
to this
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as SKScene
This will allow us to load multiple .sks
files without any errors or crashes.
Load a spritekit scene from another bundle?
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
GameScene size is wider than my device screen size
Try to use viewWillLayoutSubviews
instead of viewDidLoad and initialize your scene with skView.bounds.size
:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// Configure the view.
let skView = self.view as SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.showsPhysics = true
skView.showsDrawCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
if(skView.scene == nil){
scene.scaleMode = .AspectFill
scene.size = skView.bounds.size
skView.presentScene(scene)
}
}
}
In viewDidLoad
the final size of view may not be know yet and viewWillLayoutSubviews
is sometimes better place to initialize the scene.
When scene is loaded from .sks file it has default size of 1024x768. That's why your scene is sometimes wider than expected, and this can be changed like from the code I've posted.
If you have trouble with wrong view's size (but I doubt that) check your launch images...If those are wrongly selected, a view can have a wrong size.
Related Topics
Including Zeros in Front of an Integer
How to Call Swiftui Navigationlink Conditionally
Swift Access Array with Index Gives Following Error. Any Idea Why
Replacing Multiple Different Occurences with Multiple Different Replacements - Swift4.2
Swift Tableview How to Select All Rows
Need Explaining on the Double Label Function Declaration
Does _Arraytype or _Arrayprotocol Not Available in Swift 3.1
Is There Any Particular Use of Closure in Swift? and What's the Benefit
Getting a String Value from Nsorderedset Using Swiftui Foreach
Swift Conforming Multiple Protocols Inherits from Same Protocol with Associated Type
Getting the Count of an Optional Array as a String, or Nil
Why Does Xcode Line-Out Autocomplete Methods for Selector
From Which Direction Swift Starts to Read Dictionaries
Why Does the Completion Handler Does Not Return Anything
Swift Crash "Exc_Breakpoint 0X0000000... "
How to Compare Two Strings to Check If They Have Same Characters Swift 4
Swift: Skspritekit, Using Storyboards, Uiviewcontroller and Uibutton to Set in Game Parameters