麻烦继承SCNScene

时间:2015-12-30 18:04:13

标签: swift scenekit

我一直在尝试将SCNScene子类化,因为这似乎是保持场景相关逻辑的最佳位置。现在我不确定是否已经推荐了所以我的第一个问题是 - 我是否应该继承SCNScene,如果不是为什么不呢? 文档似乎表明它很常见,但我在线阅读的评论表明我不应该将其子类化。报废,我正在查看#key# -> #value# "Test.Action1" -> 300 "Test.Action2" -> 200 "Test.Action3" -> 400 的文档。 Dictionary<string, int>类引用未提及子类化。

假设以这种方式构建我的游戏是可以的,这是我的进步

SKScene

注意:我根据this question

的答案使用SCNScene

在我的视图控制器中,我正在尝试像这样使用GameScene

//  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的调用会触发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中覆盖的初始值设定项似乎没有被调用。

我不确定这里发生了什么。很明显,在Swift中有一些关于子类化的东西我还没有理解。或者也许是某些方面的事情意味着我错过了。

2 个答案:

答案 0 :(得分:7)

  

我应该是SCNScene的子类,如果没有,为什么不呢?

不,你不需要进行子类化,你可能最好不要进行子类化。 SCNScene更像基本数据/模型类(NSStringUIImage等),而不像行为/控制器/视图类(UIViewControllerUIControl等)。也就是说,它是某种东西的一般描述或容器(在这种情况下,是3D场景),并没有真正提供行为。由于行为方式不多,因此用子类覆盖行为的机会并不多。 (也不是围绕要重写的子类入口点设计的类,如viewDidLoad和诸如此类的东西。)

虽然可能继承SCNScene,但这样做并没有太大的收获,而且某些API可能无法按预期工作...... < / p>

  

但是,对GameScene.someMethod()的调用会触发EXEC_BAD_ACCESS错误。

     

此外,如果我省略对GameScene.someMethod的调用,则场景会正确加载,但GameScene中被覆盖的初始值设定项似乎没有被调用。

.scn文件实际上是NSKeyedArchiver存档,这意味着其中的对象具有与创建它时相同的类。当你在init(named:)子类上调用SCNScene时,超类&#39;实现最终调用NSKeyedUnarchiver unarchiveObjectWithData:来加载存档。如果没有一些unarchiver修改,该方法将实例化SCNScene,而不是您的子类的实例。

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

旁白:为什么SpriteKit与SceneKit不同? SKScene有点奇怪,因为它既不是纯粹的模型类,也不是纯粹的控制器类。这就是为什么你使用SKScene子类看到很多项目,包括Xcode模板。然而,这种方法有一些缺点 - 如果你没有仔细计划,那么将你的游戏逻辑紧密地融入你的游戏数据就太容易了,这使得扩展您的游戏(例如,扩展到多个级别)需要繁琐的编码而不是数据编辑。有WWDC 2014 session on this topic非常值得关注。

那么,该怎么办?

你在这里有几个选择......

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

  2. 子类,但安排将未归档的SCNScene中的内容放入子类的实例中 - 实例化您的子类,加载SCNScene,然后将其根节点的所有子节点移入你的子类&#39;根节点。

  3. 子类,但强制NSKeyedUnarchiver加载您的子类而不是SCNScene。 (您可以使用class name mappings执行此操作。)

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

答案 1 :(得分:3)

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

这开始是对Rickster的优秀答案的评论,但我可以看到它变得太长了。本季我教了一门课程,花了很多时间在SceneKit上。我们一直隐藏SCNScene,并没有遇到任何麻烦。

然而,在刚刚花了一个小时来审查我提供的代码,学生编写的代码以及大量其他示例代码(来自Apple和其他开发人员)之后,我再也不会继承SCNScene

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

在我查看了子SCNScene子集的所有代码中,这样做的原因是以编程方式生成场景。那是Rickster的选择#2。但是这些代码都不需要在SCNScene上。如果您在特定配置中构建一组节点,添加照明和查看约束以及设置材料,感觉就像是在构建SCNScene。但实际上你正在构建一个SCNNode个实例树。

所以我认为事后看来,选择#2并没有提供足够的理由来进行子类化。所有这些构造都可以通过编写生成器函数来完成,扩展到SCNNode

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