麻烦子类化 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
更像是基本数据/模型类(NSString
、UIImage
等),而不是行为/控制器/视图类(UIViewController
、UIControl
等)。也就是说,它是对某物(在本例中为 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 子类。
【讨论】:
说得好。如果你有一个SCNNode
s 的集合,那么你真正拥有的只是 data...没有理由在你的应用程序每次运行时都在代码中构建它。在代码中构建一次并将其归档到 .scn
文件中,或者使用 Xcode 中的场景编辑器构建一个 .scn
文件。
SCNScene 不是在 Apple 的示例代码项目 Bananas 中的子类,特别是在 AAPLGameSimulation.h 中吗?以上是关于麻烦子类化 SCNScene的主要内容,如果未能解决你的问题,请参考以下文章
麻烦将 UITextField 链接到 UITableViewCell 子类