麻烦子类化 SCNScene

Posted

技术标签:

【中文标题】麻烦子类化 SCNScene【英文标题】:Trouble subclassing SCNScene 【发布时间】:2016-04-04 17:39:57 【问题描述】:

我一直在尝试将 SCNScene 子类化,因为这似乎是保留与场景相关的逻辑的最佳位置。现在我不确定这是否被推荐,所以我的第一个问题是 - 我应该继承 SCNScene,如果不是,为什么不呢? 文档似乎表明它很常见,但我在网上阅读了 cmets,建议我不应该对它进行子类化。 废话,我正在查看 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")
    


注意:根据this question 的答案,我正在使用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 中的子类化。或者也许有一些我错过了的事情的顺序方面。

【问题讨论】:

【参考方案1】:

我应该继承SCNScene,如果不是,为什么不呢?

不,你不需要子类化,你最好不要子类化。 SCNScene 更像是基本数据/模型类(NSStringUIImage 等),而不是行为/控制器/视图类(UIViewControllerUIControl 等)。也就是说,它是对某物(在本例中为 3D 场景)的一般描述或容器,并不真正提供行为。由于行为方式不多,因此没有太多机会用子类覆盖行为。 (围绕子类入口点设计的类也不是要被覆盖的,比如viewDidLoad 等等。)

虽然可能子类化 SCNScene,但这样做并不会带来太多好处,而且某些 API 可能无法按预期工作...

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

另外,如果我省略了对 GameScene.someMethod 的调用,场景会正确加载,但似乎不会调用 GameScene 中被覆盖的初始化程序。

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

因为您没有获得子类的实例,所以不会调用子类初始化程序,并且尝试调用子类的方法会导致运行时错误。

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

那么,该怎么办?

你有几个选择...

    不要子类化。将你的游戏逻辑放在一个单独的类中。这不一定是视图控制器类——您可以拥有一个或多个 Game 对象,这些对象属于您的视图控制器或应用程序委托。 (如果您的游戏逻辑在多个场景之间转换或操纵多个场景的内容,这将特别有意义。)

    子类,但安排将未归档的 SCNScene 中的内容放入子类的实例中 — 实例化您的子类,加载 SCNScene,然后将其根节点的所有子节点移动到子类的根节点中。

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

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

【讨论】:

好的cmets。我会说在需要的地方使用#1 和#2。但请记住,为了执行 #1,您需要一个场景来将加载的场景的内容放入其中 - 这是 #2 派上用场的地方。 感谢您的详细回答。结合哈尔的回答,填补了很多空白。虽然 Apple Docs 和在线教程很棒,但作为一个 SceneKit 菜鸟,感觉花在解决所有部分应该如何组合在一起的时间太少了。【参考方案2】:

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

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

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

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

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

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

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

【讨论】:

说得好。如果你有一个SCNNodes 的集合,那么你真正拥有的只是 data...没有理由在你的应用程序每次运行时都在代码中构建它。在代码中构建一次并将其归档到 .scn 文件中,或者使用 Xcode 中的场景编辑器构建一个 .scn 文件。 SCNScene 不是在 Apple 的示例代码项目 Bananas 中的子类,特别是在 AAPLGameSimulation.h 中吗?

以上是关于麻烦子类化 SCNScene的主要内容,如果未能解决你的问题,请参考以下文章

麻烦将 UITextField 链接到 UITableViewCell 子类

java关于子类父类类型转换和引用的问题 麻烦给接受一下这道题的要点谢谢

Spring IOC&DI

Base关键字用法

麻烦纹理 VBO

UISlider thumbImage 无法退出 uislider 框架