通过转换保存 GameScene 的状态

Save state of GameScene through transitions

我想为我的 SpriteKit 游戏建立一个清单。为此,当我按下暂停按钮时,我想转到另一个代表我的库存的 SKScene 文件。我的问题是,当我从我的 InventoryScene 转换回我的 GameScene 时,GameScene 会加载全新的。这是我的 GameScene class:

的转换代码
func loadInventory(){
    if !transitionInProgress{
        transitionInProgress = true
        if let scene = InventoryScene(fileNamed: "Inventory"){
            Globals.InventoryGlobals.levelBeforeSwitch = currentLevel
            scene.scaleMode = .aspectFill
            let transition = SKTransition.push(with: .down, duration: 0.5)
            self.view?.presentScene(scene, transition: transition)
        }
    }
}

使用此代码,我将转到我的 InventoryScene。

现在在我的 InventoryScene 中,我想返回到我的 GameScene 中:

func loadLevel(level: String){
    if !transitionInProgress{
        transitionInProgress = true
        if let scene = GameScene(fileNamed: level){
            scene.currentLevel = level
            scene.scaleMode = .aspectFill
            let transition = SKTransition.doorsOpenHorizontal(withDuration: 1)
            self.view?.presentScene(scene, transition: transition)
        }
    }
}

过渡正常,但我的问题是 GameScene 加载全新,这显然是因为我实例化了一个新的 GameScene。所以当玩家在关卡中间然后转到 Inventory 并返回 GameScene 时,玩家回到了关卡的开头。如果我从 Inventory 回到场景,我想像以前一样成为 GameScene(玩家位置、敌人生命值等)

有人知道我该怎么做吗?

留住你的场景很简单,你只需要留住强引用的场景就可以了

在你的ViewController中,就像存储一个变量一样简单

class ViewController : UIViewController
{
    var gameScene = GameScene(fileNamed:"GameScene")
}

现在,只要您的视图控制器处于活动状态,您的场景就会处于活动状态。

要访问它,您只需要找到一种方法告诉 MenuScene 您的视图控制器在哪里,然后呈现场景。

class ViewController : UIViewController
{
    var gameScene = GameScene(fileNamed:"GameScene")
    lazy var skView : SKView = self.view as! SKView
    func gotoMenu()
    {
         let menu = MenuScene(fileNamed"MenuScene")
         menu.viewController = self
         skView.presentScene(menu)
    }
}

class MenuScene : SKScene
{
   var viewController : ViewController!
   func returnToGame()
   {
       view.presentScene(viewcontroller.gameScene)
   }
}

但是,如果您不想一直使用自定义 SKScene classes,使用视图控制器,或者宁愿依赖组件,为什么没有方便的方法呢?回到场景

好吧,我的朋友,这就是 userData 发挥作用的地方

class GameScene : SKScene
{
    func gotoMenu()
    {
         let menu = MenuScene(fileNamed:"MenuScene")
         menu.userData = menu.userData ?? ["":Any]()
         menu.userData["backToScene"] = self
         view.presentScene(menu)
    }
}

class MenuScene : SKScene
{
   func returnToGame()
   {
       guard let userData = userData, let scene = userData["backToScene"] as? SKScene
       view.presentScene(scene)
   }
}

由于我们将其保留在用户数据中,因此我们现在可以在我们可以访问菜单场景的任何地方呈现旧场景。

userData 也很擅长转移库存,当然我会创建一个 class 来管理库存,并通过 userData

传递参考

现在,要创建一个覆盖当前场景的菜单,就像在场景中应用一个新节点一样简单。

您甚至可以使用单独的 SKS 文件来布置您的菜单,并叠加它:

class GameScene : SKScene
{
    let menu = MenuScene(fileNamed:"MenuScene")

    func overlayMenu()
    {
         scene.addChild(menu)  //You probably want to add an SKCameraNode, and add it to the camera instead
    }
    override func update(currentTime: CFTimeInterval) 
    {
        if menu.parent != nil
        {
            menu.update(currentTime:currentTime) //do this only when you need to have a constant update call,  be sure to include additional functionality like `didFinishUpdate` in the approprate functions when needed
        }
    }


}

当然现在是开发什么叫worldNode的好时机,也可以称为gameNode

从本质上讲,这个节点是包含所有游戏元素的节点。 这允许您添加可以暂停游戏的覆盖节点。

您的场景层次结构如下所示:

SKScene
--worldNode
----属于游戏的所有节点
--menuNode
----属于菜单的所有节点

