12
votes

(this is for XCode 6 and iOS 8 beta 4)

Love the new SceneKit editor. I'm successfully loading the scene from .sks file into a custom SKScene class. However, objects inside it are instantiated as default classes (SKNode, SKSpriteNode, etc), and i'm not sure how to bind them to be instantiated as custom subclasses instead.

Currently, I'm getting around that by creating custom classes and linking to sprite nodes as a property, and that works ok

4
I hope that apple will make this available in the next Xcode updates.Dima Deplov
Adding a bounty because I would like to know if there is a preferred way of accomplishing this (Xcode 6.1, Dec 2014).Ephemera
I think they will eventually do something in Scene Editor like they do for IBDesignable now in IB for AppKit and UIKit. But if you want it, everybody file bugs requesting it! That is one of the key things that drives their prioritization process.uchuugaka
We'll be able to do this in Xcode 7!NobodyNada
@NobodyNada : how do you do it in Xcode 7?cocoseis

4 Answers

7
votes

Currently you are supposed to do it by name in the method added to the SpriteKit game templates. They cover this topic in the SpriteKit Best Practices video in WWDC 2014. It's easy to miss because the video has a lot in it.

Here is the method.

+ (instancetype)unarchiveFromFile:(NSString *)file {
    /* Retrieve scene file path from the application bundle */
    NSString *nodePath = [[NSBundle mainBundle] pathForResource:file ofType:@"sks"];
    /* Unarchive the file to an SKScene object */
    NSData *data = [NSData dataWithContentsOfFile:nodePath
                                          options:NSDataReadingMappedIfSafe
                                            error:nil];
    NSKeyedUnarchiver *arch = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    [arch setClass:self forClassName:@"SKScene"];
    SKScene *scene = [arch decodeObjectForKey:NSKeyedArchiveRootObjectKey];
    [arch finishDecoding];

    return scene;
}

It's a category on SKScene that includes a method to use NSKeyedUnarchiver to substitute the Class when loaded from the .sks file in the mainBundle. In iOS they add it to the GameViewController.m file and in OS X they add it to the AppDelegate.m file.

In here or in your individual SKNode subclasses, you can implement this. This is what he did below in his post. This was provided by Apple and covered in the WWDC video. I guess next year it will get handled better. In the meantime file a bug requesting SKNodes get something like IBDesignable for Scene Editor. :)

4
votes

I wrote a little helper delegate to deal with this: https://github.com/ice3-software/node-archive-delegate. It uses the NSKeyedUnarchiverDelegate to subclass your sprites by name.

3
votes

I made a little trick how to use SceneKit editor, and I'll try to use it but I don't sure about how this can reduce performance. I want to use scene editor to made complex SKNodes structures and load them into the current scene. It also could help with subclassing nodes.

I configure my node in scene editor, set the parent node as empty node called "base" and made an subclass of SKNode. Then I made an init method and call it when I need to in my scene:

// subclass code
class MyOwnSKNodeSubclass: SKNode {

let nodeLayer = SKNode()

   init(fromNode: SKNode){
       super.init()

       nodeLayer.addChild(fromNode.childNodeWithName("base").copy() as SKNode)
       addChild(nodeLayer)
   }
}



// scene code
if let myNode = SKNode.unarchiveNodeFromFile("MyScene")
{
   // you can use your subclass here
   let node = MyOwnSKNodeSubclass(fromNode: myNode)

   addChild(node)
}

I also used my own SKNode extension, not a big difference but:

class func unarchiveNodeFromFile(file:String) -> SKNode?{

    if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
        var nodeData = NSData.dataWithContentsOfFile(path, options: .DataReadingMappedIfSafe, error: nil)
        var archiver = NSKeyedUnarchiver(forReadingWithData: nodeData)

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

As I said, I don't sure about productivity, but I assume that it won't harm too much if not use this very often. Hope this helps somebody.

2
votes

Yes, Apple ought to make this easier for all of us, but for the time being, here is the most viable solution for my needs:

Avoid subclassing

Sure, that may not be at all possible, but in my case, the subclass I had in mind contains logic to control the graphics I laid out in the SKS file ... Sounds awfully close to ViewController, doesn't it?

I created a OverlayController class, and I initialize it with:

self.childNodeWithName(Outlet.OverlayNode)!

So now, overlay node is just a dumb node, and the controller is the full fledged subclass that has all the goodies.

But there is more

Just throwing this in to sweeten the deal:

private extension SKNode {
    var progressBar: SKNode {
        return self.childNodeWithName("ProgressBar")!
    }
}

This is a private extension inside the new controller class file, so we can painlessly access the children of the dumb SKNode.

Pain point

An admittedly painful point is touch handling. It's natural to subclass an SKNode to do custom touch handling, and the composition approach hardly does anything on that front. I'm forwarding touches from the scene for now, but would post updates if there is a better way.