Swift 3(SpriteKit):游戏结束后重新设置GameScene

时间:2017-01-14 11:28:17

标签: swift sprite-kit reset skscene

我想重置'并且'重新启动' GameScene就好像GameScene首次被调用一样。我已经看过不同的方法来做这件事,但每次我收到警告我都试图将节点添加到已经拥有父节点的父节点。但是,在我的代码中,我删除了所有现有节点,因此我对如何重置GameScene感到困惑。这就是我现在这样做的方法(当我想从头开始重新启动GameScene并在GameScene类中调用时,会调用此代码):

let scene = GameScene(size: self.size)
scene.scaleMode = .aspectFill
let animation = SKTransition.fade(withDuration: 1.0) 
self.view?.presentScene(scene, transition: animation)
self.removeAllChildren()
self.removeAllActions()
self.scene?.removeFromParent()

1.Edited: 我意识到为什么我会收到此警告:"我试图将节点添加到已经存在的父节点有父母"是因为我拥有类外部场景的所有变量和全局变量。但是,现在当游戏重新开始时,游戏就在左下角。为什么会这样,我该如何解决这个问题? - FIXED

2.Edited: 现在一切正常,但现在我担心的是即使每个节点都被删除,deinit{}也不会被调用fps不会随着时间的推移而下降。以下是我在GameViewController中设置场景的方法以及我的GameScene(每个与场景相关的实例基本上都是相关的):

import UIKit
import SpriteKit
import GameplayKit

var screenSize = CGSize()

class GameViewController: UIViewController {

override func viewDidLoad() {
    super.viewDidLoad()
    if let view = self.view as! SKView? {
        // Load the SKScene from 'GameScene.sks'
        if let scene = SKScene(fileNamed: "GameScene") {
            // Set the scale mode to scale to fit the window
            scene.scaleMode = .aspectFill
            screenSize = scene.size     
            // Present the scene
            view.presentScene(scene)
        }

        view.ignoresSiblingOrder = true

        view.showsFPS = true
        view.showsNodeCount = true
    }
}

然后我的GameScene基本上是:

import SpriteKit
import GameplayKit

class GameScene: SKScene, SKPhysicsContactDelegate {
//Declare and initialise variables and enumerations here

deinit{print("GameScene deinited")}

override func didMove(to view: SKView) {
     //Setup scene and nodes
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    //Do other things depending on when and where you touch

    //When I want to reset the GameScene 
    let newScene = GameScene(size: self.size)
    newScene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
    newScene.scaleMode = self.scaleMode
    let animation = SKTransition.fade(withDuration: 1.0)
    self.view?.presentScene(newScene, transition: animation)
}

任何答案都将非常感谢:)

2 个答案:

答案 0 :(得分:9)

如何重置场景?

您只需要随时再次呈现新的相同场景。所以,你做得很好。

可能出现泄漏问题?

此外,如果您的游戏没有泄漏,意味着没有强大的参考周期,您甚至不需要self.removeAllChildren()self.removeAllActions() ......当然,如果您明确要停止过渡动画开始前的动作,使用此方法是有意义的。关键是,当场景解除分配时,依赖于它的所有对象也应该/将要解除分配。

但是,如果你从一开始就不知道你在做什么,以及如何防止泄漏,例如。你在块中使用强自我,这是一个动作序列的一部分,它永远重复,然后你肯定有泄漏,self.removeAllActions()可能有帮助(在许多情况下,但它不是一个最终的解决方案)。我建议一般性地阅读有关捕获列表和ARC的信息,因为了解这些情况是如何工作的。

场景是根节点

在场景上调用removeFromParent()无效。场景是根节点,因此无法在当前上下文中删除它。如果你检查场景的父属性,你会发现它是零。当然可以将场景添加到另一个场景,但是在这种情况下,作为子场添加的场景将充当普通节点。

最后,如何呈现新场景?很容易,像这样:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {


    let newScene = GameScene(size: self.size)
    newScene.scaleMode = self.scaleMode
    let animation = SKTransition.fade(withDuration: 1.0)
    self.view?.presentScene(newScene, transition: animation)


}

如果某些内容对您不起作用,则可能是您有泄漏(意味着您的场景未被解除分配)。要检查这一点,GameScene中的某个地方会覆盖deinit方法,如下所示:

deinit{print("GameScene deinited")} 

再向你解释一下......应该发生的是你应该呈现一个新场景,应该发生过渡,应该释放一个旧场景,你应该看到一个具有初始状态的新场景。

同样覆盖deinit只会告诉您场景是否正确解除分配。但它不会告诉你原因。您可以在代码中找到保留场景的内容。

答案 1 :(得分:0)

我可以想到有两种主要方法可以做到这一点。我这样做的主要方式是,如果游戏结束,(由于角色健康状况下降到零,或者它们与导致轮次结束的对象发生碰撞,或者时间到了或等等),当发生这种情况时我喜欢过渡到一个新的场景,这个场景是他们得分的总结屏幕,他们做了多远等等。

我这样做是通过在主GamePlay场景中设置一个bool变量来实现的。

    var gameOver: Bool = false

然后在激活的代码中导致游戏结束设置变量= true。

在更新功能中检查gameOver == true并转换到GameOverScene。

override func update(_ currentTime: TimeInterval) {
    // Called before each frame is rendered

    // Initialize _lastUpdateTime if it has not already been
    if (self.lastUpdateTime == 0) {
        self.lastUpdateTime = currentTime
    }

    // Calculate time since last update
    let dt = currentTime - self.lastUpdateTime

    // Update entities
    for entity in self.entities {
        entity.update(deltaTime: dt)
    }

    self.lastUpdateTime = currentTime

    if gameOver == true {
        print("Game Over!")

        let nextScene = GameOverScene(size: self.scene!.size)
        nextScene.scaleMode = self.scaleMode
        nextScene.backgroundColor = UIColor.black
        self.view?.presentScene(nextScene, transition: SKTransition.fade(with: UIColor.black, duration: 1.5))
    }
}

更新功能将检查每个帧渲染以查看游戏是否结束,如果发现它已经结束,它将执行您需要的任何操作,然后呈现下一个场景。

然后在GameOverScene上我放了一个按钮说“重试”,当他们点击它时再次触发GamePlayScene,运行视图DidLoad功能并从头开始设置GamePlayScene。

这是我如何处理它的一个例子。调用场景转换有几种不同的方法。如果它不能正常工作,你可以试试这个。

    if node.name == "retryButton" {

            if let scene = GameScene(fileNamed:"GameScene") {
                // Configure the view.
                let skView = self.view! as SKView

                /* Sprite Kit applies additional optimizations to improve rendering performance */
                skView.ignoresSiblingOrder = true

                /* Set the scale mode to scale to fit the window */
                scene.scaleMode = .aspectFill
                scene.size = skView.bounds.size

                skView.presentScene(scene, transition: SKTransition.fade(withDuration: 2.0))
            }
        }

这是我在过渡时处理游戏的首选方法。

另一种方法是创建一个函数,重置在播放过程中发生变化的所有变量。然后你可以使用上面更新功能中的相同代码,但不是过渡,你可以在场景上创建一个标签。如果用户单击该标签,它将触发该功能以重置所有已更改的变量,重置玩家位置,停止所有操作,重置玩家健康状况等。取决于您在游戏过程中改变了多少事情它可能更实用,(正如我发现的那样),转换到新场景以提供摘要,然后通过按钮重新加载GamePlayScene。然后一切都会像第一次用户输入主GamePlayScene一样加载。