SceneKit -– How to get animations for a .dae model? - swift

Ok, I am working with ARKit and SceneKit here and I am having trouble looking at the other questions dealing with just SceneKit trying to have a model in .dae format and load in various animations to have that model run - now that we're in iOS11 seems that some solutions don't work.
Here is how I get my model - from a base .dae scene where no animations are applied. I am importing these with Maya -
var modelScene = SCNScene(named: "art.scnassets/ryderFinal3.dae")!
if let d = modelScene.rootNode.childNodes.first {
theDude.node = d
theDude.setupNode()
}
Then in Dude class:
func setupNode() {
node.scale = SCNVector3(x: modifier, y: modifier, z: modifier)
center(node: node)
}
the scaling and centering of axes is needing because my model was just not at the origin. That worked. Then now with a different scene called "Idle.dae" I try to load in an animation to later run on the model:
func animationFromSceneNamed(path: String) -> CAAnimation? {
let scene = SCNScene(named: path)
var animation:CAAnimation?
scene?.rootNode.enumerateChildNodes({ child, stop in
if let animKey = child.animationKeys.first {
animation = child.animation(forKey: animKey)
stop.pointee = true
}
})
return animation
}
I was going to do this for all my animations scenes that I import into Xcode and store all the animations in
var animations = [CAAnimation]()
First Xcode says animation(forKey: is deprecated and This does not work it seems to (from what I can tell) de-center and de-scale my model back to the huge size it was. It screws up its position because I expect making the model move in an animation, for example, would make the instantiated model in my game snap to that same position.
and other attempts cause crashes. I am very new to scene kit and trying to get a grip on how to properly animate a .dae model that I instantiate anywhere in the scene -
How in iOS11 does one load in an array of animations to apply to their SCNNode?
How do you make it so those animations are run on the model WHEREVER THE MODEL IS (not snapping it to some other position)?

At first I should confirm that CoreAnimation framework and some of its methods like animation(forKey:) instance method are really deprecated in iOS and macOS. But some parts of CoreAnimation framework are now implemented into SceneKit and other modules. In iOS 11+ and macOS 10.13+ you can use SCNAnimation class:
let animation = CAAnimation(scnAnimation: SCNAnimation)
and here SCNAnimation class has three useful initializers:
SCNAnimation(caAnimation: CAAnimation)
SCNAnimation(contentsOf: URL)
SCNAnimation(named: String)
In addition I should add that you can use not only animations baked in .dae file format, but also in .abc, .scn and .usdz.
Also, you can use SCNSceneSource class (iOS 8+ and macOS 10.8+) to examine the contents of a SCNScene file or to selectively extract certain elements of a scene without keeping the entire scene and all the assets it contains.
Here's how a code with implemented SCNSceneSource might look like:
#IBOutlet var sceneView: ARSCNView!
var animations = [String: CAAnimation]()
var idle: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
let scene = SCNScene()
sceneView.scene = scene
loadMultipleAnimations()
}
func loadMultipleAnimations() {
let idleScene = SCNScene(named: "art.scnassets/model.dae")!
let node = SCNNode()
for child in idleScene.rootNode.childNodes {
node.addChildNode(child)
}
node.position = SCNVector3(0, 0, -5)
node.scale = SCNVector3(0.45, 0.45, 0.45)
sceneView.scene.rootNode.addChildNode(node)
loadAnimation(withKey: "walking",
sceneName: "art.scnassets/walk_straight",
animationIdentifier: "walk_version02")
}
...
func loadAnimation(withKey: String, sceneName: String, animationIdentifier: String) {
let sceneURL = Bundle.main.url(forResource: sceneName, withExtension: "dae")
let sceneSource = SCNSceneSource(url: sceneURL!, options: nil)
if let animationObj = sceneSource?.entryWithIdentifier(animationIdentifier,
withClass: CAAnimation.self) {
animationObj.repeatCount = 1
animationObj.fadeInDuration = CGFloat(1)
animationObj.fadeOutDuration = CGFloat(0.5)
animations[withKey] = animationObj
}
}
...
func playAnimation(key: String) {
sceneView.scene.rootNode.addAnimation(animations[key]!, forKey: key)
}

