SCNScene子类化问题

4
我一直在尝试将SCNScene作为子类,因为这似乎是保存场景相关逻辑的最佳位置。现在我不确定是否推荐这样做,我的第一个问题是 - 我应该将SCNScene作为子类吗?如果不是,为什么不是?文档似乎表明这很常见,但我在网上读到的评论表明我不应该将其作为子类。忽略那个,我看的是SKScene的文档。 SCNScene类参考没有提到继承。
假设这种方式来构建我的游戏是可以的,这是我的进展。
//  GameScene.swift

import Foundation
import SceneKit


class GameScene: SCNScene {

    lazy var enityManager: BREntityManager = {
        return BREntityManager(scene: self)
    }()

    override init() {
        print("GameScene init")
        super.init()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }


    func someMethod() {
        // Here's where I plan to setup the GameplayKit stuff
        // for the scene
        print("someMethod called")
    }

}

注意:根据这个问题的答案,我正在使用lazy var
在我的视图控制器中,我试图像这样使用GameScene。
class GameViewController: UIViewController {

    override func viewDidLoad() {

        super.viewDidLoad()

        // create a new scene
        let scene = GameScene(named: "art.scnassets/game.scn")!

        // retrieve the SCNView
        let scnView = self.view as! SCNView

        // set the scene to the view
        scnView.scene = scene

        // Should print "someMethod called"
        scene.someMethod()
    }
}

然而,调用GameScene.someMethod()会引发EXEC_BAD_ACCESS错误。
另外,如果我省略对GameScene.someMethod的调用,则场景可以正确加载,但是在GameScene中覆盖的初始化程序似乎没有被调用。
我不确定这里发生了什么。很明显,我没有理解Swift中关于子类化的某些内容。或者我可能错过了事物应该运行的顺序的某些方面。
2个回答

8
我需要子类化SCNScene吗?如果不需要,为什么不需要?
不需要,最好不要子类化。 SCNScene 更像基本数据/模型类(NSStringUIImage等),而不是行为/控制器/视图类(UIViewControllerUIControl等)。也就是说,它是某些东西的一般描述或容器(在这种情况下,是3D场景),并没有真正提供行为。由于行为方面的内容不多,因此没有太多机会用子类覆盖行为。 (也没有设计围绕子类入口点的类可以被覆盖,例如viewDidLoad等。)
虽然可能可以子类化SCNScene,但这样做并没有太多好处,并且一些API可能无法按照您的预期工作...
但是,调用GameScene.someMethod()会触发EXEC_BAD_ACCESS错误。
此外,如果我省略对GameScene.someMethod的调用,则场景将正确加载,但是GameScene中重写的初始化程序似乎未被调用。 .scn文件实际上是一个NSKeyedArchiver归档文件,这意味着其中的对象具有创建它所使用的相同类。当您在SCNScene子类上调用init(named:)时,超类的实现最终调用NSKeyedUnarchiver unarchiveObjectWithData:来加载存档文件。如果没有一些反序列化程序混淆,该方法将实例化一个SCNScene,而不是您的子类的实例。
因为您没有得到子类的实例,所以不会调用子类的初始化程序,并且尝试调用子类的方法会导致运行时错误。 附:为什么SpriteKit在这里与SceneKit不同? SKScene 是一个有点奇怪的东西,既不是纯模型类也不是纯控制器类。这就是为什么您会看到很多项目(包括Xcode模板)使用SKScene子类的原因。但是,这种方法也有缺点-如果您不仔细计划,那么将游戏逻辑紧密地与游戏数据结合起来变得太容易,这使得扩展游戏(例如,到多个级别)需要进行繁琐的编码而不是数据编辑。关于此主题有一个WWDC 2014会议,非常值得观看。

那该怎么办?

您有几个选择...

  1. 不要使用子类。将游戏逻辑放在一个单独的类中。这个类不一定要是视图控制器类 - 你可以有一个或多个Game对象,它们由你的视图控制器或应用程序委托拥有。(如果你的游戏逻辑在多个场景之间转换或操作多个场景的内容,这尤其有意义。)

  2. 使用子类,但安排将未归档的SCNScene的内容放入你的子类实例中 - 实例化你的子类,加载一个SCNScene,然后将其根节点的所有子节点移动到你的子类的根节点。

  3. 使用子类,但强制NSKeyedUnarchiver加载你的子类而不是SCNScene。(你可以使用类名映射来实现。)

其中,第1种方法可能是最好的。


好的评论。我会建议在需要的时候选择#1和#2。但请记住,为了执行#1,您需要一个场景来放置已加载场景的内容 - 这就是#2派上用场的地方。 - StackUnderflow
1
感谢您提供这么详细的答案。结合Hal的回答,填补了很多空白。虽然苹果文档和在线教程非常好,但对于一个SceneKit新手来说,似乎花费在解决所有组件应该如何拼接在一起的时间太少了。 - gargantuan

3
tl;dr: 扩展SCNNode以编程方式生成场景。不要子类化SCNScene,而是在SCNNode的扩展中编写生成器函数来构建树形结构。
在使用SceneKit时,我们经常会子类化SCNScene,但这样做并不明智。苹果的示例代码都没有这样做,这应该是一个很强的提示。处理序列化(initWithCoder和从文件读取)是StackOverflow上经常出现的问题。如果你想以编程方式生成场景,可以在SCNNode的扩展中编写生成器函数,而不必子类化SCNScene。尽管看起来像是在构建SCNScene,但实际上你是在构建一棵SCNNode实例的树。如果你为自定义树形结构子类化SCNNode,你将无法在Xcode Scene Editor中打开场景,因为Scene Editor不知道如何解档和实例化自定义的SCNNode子类。

说得好。如果你有一组 SCNNode,实际上你拥有的只是数据...没有理由每次应用程序运行时都在代码中构建它。在代码中构建一次并将其存档到 .scn 文件中,或者使用 Xcode 中的场景编辑器构建一个 .scn 文件。 - rickster
1
在苹果的示例代码项目Bananas中,SCNScene不是被子类化了吗?具体来说,在AAPLGameSimulation.h文件中。 - Jim Hillhouse

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