使用SpriteKit .sks场景文件创建的SKNode子类化

12

(这是适用于XCode 6和iOS 8 beta 4的)

喜欢新的SceneKit编辑器。我成功地将.sks文件中的场景加载到自定义的SKScene类中。然而,其中的对象被实例化为默认类(SKNode,SKSpriteNode等),我不确定如何将它们绑定为实例化为自定义子类。

目前,我通过创建自定义类并将其链接到精灵节点作为属性来解决这个问题,效果还不错。


我希望苹果公司能在下一次Xcode更新中提供这个功能。 - Dima Deplov
添加赏金是因为我想知道是否有一种首选的方法来完成这个任务(Xcode 6.1,2014年12月)。 - Ephemera
我认为他们最终会在场景编辑器中做一些类似于IBDesignable现在在AppKit和UIKit中所做的事情。但是如果你想要这个功能,每个人都可以提交bug请求!这是推动他们优先级过程的关键因素之一。 - uchuugaka
2
我们将能够在Xcode 7中完成这个! - NobodyNada
@NobodyNada:你在Xcode 7中怎么做? - cocoseis
@cocoseis 选择一个节点,然后在自定义类检查器中更改它。(我个人还没有让它正常工作;我认为设备需要运行iOS 9,并且我自更新之前就没有测试过。) - NobodyNada
4个回答

7

目前,您需要在添加到SpriteKit游戏模板的方法中按名称执行此操作。在WWDC 2014年的SpriteKit最佳实践视频中涵盖了这个主题。很容易错过,因为该视频内容很多。

以下是该方法。

+ (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;
}

这是SKScene类别中的一个方法,用于在从mainBundle加载的.sks文件中使用NSKeyedUnarchiver替换类。在iOS中,将其添加到GameViewController.m文件中,在OS X中将其添加到AppDelegate.m文件中。

您可以在此处或您自己的SKNode子类中实现此功能。这就是下面文章作者所做的。此功能由Apple提供,并在WWDC视频中进行了介绍。我想明年会有更好的处理方式。与此同时,提交一个bug请求,以便为Scene Editor获取类似于IBDesignable的东西。 :)


我有点不确定你的意思。我认为你指的是[8:00]的演示,但它并没有处理SKSpriteNodes的子类...所展示的-setupLevel1方法只能从.sks文件中提取可以插入其中的节点(而不能是用户定义的子类)。 - Ephemera
虽然如果您将该方法与flinth的答案相结合,它可能有效,前提是您希望特定 .sks 文件中的每个节点都被子类化为同一个类... - Ephemera
相反,我在下载的视频中看到了这个问题,在第7分钟处,它与我上面描述的相符。这是SKScene上的一个类别。.sks文件是SKScenes。SKScene是SKNode(和SKEffectNode)子类。你可以在场景类中应用它,因为那是唯一真正从文件解档场景的类。感觉他们有点匆忙地把它推出去了。 :) - uchuugaka
我明白你的意思,但是在SKScene中所有的SKSpriteNodes仍然需要一个额外的 [arch setClass:MySpriteNodeSubclass forClassName:@"SKSpriteNode"] 吗? - Ephemera
是的,您需要为存档中的每种类型设置逻辑。这是一种鲜为人知的方式,您可以在Cocoa中的自己的子类中使用initWithCoder:进行类替换。在Foundation类中的类簇中更常见的是条件逻辑。 - uchuugaka
1
啊,我明白了。谢谢你,你非常有帮助! :) - Ephemera

4

3

我有一个小技巧,可以使用SceneKit编辑器,并且我将尝试使用它,但我不确定这样做是否会降低性能。我想使用场景编辑器创建复杂的SKNodes结构并将它们加载到当前场景中。这也可以帮助子类化节点。

我在场景编辑器中配置了我的节点,将父节点设置为名为“base”的空节点,并创建了SKNode的子类。然后我创建了一个init方法,并在需要时在我的场景中调用它:

// 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)
}

我也使用了自己的SKNode扩展,没有太大的区别,但是:

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
    }
}

正如我所说,我不确定生产力会受到多大影响,但我认为如果不经常使用这个功能,它不会对生产力造成太大的伤害。希望这能对某些人有所帮助。


2
是的,苹果公司应该让这对我们所有人更容易些,但在目前情况下,以下是我需要的最可行解决方案:
避免子类化
当然,这可能根本不可能,但在我的情况下,我想到的子类包含了控制我在SKS文件中布局的图形的逻辑......听起来非常接近ViewController,不是吗?
我创建了一个OverlayController类,并使用以下方式初始化它:
self.childNodeWithName(Outlet.OverlayNode)!

现在,覆盖节点只是一个简单的节点,控制器是具有所有好处的全功能子类。

但还有更多

这只是为了让事情更美好:

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

这是新控制器类文件中的一个私有扩展,因此我们可以轻松地访问愚蠢 SKNode 的子元素。

痛点

一个令人痛苦的问题是触摸处理。自定义触摸处理通常会通过对 SKNode 进行子类化来实现,而组合方式在这方面几乎没有任何作用。我现在正在从场景中转发触摸事件,但如果有更好的方法,我会发布更新。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接