Related

RealityKit .nonAR installGestures is missing translation and rotation is y axis only

I'm trying to reverse engineer the 3d Scanner App using RealityKit and am having real trouble getting just a basic model working with all gestures. When I run the code below, I get a cube with scale and rotation (about the y axis only), but no translation interaction. I'm trying to figure out how to get rotation about an arbitray axis as well as translation, like in the 3d Scanner App above. I'm relatively new to iOS and read one should use RealityKit as Apple isn't really supporting SceneKit anymore, but am now wondering if SceneKit would be the way to go, as RealityKit is still young. Or if anyone knows of an extension to RealityKit ModelEntity objects to give them better interaction capabilities.
I've got my app taking a scan with the LiDAR sensor and saving it to disk as a .usda mesh, per this tutorial, but when I load the mesh as a ModelEntity and attach gestures to it, I don't get any interaction at all.
The below example code recreates the limited gestures for a box ModelEntity, and I have some commented lines showing where I would load my .usda model from disk, but again while it will render, it gets no interaction with gestures.
Any help appreciated!
// ViewController.swift
import UIKit
import RealityKit
class ViewController: UIViewController {
var arView: ARView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
arView = ARView(frame: view.frame, cameraMode: .nonAR, automaticallyConfigureSession: false)
view.addSubview(arView)
// create pointlight
let pointLight = PointLight()
pointLight.light.intensity = 10000
// create light anchor
let lightAnchor = AnchorEntity(world: [0, 0, 0])
lightAnchor.addChild(pointLight)
arView.scene.addAnchor(lightAnchor)
// eventually want to load my model from disk and give it gestures.
// guard let scanEntity = try? Entity.loadModel(contentsOf: urlOBJ) else {
// print("couldn't load scan in this format")
// return
// }
// entity to add gestures to
let cubeMaterial = SimpleMaterial(color: .blue, isMetallic: true)
let myEntity = ModelEntity(mesh: .generateBox(width: 0.1, height: 0.2, depth: 0.3, cornerRadius: 0.01, splitFaces: false), materials: [cubeMaterial])
myEntity.generateCollisionShapes(recursive: false)
let myAnchor = AnchorEntity(world: .zero)
myAnchor.addChild(myEntity)
// add collision and interaction
let scanEntityBounds = myEntity.visualBounds(relativeTo: myAnchor)
myEntity.collision = CollisionComponent(shapes: [.generateBox(size: scanEntityBounds.extents).offsetBy(translation: scanEntityBounds.center)])
arView.installGestures(for: myEntity).forEach {
gestureRecognizer in
gestureRecognizer.addTarget(self, action: #selector(handleGesture(_:)))
}
arView.scene.addAnchor(myAnchor)
// without this, get no gestures at all
let camera = PerspectiveCamera()
let cameraAnchor = AnchorEntity(world: [0, 0, 0.2])
cameraAnchor.addChild(camera)
arView.scene.addAnchor(cameraAnchor)
}
#objc private func handleGesture(_ recognizer: UIGestureRecognizer) {
if recognizer is EntityTranslationGestureRecognizer {
print("translation!")
} else if recognizer is EntityScaleGestureRecognizer {
print("scale!")
} else if recognizer is EntityRotationGestureRecognizer {
print("rotation!")
}
}
}
To extend ModelEntity's gesture interaction capabilities setup your own 2D gestures. There are 8 screen gestures in UIKit, and in SwiftUI you have 5 principal gestures and additionally Sequence, Simultaneous and Exclusive variations.
Form what I have understood, that the gestures are working for the box but not for your .usdz file/model. If this is the case, then the issue is because the model does not have a collision mesh(HasCollsion). If you are using reality composer to edit your models, you could do the following:
click on the model
under the Physics dropdown, click Participate
under collision shape select automatic
Overalls, make sure that the model has collision and you cast within the code that it has collision
let myEntity = try? Entity.loadModel(named: "fileName") as! HasCollision

