Sprite Kit:为所有场景只创建一次节点

Posted

技术标签:

【中文标题】Sprite Kit:为所有场景只创建一次节点【英文标题】:Sprite Kit: create node only once for all scenes 【发布时间】:2015-03-31 08:29:14 【问题描述】:

通常在精灵套件游戏中,当出现新场景时,旧场景中的所有节点及其内容都会自动删除。现在,如果不应该删除像“HUD”这样的节点呢? sprite kit 中是否有任何方法可以只创建一次节点并在所有场景中使用它,而无需在每个新场景中一次又一次地删除和创建它?必须有一种技术使它成为可能。如果不可能的话,那是一个严重的精灵套件设计问题。但我不这么认为。单例技术非常适用于音频播放器,它只创建一次并在所有场景中使用。 surley 有一种方法可以只创建一次节点并在所有场景中使用它。感谢您的任何想法。

【问题讨论】:

你到底在争取什么?是实际创建节点的代码吗?存储值?两者都有? @sangony 我只是举例说“HUD”。我正在努力的是,我只想创建一个节点(例如 SKEmitterNode)并在所有游戏场景中使用它。当我创建一个节点并将其添加到场景中时,该节点会在出现新场景时自动删除。我必须在新场景中再次创建节点。我必须一次又一次地在每个新场景中创建相同的节点。因此,我只想创建一次节点并在所有场景中使用它,而不是在每个场景中删除和创建它。我希望,现在很清楚,我在争取什么。否则让我知道。感谢您的任何想法。 好吧... ... ...这将被称为类实例化。为您的节点创建一个自定义 SKEmitterNode 类,并在您加载新场景时创建它的一个实例。 谢谢。我不确定,如果我理解正确的话。你的意思是,我创建了一个自定义类 MyEmitterNode :SKEmitterNode。然后我创建一个 MyEmitterNode 类的新实例并将其添加到我的新场景中,如下所示:MyEmitterNode *myEmitter = [NSKeyedUnarchiver unarchiveObjectWithFile:[[NSBundle mainBundle] pathForResource:@"MyParticle" ofType:@"sks"]]; [sceneOne addChild:myEmitter]; 当我呈现一个新场景 (sceneTwo) 时,该节点 (myEmitter) 是否应该不被移除? (对不起我的英语) 如果你不熟悉子类的概念,你应该阅读这个主题,因为它是任何程序的基石。 【参考方案1】:

您无法创建在场景之间持续存在的节点。呈现新场景后,您需要将节点添加到这个新场景。

出于这个原因,由于这个问题,我没有按照 Apple 在文档中描述的方式使用 SKScenes。不仅每次都必须将节点添加到新场景中很麻烦,而且对于像后台节点这样应该始终存在的节点来说效率也非常低。

所以我所做的是创建 2 个场景,一个用于游戏场景,一个用于菜单 (GUI)。

对于菜单场景,我将 SKNodes 子类化为我的界面,然后在这些节点上使用 SKActions 在屏幕上呈现和关闭它们,因此感觉就像用户在场景之间转换。这为您提供了完全自定义,因为您可以呈现多个节点,您可以将节点永久保留在屏幕上等等。

通过子类化 SKNode,您可以像处理场景一样组织代码。每个节点将代表您应用程序中的一个“场景”。然后你只需要编写一个方法来呈现和关闭这些节点。


我在下面添加了一些示例代码,以展示使用SKNodes 作为“场景”的一种实现。示例代码有一个名为SceneNode 的基类,我们将其子类化(就像您一样将继承SKScene)。在这个实现中,我使用GameScene 来处理场景节点之间的所有转换*。我还跟踪当前场景节点,以便在场景改变大小时更新其布局(例如 OS X 上的旋转或窗口大小调整**)。您的游戏可能不需要它,但它是动态布局节点的好方法。您想要添加到背景中或保留的任何内容,只需将其添加到GameScene。您想添加到场景中的任何内容,只需将 SceneNode 子类化,然后过渡到它即可。

*您可以轻松地直接从其他场景节点呈现场景节点,而不是通过 GameScene。但是我发现使用 GameScene 处理节点之间的过渡效果非常好,尤其是当您有许多具有复杂过渡的场景时。 **在 OS X 上存在一个错误,调整窗口大小不会调用场景的 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


【讨论】:

非常感谢。这就是我的意思,也是我正在寻找的。 IMO 这是精灵套件的概念/设计的严重缺陷。我认为,必须可以在所有场景中使用相同的节点(例如背景发射器节点),而无需在每个新场景中每次都创建它。我接受你很好的回答,我会尽力去做,正如你所描述的。谢谢!

以上是关于Sprite Kit:为所有场景只创建一次节点的主要内容,如果未能解决你的问题,请参考以下文章

Sprite kit场景编辑器中节点相对位置的解决方案?

使用 Swift 在 sprite kit 中的多个场景中重用相同的 sprite 节点

Sprite Kit SKShapeNode 创建两个节点而不是一个

iOS 7 Sprite Kit 释放内存

Sprite kit:删除特定节点而不是所有节点

如何确定节点出现在 sprite kit 中的顺序