麻烦子类化 SCNScene
Trouble subclassing SCNScene
我一直在尝试子class SCNScene,因为这似乎是保持场景相关逻辑的最佳位置。现在我不确定是否推荐这样做,所以我的第一个问题是——我应该使用 subclassing SCNScene,如果不是,为什么不呢? 文档似乎表明它很常见,但我在网上阅读了建议我不应该 class 它的评论。 废话,我正在查看 SKScene
。 SCNScene
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 (UIViewController
、UIControl
、等等)。也就是说,它是对某物(在本例中为 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值得一看
那么,怎么办?
这里有几个选择...
不要订阅class。将您的游戏逻辑保存在单独的 class 中。这不一定是视图控制器 class — 您可以拥有一个或多个 Game
对象,这些对象由您的视图控制器或应用程序委托拥有。 (如果您的游戏逻辑在多个场景之间转换或操纵多个场景的内容,这尤其有意义。)
Subclass,但安排将未存档的内容 SCNScene
放入您的 subclass 的实例中 — 实例化您的 subclass,加载一个SCNScene
,然后将其根节点的所有子节点移动到你的子class'根节点。
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 子类。
我一直在尝试子class SCNScene,因为这似乎是保持场景相关逻辑的最佳位置。现在我不确定是否推荐这样做,所以我的第一个问题是——我应该使用 subclassing SCNScene,如果不是,为什么不呢? 文档似乎表明它很常见,但我在网上阅读了建议我不应该 class 它的评论。 废话,我正在查看 SKScene
。 SCNScene
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 (UIViewController
、UIControl
、等等)。也就是说,它是对某物(在本例中为 3D 场景)的一般描述或容器,并不真正提供行为。由于行为方式不多,因此没有太多机会使用 subclass 覆盖行为。 (class 也不是围绕 subclass 入口点设计的,就像 viewDidLoad
和诸如此类的东西。)
虽然 可能 到 subclass SCNScene
,但您不会从中获益太多,并且某些 API 可能无法按您的预期工作...
However, the call to
GameScene.someMethod()
triggers anEXEC_BAD_ACCESS
error.Also, if I omit the call to
GameScene.someMethod
, the scene loads correctly, but the overridden initializer inGameScene
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值得一看
那么,怎么办?
这里有几个选择...
不要订阅class。将您的游戏逻辑保存在单独的 class 中。这不一定是视图控制器 class — 您可以拥有一个或多个
Game
对象,这些对象由您的视图控制器或应用程序委托拥有。 (如果您的游戏逻辑在多个场景之间转换或操纵多个场景的内容,这尤其有意义。)Subclass,但安排将未存档的内容
SCNScene
放入您的 subclass 的实例中 — 实例化您的 subclass,加载一个SCNScene
,然后将其根节点的所有子节点移动到你的子class'根节点。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 子类。