麻烦子类化 SCNScene

Trouble subclassing SCNScene

我一直在尝试子class SCNScene,因为这似乎是保持场景相关逻辑的最佳位置。现在我不确定是否推荐这样做,所以我的第一个问题是——我应该使用 subclassing SCNScene,如果不是,为什么不呢? 文档似乎表明它很常见,但我在网上阅读了建议我不应该 class 它的评论。 废话,我正在查看 SKSceneSCNScene class 参考没有提到 subclassing。

假设以这种方式构建我的游戏没问题,这是我的进度

//  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 中的 subclassing 有一些我不理解的地方。或者,也许我错过了一些关于 运行 的顺序。

Should I be subclassing SCNScene, and if not why not?

不,您不需要转入class,而且您最好不要转入class。 SCNScene 更像是基本的 data/model classes (NSString, UIImage, etc) 而不是 behavior/controller/view classes (UIViewControllerUIControl、等等)。也就是说,它是对某物(在本例中为 3D 场景)的一般描述或容器,并不真正提供行为。由于行为方式不多,因此没有太多机会使用 subclass 覆盖行为。 (class 也不是围绕 subclass 入口点设计的,就像 viewDidLoad 和诸如此类的东西。)

虽然 可能 到 subclass SCNScene,但您不会从中获益太多,并且某些 API 可能无法按您的预期工作...

However, the call to GameScene.someMethod() triggers an EXEC_BAD_ACCESS error.

Also, if I omit the call to GameScene.someMethod, the scene loads correctly, but the overridden initializer in GameScene doesn't appear to be called.

一个 .scn 文件实际上是一个 NSKeyedArchiver 存档,这意味着它里面的对象与用来创建它的对象相同 classes。当您在 SCNScene subclass 上调用 init(named:) 时,superclass' 实现最终会调用 NSKeyedUnarchiver unarchiveObjectWithData: 来加载存档。如果没有一些 unarchiver mangling,该方法将实例化一个 SCNScene,而不是您的 subclass.

的实例

因为您没有获得 subclass 的实例,所以不会调用您的 subclass 初始值设定项,并且尝试调用您的 subclass' 方法会导致运行时错误。

旁白:为什么 SpriteKit 在这里不同于 SceneKit? SKScene 有点奇怪,因为它既不是纯模型 class 也不是纯控制器 class。这就是为什么您会看到很多项目(包括 Xcode 模板)使用 SKScene subclass。然而,这种方法有一些缺点 — 如果您不仔细计划,很容易将您的游戏 logic 与您的游戏 data[=70= 紧密结合],这使得扩展您的游戏(例如,扩展到多个级别)需要繁琐的编码而不是数据编辑。有个WWDC 2014 session on this topic值得一看

那么,怎么办?

这里有几个选择...

  1. 不要订阅class。将您的游戏逻辑保存在单独的 class 中。这不一定是视图控制器 class — 您可以拥有一个或多个 Game 对象,这些对象由您的视图控制器或应用程序委托拥有。 (如果您的游戏逻辑在多个场景之间转换或操纵多个场景的内容,这尤其有意义。)

  2. Subclass,但安排将未存档的内容 SCNScene 放入您的 subclass 的实例中 — 实例化您的 subclass,加载一个SCNScene,然后将其根节点的所有子节点移动到你的子class'根节点。

  3. Subclass,但强制 NSKeyedUnarchiver 加载你的 subclass 而不是 SCNScene。 (你可以用 class name mappings 来做到这一点。)

其中,#1 可能是最好的。

tl;dr:扩展 SCNNode 以编程方式生成您的场景。

这开始是对 Rickster 出色回答的评论,但我觉得它变得太长了。我在本季度教授的一门课程花费了大量时间在 SceneKit 上。我们一直 SCNScene 子类化,没有 运行 遇到任何麻烦。

但是,在刚刚花了一个小时查看我提供的代码、学生编写的代码以及大量其他示例代码(来自 Apple 和其他开发人员)之后,我不会子类化 SCNScene再次.

None 的 Apple 示例代码子类 SCNScene。这应该是一个非常有力的线索。处理序列化(initWithCoder 和从文件中读取)是 Whosebug 上反复出现的问题。这是你不想去那里的第二个强烈线索。

在我查看子类 SCNScene 的所有代码中,这样做的原因是以编程方式生成场景。这是 Rickster 的第二个选择。但是该代码的 none 确实需要在 SCNScene 上。如果您正在构建一组特定配置的节点,添加照明和 look-at 约束,并设置材质,感觉就像您正在构建一个 SCNScene。但实际上您正在构建 SCNNode 个实例的树。

所以我认为事后看来,选择 #2 没有提供足够好的子类化理由。所有这些构造都可以通过在 SCNNode.

的扩展中编写一个生成器函数来完成

很想为这棵自定义树创建子类 SCNNode。不要这样做。您将能够保存并阅读它(假设您编写了适当的 NSSecureCoding 函数)。但是您将无法使用 Xcode 场景编辑器打开您的场景,因为场景编辑器不知道如何取消存档和实例化您的自定义 SCNNode 子类。