Sprite Kit:为所有场景只创建一次节点
Sprite Kit: create node only once for all scenes
通常在精灵套件游戏中,当出现新场景时,旧场景中的所有节点及其内容会自动删除。现在,如果不应该删除像 "HUD" 这样的节点呢? sprite kit 中是否有任何方法可以只创建一次节点并在所有场景中使用它,而无需在每个新场景中每次都一次又一次地删除和创建它?必须有一种技术使之成为可能。这是一个严重的 sprite 套件设计问题,如果不可能的话。但我不这么认为。单例技术与音频播放器配合得很好,它只创建一次并在所有场景中使用。有一种方法可以只创建一次节点并在所有场景中使用它。谢谢你的想法。
您无法创建在场景之间持续存在的节点。呈现新场景后,您需要将节点添加到这个新场景。
因此,由于这个问题,我没有按照 Apple 在文档中描述的方式使用 SKScenes。不仅每次都必须将节点添加到新场景中很麻烦,而且对于应该始终存在的背景节点等节点,效率极低。
所以我所做的是创建 2 个场景,一个用于游戏场景,一个用于菜单 (GUI)。
对于菜单场景,我为我的界面子class SKNodes,然后在这些节点上使用 SKActions 在屏幕上呈现和关闭它们,这样用户感觉就像在场景之间转换。这为您提供了完全自定义,因为您可以呈现多个节点,可以将节点永久保留在屏幕上等。
通过子class SKNodes,您可以像在场景中一样组织代码。每个节点将代表您应用程序中的一个 "scene"。然后你只需要写一个方法来呈现和关闭这些节点。
我在下面添加了一些示例代码来展示使用 SKNodes
作为 "Scenes." 的一种实现示例代码有一个基础 class 称为 SceneNode
,我们将其子 class(就像您将 class 子 SKScene
一样)。在此实现中,我使用 GameScene
来处理场景节点之间的所有转换*。我还跟踪当前场景节点,以便在场景大小发生变化时更新其布局(例如旋转或 window 在 OS X** 上调整大小)。您的游戏可能不需要这个,但这是动态布局节点的好方法。任何您想添加到背景或保留的内容,只需将其添加到 GameScene
。任何您想添加到场景中的内容,只需从 class 到 SceneNode
,过渡到它就可以了。
*您可以轻松地直接从其他场景节点呈现场景节点,而不是通过 GameScene。但是我发现使用 GameScene 来处理节点之间的转换非常有效,尤其是当您有许多具有复杂转换的场景时。
** OS X 上有一个错误,调整 window 的大小不会调用场景的 didChangeSize。需要手动调用。
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = GameScene(size:self.view.bounds.size)
scene.scaleMode = .ResizeFill
(self.view as! SKView).presentScene(scene)
}
}
class GameScene: SKScene {
var currentSceneNode: SceneNode!
override func didMoveToView(view: SKView) {
self.backgroundColor = SKColor.whiteColor()
transitionToScene(.Menu)
}
override func didChangeSize(oldSize: CGSize) {
currentSceneNode?.layout()
}
func transitionToScene(sceneType: SceneTransition) {
switch sceneType {
case .Menu:
currentSceneNode?.dismissWithAnimation(.Right)
currentSceneNode = MenuSceneNode(gameScene: self)
currentSceneNode.presentWithAnimation(.Right)
case .Scores:
currentSceneNode?.dismissWithAnimation(.Left)
currentSceneNode = ScoresSceneNode(gameScene: self)
currentSceneNode.presentWithAnimation(.Left)
default: fatalError("Unknown scene transition.")
}
}
}
class SceneNode: SKNode {
weak var gameScene: GameScene!
init(gameScene: GameScene) {
self.gameScene = gameScene
super.init()
}
func layout() {}
func presentWithAnimation(animation:Animation) {
layout()
let invert: CGFloat = animation == .Left ? 1 : -1
self.position = CGPoint(x: invert*gameScene.size.width, y: 0)
gameScene.addChild(self)
let action = SKAction.moveTo(CGPoint(x: 0, y: 0), duration: 0.3)
action.timingMode = SKActionTimingMode.EaseInEaseOut
self.runAction(action)
}
func dismissWithAnimation(animation:Animation) {
let invert: CGFloat = animation == .Left ? 1 : -1
self.position = CGPoint(x: 0, y: 0)
let action = SKAction.moveTo(CGPoint(x: invert*(-gameScene.size.width), y: 0), duration: 0.3)
action.timingMode = SKActionTimingMode.EaseInEaseOut
self.runAction(action, completion: {self.removeFromParent()})
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class MenuSceneNode: SceneNode {
var label: SKLabelNode
var container: SKSpriteNode
override func layout() {
container.position = CGPoint(x: gameScene.size.width/2.0, y: gameScene.size.height/2.0)
}
override init(gameScene: GameScene) {
label = SKLabelNode(text: "Menu Scene")
label.horizontalAlignmentMode = .Center
label.verticalAlignmentMode = .Center
container = SKSpriteNode(color: UIColor.blackColor(), size: CGSize(width: 200, height: 200))
container.addChild(label)
super.init(gameScene: gameScene)
self.addChild(container)
self.userInteractionEnabled = true
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
self.gameScene.transitionToScene(.Scores)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ScoresSceneNode: SceneNode {
var label: SKLabelNode
var container: SKSpriteNode
override func layout() {
container.position = CGPoint(x: gameScene.size.width/2.0, y: gameScene.size.height/2.0)
}
override init(gameScene: GameScene) {
label = SKLabelNode(text: "Scores Scene")
label.horizontalAlignmentMode = .Center
label.verticalAlignmentMode = .Center
container = SKSpriteNode(color: UIColor.blackColor(), size: CGSize(width: 200, height: 200))
container.addChild(label)
super.init(gameScene: gameScene)
self.addChild(container)
self.userInteractionEnabled = true
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
self.gameScene.transitionToScene(.Menu)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
enum SceneTransition{
case Menu, Scores
}
enum Animation {
case Left, Right, None
}
通常在精灵套件游戏中,当出现新场景时,旧场景中的所有节点及其内容会自动删除。现在,如果不应该删除像 "HUD" 这样的节点呢? sprite kit 中是否有任何方法可以只创建一次节点并在所有场景中使用它,而无需在每个新场景中每次都一次又一次地删除和创建它?必须有一种技术使之成为可能。这是一个严重的 sprite 套件设计问题,如果不可能的话。但我不这么认为。单例技术与音频播放器配合得很好,它只创建一次并在所有场景中使用。有一种方法可以只创建一次节点并在所有场景中使用它。谢谢你的想法。
您无法创建在场景之间持续存在的节点。呈现新场景后,您需要将节点添加到这个新场景。
因此,由于这个问题,我没有按照 Apple 在文档中描述的方式使用 SKScenes。不仅每次都必须将节点添加到新场景中很麻烦,而且对于应该始终存在的背景节点等节点,效率极低。
所以我所做的是创建 2 个场景,一个用于游戏场景,一个用于菜单 (GUI)。
对于菜单场景,我为我的界面子class SKNodes,然后在这些节点上使用 SKActions 在屏幕上呈现和关闭它们,这样用户感觉就像在场景之间转换。这为您提供了完全自定义,因为您可以呈现多个节点,可以将节点永久保留在屏幕上等。
通过子class SKNodes,您可以像在场景中一样组织代码。每个节点将代表您应用程序中的一个 "scene"。然后你只需要写一个方法来呈现和关闭这些节点。
我在下面添加了一些示例代码来展示使用 SKNodes
作为 "Scenes." 的一种实现示例代码有一个基础 class 称为 SceneNode
,我们将其子 class(就像您将 class 子 SKScene
一样)。在此实现中,我使用 GameScene
来处理场景节点之间的所有转换*。我还跟踪当前场景节点,以便在场景大小发生变化时更新其布局(例如旋转或 window 在 OS X** 上调整大小)。您的游戏可能不需要这个,但这是动态布局节点的好方法。任何您想添加到背景或保留的内容,只需将其添加到 GameScene
。任何您想添加到场景中的内容,只需从 class 到 SceneNode
,过渡到它就可以了。
*您可以轻松地直接从其他场景节点呈现场景节点,而不是通过 GameScene。但是我发现使用 GameScene 来处理节点之间的转换非常有效,尤其是当您有许多具有复杂转换的场景时。
** OS X 上有一个错误,调整 window 的大小不会调用场景的 didChangeSize。需要手动调用。
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = GameScene(size:self.view.bounds.size)
scene.scaleMode = .ResizeFill
(self.view as! SKView).presentScene(scene)
}
}
class GameScene: SKScene {
var currentSceneNode: SceneNode!
override func didMoveToView(view: SKView) {
self.backgroundColor = SKColor.whiteColor()
transitionToScene(.Menu)
}
override func didChangeSize(oldSize: CGSize) {
currentSceneNode?.layout()
}
func transitionToScene(sceneType: SceneTransition) {
switch sceneType {
case .Menu:
currentSceneNode?.dismissWithAnimation(.Right)
currentSceneNode = MenuSceneNode(gameScene: self)
currentSceneNode.presentWithAnimation(.Right)
case .Scores:
currentSceneNode?.dismissWithAnimation(.Left)
currentSceneNode = ScoresSceneNode(gameScene: self)
currentSceneNode.presentWithAnimation(.Left)
default: fatalError("Unknown scene transition.")
}
}
}
class SceneNode: SKNode {
weak var gameScene: GameScene!
init(gameScene: GameScene) {
self.gameScene = gameScene
super.init()
}
func layout() {}
func presentWithAnimation(animation:Animation) {
layout()
let invert: CGFloat = animation == .Left ? 1 : -1
self.position = CGPoint(x: invert*gameScene.size.width, y: 0)
gameScene.addChild(self)
let action = SKAction.moveTo(CGPoint(x: 0, y: 0), duration: 0.3)
action.timingMode = SKActionTimingMode.EaseInEaseOut
self.runAction(action)
}
func dismissWithAnimation(animation:Animation) {
let invert: CGFloat = animation == .Left ? 1 : -1
self.position = CGPoint(x: 0, y: 0)
let action = SKAction.moveTo(CGPoint(x: invert*(-gameScene.size.width), y: 0), duration: 0.3)
action.timingMode = SKActionTimingMode.EaseInEaseOut
self.runAction(action, completion: {self.removeFromParent()})
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class MenuSceneNode: SceneNode {
var label: SKLabelNode
var container: SKSpriteNode
override func layout() {
container.position = CGPoint(x: gameScene.size.width/2.0, y: gameScene.size.height/2.0)
}
override init(gameScene: GameScene) {
label = SKLabelNode(text: "Menu Scene")
label.horizontalAlignmentMode = .Center
label.verticalAlignmentMode = .Center
container = SKSpriteNode(color: UIColor.blackColor(), size: CGSize(width: 200, height: 200))
container.addChild(label)
super.init(gameScene: gameScene)
self.addChild(container)
self.userInteractionEnabled = true
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
self.gameScene.transitionToScene(.Scores)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ScoresSceneNode: SceneNode {
var label: SKLabelNode
var container: SKSpriteNode
override func layout() {
container.position = CGPoint(x: gameScene.size.width/2.0, y: gameScene.size.height/2.0)
}
override init(gameScene: GameScene) {
label = SKLabelNode(text: "Scores Scene")
label.horizontalAlignmentMode = .Center
label.verticalAlignmentMode = .Center
container = SKSpriteNode(color: UIColor.blackColor(), size: CGSize(width: 200, height: 200))
container.addChild(label)
super.init(gameScene: gameScene)
self.addChild(container)
self.userInteractionEnabled = true
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
self.gameScene.transitionToScene(.Menu)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
enum SceneTransition{
case Menu, Scores
}
enum Animation {
case Left, Right, None
}