Use multiple classes to control a single SKScene [duplicate]

I'm trying to learn how to make a GameManager type class, and making individual classes for each of my GameScenes... probably the wrong thing to do, but for the sake of this question, please accept this as the way to do things.
My GameManager looks like this, having a reference to each of the scenes, that's static:
import SpriteKit
class GM {
static let scene2 = SecondScene()
static let scene3 = ThirdScene()
static let home = SKScene(fileNamed: "GameScene")
}
How do I create a SKScene programmatically, without size info, since they're in a subclass of SKScene and don't have any idea what the view size is, and I don't want them to need worry about this:
I'm doing this, but getting a EXC_BAD_Access at convenience override init()
class SecondScene: SKScene {
override init(size: CGSize){
super.init(size: size)
}
convenience override init(){
self.init()
self.backgroundColor = SKColor.red
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
}
}
As I mentioned your question is a bit vague but lets do some examples of what a GameManager class can be.
Before I start lets differentiate between calling this
let scene = StartScene(size: ...)
and this
let scene = SKScene(fileNamed: "StartScene")
The 1st method, with size, is when you create your scenes all in code and you are not using the xCode visual level editor.
The 2nd method is when you are using the Xcode level editor, so you would need to create a StartScene.sks file. Its that .sks file that it looks for in fileNamed.
Now for some game manager example, lets first imagine we have 3 SKScenes.
class StartScene: SKScene {
override func didMove(to view: SKView) { ... }
}
class GameScene: SKScene {
override func didMove(to view: SKView) { ... }
}
class GameOverScene: SKScene {
override func didMove(to view: SKView) { ... }
}
Lets say you want to transition from StartScene to GameScene, you would add this code in your StartScene at the correct spot e.g when the play button is pressed. Thats the simplest way to move from one SKScene to the next, directly from the SKScene itself.
// Code only, no xCode level editor
let gameScene = GameScene(size: CGSize(...))
let transition = SKTransition...
gameScene.scaleMode = .aspectFill
view?.presentScene(gameScene, transition: transition)
// With xCode level editor (returns an optional so needs if let
// This will need the GameScene.sks file with the correct custom class set up in the inspector
// Returns optional
if let gameScene = SKScene(fileNamed: "GameScene") {
let transition = SKTransition...
gameScene.scaleMode = .aspectFill
view?.presentScene(gameScene, transition: transition)
}
Now for some actual examples of GameManagers, Im sure you know about some of them already.
EXAMPLE 1
Lets say we want a scene loading manager. You approach with static methods will not work because a new instance of SKScene needs be created when you transition to one, otherwise stuff like enemies etc will not reset. Your approach with static methods means you would use the same instance every time and that is no good.
I personally use a protocol extension for this.
Create a new .swift file and call it SceneLoaderManager or something and add this code
enum SceneIdentifier: String {
case start = "StartScene"
case game = "GameScene"
case gameOver = "GameOverScene"
}
private let sceneSize = CGSize(width: ..., height: ...)
protocol SceneManager { }
extension SceneManager where Self: SKScene {
// No xCode level editor
func loadScene(withIdentifier identifier: SceneIdentifier) {
let scene: SKScene
switch identifier {
case .start:
scene = StartScene(size: sceneSize)
case .game:
scene = GameScene(size: sceneSize)
case .gameOver:
scene = GameOverScene(size: sceneSize)
}
let transition = SKTransition...\
scene.scaleMode = .aspectFill
view?.presentScene(scene, transition: transition)
}
// With xCode level editor
func loadScene(withIdentifier identifier: SceneIdentifier) {
guard let scene = SKScene(fileNamed: identifier.rawValue) else { return }
scene.scaleMode = .aspectFill
let transition = SKTransition...
view?.presentScene(scene, transition: transition)
}
}
Now in the 3 scenes conform to the protocol
class StartScene: SKScene, SceneManager { ... }
and call the load method like so, using 1 of the 3 enum cases as the scene identifier.
loadScene(withIdentifier: .game)
EXAMPLE 2
Lets make a game manager class for game data using the Singleton approach.
class GameData {
static let shared = GameData()
private init() { } // Private singleton init
var highscore = 0
func updateHighscore(forScore score: Int) {
guard score > highscore else { return }
highscore = score
save()
}
func save() {
// Some code to save the highscore property e.g UserDefaults or by archiving the whole GameData class
}
}
Now anywhere in your project you can say
GameData.shared.updateHighscore(forScore: SOMESCORE)
You tend to use Singleton for things where you only need 1 instance of the class. A good usage example for Singleton classes would be things such as helper classes for Game Center, InAppPurchases, GameData etc
EXAMPLE 3
Generic helper for storing some values you might need across all scenes. This uses static method approach similar to what you were trying to do. I like to use this for things such as game settings, to have them in a nice centralised spot.
class GameHelper {
static let enemySpawnTime: TimeInterval = 5
static let enemyBossHealth = 5
static let playerSpeed = ...
}
Use them like so in your scenes
... = GameHelper.playerSpeed
EXAMPLE 4
A class to manage SKSpriteNodes e.g enemies
class Enemy: SKSpriteNode {
var health = 5
init(imageNamed: String) {
let texture = SKTexture(imageNamed: imageNamed)
super.init(texture: texture, color: SKColor.clear, size: texture.size())
}
func reduceHealth(by amount: Int) {
health -= amount
}
}
Than in your scene you can create enemies using this helper class and call the methods and properties on it. This way you can add 10 enemies easily and individually manage their health etc. e.g
let enemy1 = Enemy(imageNamed: "Enemy1")
let enemy2 = Enemy(imageNamed: "Enemy2")
enemy1.reduceHealth(by: 3)
enemy2.reduceHealth(by: 1)
Its a massive answer but I hope this helps.

