【问题标题】:Swift 3 (SpriteKit): Reseting the GameScene after the game endsSwift 3 (SpriteKit):游戏结束后重置 GameScene
【发布时间】:2017-01-14 11:28:17
【问题描述】:

我想要“重置”和“重新启动”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: 我意识到为什么我会收到此警告:“我正在尝试将节点添加到已经有父节点的父节点”是因为我在课堂之外拥有场景的所有变量并作为全局变量。但是,现在当游戏重新开始时,游戏在左下角。为什么会出现这种情况,我该如何解决? - 已修复

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)
}

任何答案将不胜感激:)

【问题讨论】:

  • 关于“尝试将节点添加到已经有父节点的父节点”,我想说你弄得一团糟:) 如果旧场景没有被释放,并且你正在全局存储节点,就会发生这种情况并尝试将它们添加到当前场景中。不过,这只是一个猜测,也许您的旧场景已正确释放,而您实际上是在尝试将节点添加到另一个父节点。因此,需要更多信息。当您收到该错误时,您在什么时候将节点添加到场景中?
  • 如果游戏场景文件中的所有变量都定义在类之外(包括出现此错误的节点),是否可以?会不会是这个问题?
  • ^ 这行得通,但是现在当游戏在屏幕左下角重新开始游戏时?我该如何解决这个问题?

标签: swift sprite-kit reset skscene


【解决方案1】:

如何重置场景?

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

可能的泄漏问题?

另外,如果你的游戏没有漏洞,意味着没有强引用循环,你甚至不需要self.removeAllChildren()self.removeAllActions()...当然如果你明确想要在过渡动画之前停止动作开始,使用这种方法是有意义的。关键是,当场景解除分配时,依赖于它的所有对象也应该/将解除分配。

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

场景是根节点

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

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

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 只会告诉你场景是否被正确释放。但它不会告诉你为什么。您可以在代码中找到保留场景的内容。

【讨论】:

  • 这可行,但由于某种原因,现在当游戏重新启动时,游戏位于左下角。
  • @J.Treutlein 该代码基于您问题中的代码。表示场景不是从 sks 文件创建的,这意味着它的 anchorPoint 设置为 0.0、0.0。所以我不知道你真正想要什么,但如果你想让你的节点默认添加到屏幕中间,要么将场景的锚点更改为 0.5、0.5,要么从 sks 文件创建场景。顺便说一句,我想我已经在你的一个问题中解释了这一点:)
  • 非常感谢,这行得通!回答你的另一个问题:不,我还没有问过关于 SKScenes 的问题,但是是的,我的第一个问题是关于锚点的,但与此不同。所以你是对的:)谢谢你的帮助,无论如何你很清楚SpriteKit。
  • 实际上我只是尝试将 deinit{print("GameScene deinited")} 添加到游戏场景中,即使所有节点都已删除并且所有内容都已重置,它也不会打印。这只是一个奇怪的错误吗?
  • 但似乎没有任何泄漏。之前的 GameScene 没有任何内容。
【解决方案2】:

我能想到的主要方法有两种。我这样做的主要方式是,如果游戏结束,(由于角色健康下降到零,或者他们与导致回合结束的物体发生碰撞,或者时间到了等等),当这种情况发生时我喜欢过渡到一个新场景,该场景是他们得分的摘要屏幕,他们取得了多远等。

我通过在 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 时一样加载。

【讨论】:

  • 我现在的问题是游戏场景重置时游戏位置在左下角。当他们单击重试按钮时,就会发生这种情况。您能否使用单击重试按钮时使用的代码编辑您的帖子,以确保我没有有趣的事情?
  • @J.Treutlein 我刚刚编辑添加了我如何实现该重试按钮的示例。您还可以使用更新语句中存在的相同转换代码并更改场景名称。
  • 非常感谢您的帮助,但旋风的回答实际上解决了这个问题。无论如何,谢谢,这对其他有同样问题的人很有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多