现在任何时候,menu 都可以将 worldNode 的 isPaused 状态设置为 true,允许游戏暂停并仍然让您能够与 menuNode 进行交互

这正是您想要的。

class GameScene: SKScene {
class InventoryManager: UIView {
    override init(frame: CGRect) {
        super.init(frame: CGRect(x: 0, y: 0, width: 300, height: 200))

        //here is where you set up anything you want to display inside the view
        self.backgroundColor = UIColor.green

        let Button = UIButton()
        Button.frame = CGRect(x: 0, y: 0, width: 40, height: 20)
        Button.backgroundColor = UIColor.red

        let TouchView = UIView()
        TouchView.frame = self.frame

        Button.center.x = TouchView.center.x
        Button.center.y = TouchView.center.y

        TouchView.backgroundColor = UIColor.brown

        self.addSubview(TouchView)
        //make sure you add any objects that you want to the TouchView and not to the self
        TouchView.addSubview(Button)

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
var InventoryView = InventoryManager()

var Node = SKSpriteNode()

override func didMove(to view: SKView) {
    //this keeps the view centered in the screen
    InventoryView.center.x = (self.view?.center.x)!
    InventoryView.center.y = (self.view?.center.y)!



    Node = SKSpriteNode(color: UIColor.blue, size: CGSize(width: 40, height: 40))
    Node.position = CGPoint(x: self.frame.size.width / 3, y: self.frame.size.height / 3)

    self.addChild(Node)
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    for touch in touches {
        let location = touch.location(in: self)

        if !(self.view?.frame.contains(location))! {
            InventoryView.removeFromSuperview()
        }
        if Node.contains(location) && InventoryView.isDescendant(of: self.view!) {
            self.view?.addSubview(InventoryView)
        }

    }
}

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

单击蓝色按钮再次将其添加到场景中,单击其他任何地方将其从场景中删除

我在我的 Spritekit 游戏中一直进行中途停留 windows,它不一定像您想的那么复杂。以下是如何在不离开场景的情况下在 Spritekit 中完成所有操作。

为您的 InventoryDialog 创建一个新的 class,它是 SKSpriteNode 的子class。

import SpriteKit

protocol InventoryDialogDelegate: class {
    func close()
}

class InventoryDialog: SKSpriteNode {

    private var closeButton: SKSpriteNode!
    weak var delegate: InventoryDialogDelegate?

    init(size: CGSize) {
        super.init(texture: nil, color: .clear, size: size)

        name = "inventoryDialog"

        //do some background design work here   
        let background = SKSpriteNode(color: .white, size: self.size)
        background.zPosition = 1
        addChild(background) 

        closeButton = SKSpriteNode(texture: SKTexture(imageNamed: "closeButton"))
        closeButton.position = CGPoint(x: self.size.width / 2 - closeButton.size.width / 2, y: self.size.height / 2 - closeButton.size.height / 2)
        closeButton.zPosition = 2
        addChild(closeButton)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        let touchLocation = touch!.location(in: self)
        if closeButton.contains(touchLocation) {
            close()
        }
    }

    func close() {
        self.delegate?.close()
    }
}

在您的 GameScene 文件中

class GameScene: SKScene {

    var inventoryDialog: InventoryDialog!
    var openButton: SKSpriteNode!

    override func didMove(to view: SKView) {
        openButton = SKSpriteNode(texture: SKTexture(imageNamed: "openButton"))
        openButton.position = CGPoint(x: self.size.width / 2 - closeButton.size.width / 2, y: self.size.height / 2 - closeButton.size.height / 2)
        openButton.zPosition = 2
        addChild(openButton) 
    }

    func displayInventoryDialog() {
        backgroundBlocker = SKSpriteNode(imageNamed: "background3")
        backgroundBlocker.size = self.size
        backgroundBlocker.zPosition = 4999
        addChild(backgroundBlocker)

        inventoryDialog = InventoryDialog(size: CGSize(width: 500, height: 800))
        inventoryDialog.delegate = self
        inventoryDialog.zPosition = 5000
        addChild(inventoryDialog)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        //pause any action that you don't want running while the dialog is open
        gameLayer.isPaused = true

        let touch = touches.first
        let touchLocation = touch!.location(in: self)
        if openButton.contains(touchLocation) {
            displayInventoryDialog()
        }
    }
}

//MARK: - InventoryDialogDelegate Methods

extension GameScene: InventoryDialogDelegate {

    func close() {
        //at this point you could update any GUI nesc. based on what happened in your dialog
        backgroundBlocker.removeFromParent()
        inventoryDialog?.removeFromParent()
        gameLayer.isPaused = false
    }
}