Adding a custom GKComponent to an entity in Xcode SpriteKit scene editor sets GKScene.rootNode to nil

When I add a CustomComponent (GKComponent) to an entity in Xcode SpriteKit scene editor and try to load that .sks file using a GKScene.init, GKScene.rootNode is not set. Even stranger, this happens only on iOS 13 and not on iOS 12.
I have a small sprite kit github project setup that demonstrates this issue clearly. Just run the app on an iOS 13 emulator to reproduce the issue. https://github.com/hdsenevi/answers-gkscene-rootnode-nil-bug
If I remove CustomComponent from SpriteKit scene editor entity/sprite, then it runs fine. ie: loads SKScene into GKScene.rootNode
Is there any other special modifications that needs to happen when adding GKComponents from Xcode SpriteKit scene editor?
Am I missing something obvious here?
And why would this code work without an issue on iOS 12 and not iOS 13?
Has SpriteKit functionality changed with regards to this in iOS 13?
For reference
import UIKit
import SpriteKit
import GameplayKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Load 'GameScene.sks' as a GKScene. This provides gameplay related content
// including entities and graphs.
if let scene = GKScene(fileNamed: "GameScene") {
// Get the SKScene from the loaded GKScene
if let sceneNode = scene.rootNode as! GameScene? {
// Copy gameplay related content over to the scene
sceneNode.entities = scene.entities
sceneNode.graphs = scene.graphs
// Set the scale mode to scale to fit the window
sceneNode.scaleMode = .aspectFill
// Present the scene
if let view = self.view as! SKView? {
view.presentScene(sceneNode)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
} else {
print("Error. No GameScene was found on GKScene.rootNode")
}
} else {
print("Error loading GKScene file GameScene")
}
}
}
import SpriteKit
import GameplayKit
class CustomComponent: GKComponent {
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func didAddToEntity() {
guard let gkSkNodeComponent = self.entity?.component(ofType: GKSKNodeComponent.self) else {
print("Error. Cannot obtain a reference to GKSKNodeComponent")
return
}
if let sprite = gkSkNodeComponent.node as? SKSpriteNode {
sprite.texture?.filteringMode = .nearest
}
}
}
Update
Few details on my setup
macOS Mojave 10.14.6
Xcode 11.0 (tried on 10.1 and 10.3, same behaviour)
To solve the problem, your component needs to override variable supportsSecureCoding and it must return true.
This worked for me:
override class var supportsSecureCoding: Bool {
return true
}
I have also just come across this and have sent a Feedback with a link to this article as it's a great description of the problem.
It was happening to me too.
I have a Color Sprite with body type Alpha Mask, I found out that if I change the body type to any other type it fixes this and the rootNode works again.
Prior to iOS 13, I accessed the root node of GKScene. For reasons still unclear to me, that results in nil with iOS 13+. Lo and behold, though, you can access the legacy "root node" directly from the GameScene. I did not have to alter any custom classes in any way in order to restore full functionality.
//From GameViewController (UIViewController):
- (void)viewDidLoad {
[super viewDidLoad];
GameScene *sceneNode;
NSOperatingSystemVersion ios13_0_0 = (NSOperatingSystemVersion){13, 0, 0};
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:ios13_0_0]) {
// iOS 13.0.0 and above logic for rootNode
// Load the SKScene from 'GameScene.sks'
GameScene *scene = (GameScene *)[SKScene nodeWithFileNamed:#"GameScene"];
SKView *skView = (SKView *)self.view;
// The fix for use in iOS 13.0+
sceneNode = scene;
// Set the scale mode to scale to fit the window
sceneNode.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene
[skView presentScene:scene];
} else {
// prior to iOS 13.0.0 logic
GKScene *scene = [GKScene sceneWithFileNamed:#"GameScene"];
SKView *skView = (SKView *)self.view;
// This will result in nil if used in iOS 13.0+
sceneNode = (GameScene *)(scene.rootNode);
// Set the scale mode to scale to fit the window
sceneNode.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene
[skView presentScene:sceneNode];
}
}
This instantiates an instance of GameScene (SKScene). In GameScene, I am able to access custom nodes that I've added into the scene with a custom name via:
for (SKNode *node in self.children)
{
if ([node.name containsString:#"NameOfNodeHere"])
{
// Access your custom node in your .sks here
}
}
So I've got a rather hacky workaround if you want to keep the same style of defining components in the SK Scene Editor by using UserData.
In the SK Scene Editor populate nodes UserData with attributes that your code then uses to turn into components, and then automate setup GKSKNodeComponents.
I've made a class TemplateScene which I put as the scene of the sks file.
import Foundation
import SpriteKit
import GameplayKit
class TemplateScene: SKScene {
lazy var entities: [GKEntity] = {
var entities: [GKEntity] = []
// applyAllChildren is an extension I made that simple recurses all children, children's children etc
self.applyAllChildren { node in
if let userData = node.userData {
var components: [GKComponent] = []
if userData["c.BasicComponent"] as? Bool == true {
components.append(BasicComponent())
}
if components.count > 0 {
components.append(GKSKNodeComponent(node: node))
let entity = GKEntity()
for component in components {
entity.addComponent(component)
}
entities.append(entity)
}
}
}
return entities
}()
}
Just keep extending the code to handle more components. If you have component attributes then have UserData in the form of c.BasicComponent.x etc
(To be clear, this is a hack to get around the fact that Apple has a serious bug that makes a large chunk of SpriteKit Editor functionality unsuable in an entire version of iOS. Maybe it's fixed in iOS 14?)

Using a MTLTexture as the environment map of a SCNScene

I want to set a MTLTexture object as the environment map of a scene, as it seems to be possible according to the documentation. I can set the environment map to be a UIImage with the following code:
let roomImage = UIImage(named: "room")
scene.lightingEnvironment.contents = roomImage
This works and I see the reflection of the image on my metallic objects. I tried converting the image to a MTLTexture and setting it as the environment map with the following code:
let roomImage = UIImage(named: "room")
let loader = MTKTextureLoader(device: MTLCreateSystemDefaultDevice()!)
let envMap = try? loader.newTexture(cgImage: (roomImage?.cgImage)!, options: nil)
scene.lightingEnvironment.contents = envMap
However this does not work and I end up with a blank environment map with no reflection on my objects.
Also, instead of setting the options as nil, I tried setting the MTKTextureLoader.Option.textureUsage key with every possible value it can get, but that didn't work either.
Edit: You can have a look at the example project in this repo and use it to reproduce this use case.
Lighting SCN Environment with an MTK texture
Using Xcode 13.3.1 on macOS 12.3.1 for iOS 15.4 app.
The trick is, the environment lighting requires a cube texture, not a flat image.
Create 6 square images for MetalKit cube texture
in Xcode Assets folder create Cube Texture Set
place textures to their corresponding slots
mirror images horizontally and vertically, if needed
Paste the code:
import ARKit
import MetalKit
class ViewController: UIViewController {
#IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
let imageName = "CubeTextureSet"
let textureLoader = MTKTextureLoader(device: sceneView.device!)
let environmentMap = try! textureLoader.newTexture(name: imageName,
scaleFactor: 2,
bundle: .main,
options: nil)
let daeScene = SCNScene(named: "art.scnassets/testCube.dae")!
let model = daeScene.rootNode.childNode(withName: "polyCube",
recursively: true)!
scene.lightingEnvironment.contents = environmentMap
scene.lightingEnvironment.intensity = 2.5
scene.background.contents = environmentMap
sceneView.scene = scene
sceneView.allowsCameraControl = true
scene.rootNode.addChildNode(model)
}
}
Apply metallic materials to models. Now MTL environment lighting is On.
If you need a procedural skybox texture – use MDLSkyCubeTexture class.
Also, this post may be useful for you.

Scene created in Sprite kit level editor is not working

I'm trying to do this for a while. I have a main scene for a game named PlayScene. I have a pause button there. When player is tapping that button I want to load another scene, named PauseScene. For visualy creating that scene I use Sprite kit level editor. So I have two files PauseScene.swiftand PauseScene.sks. But .sks content ( just a background for now) isn't unarchiving. I don't really know where could I make a mistake.
So this is my transition via pause button, located in PlayScene
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if self.nodeAtPoint(location) == self.pause {
var scene = PauseScene(size: self.size)
scene.paused = true
let skView = self.view as SKView!
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
scene.size = skView.bounds.size
skView.presentScene(scene)
}
}
And this is what I have in PauseScene.swift:
class PauseScene: SKScene {
override func didMoveToView(view: SKView) {
self.initPauseScene()
}
func initPauseScene() {
}
}
I tried this and it's not working very well. However that tutorial is closest I could get with my problem. It's not crushing and isn't showing any errors, but when I tap pause button it's transitioning to that scene without my background, just standard grey scene. This answer is not working for me. First suggestion show errors and when I tried second and tap the pause – game is crushing.
I don't know, that level editor is made for easier work but for know it just doubles it for me. I even thought that problem is in my background image maybe. But its standard .png. Why my content isn't showing up?
So I tried to copy GameViewController default code for loading GameScene and ended up with crashing. It's not the first time I have that error while trying to unarchive this. What does it mean? I got this when i tried to replace var scene = PauseScene(size: self.size) with a if let scene = PauseScene.unarchiveFromFile("PauseScene") as? PauseScene
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.