Swift 3: Создание меню паузы в SpriteKit путем наложения SKView?

контекст

В то время как есть некоторые игры, которые предпочитают отказаться от меню паузы - предположительно из-за короткой продолжительности игры, такие как " Не молоть", - я лично считаю, что приостановка игры является критически важной функцией, и я хотел бы узнать, как реализовать ее в Swift 3 для SpriteKit.

Я видел попытки сделать это с помощью UIAlertController, который работает, но я - возможно, ложно - считаю, что лучшей альтернативой было бы наложение SKView на вершине течения SKView,

Я посмотрел на DemoBots от Apple, чтобы узнать, смогу ли я понять, как они приостанавливают игру. Однако после загрузки и запуска на моем устройстве это вызвало ошибку, поэтому я не склонен следовать этому примеру. Однако, если кто-то сможет подробно объяснить множество файлов, таких как "LevelScene+Pause", "SceneManager", "SceneOperation" и т. Д., И то, как они работают вместе, это также будет здорово.

Вопрос

Как я могу наложить SKView над GameScene сделать меню паузы?

Минимальный рабочий пример

MWE, Stackru SpriteKit с меню, является "игрой" для "скелетов" для контекстуализации ответов. Пожалуйста, ответьте на вопрос относительно MWE

Обновить

Ниже приведена модифицированная версия из MWE файла "GameScene". При этом учитывается добавление основного узла для элементов, которые должны быть приостановлены, и другого узла для меню паузы.

Хотя меню паузы работает, фон все еще работает, хотя gameNode.isPaused = true, (Попробуйте нажать самый левый синий спрайт).

//

//  GameScene.swift
//  Stackru
//
//  Created by Sumner on 1/17/17.
//  Copyright © 2017 Sumner. All rights reserved.
//

import SpriteKit
import GameplayKit

class GameScene: SKScene {
    var cam: SKCameraNode!



    var sprite = SKSpriteNode(imageNamed: "sprite")
    var sprite2 = SKSpriteNode(imageNamed: "sprite2")

    let pauseLabel = SKLabelNode(text: "Pause!")


    /*
     *
     * START: NEW CODE
     *
     */
    let gameNode = SKNode()
    var pauseMenuSprite: SKShapeNode!
    let pauseMenuTitleLabel = SKLabelNode(text: "Pause Menu")
    let pauseMenuContinueLabel = SKLabelNode(text: "Resume game?")
    let pauseMenuToMainMenuLabel = SKLabelNode(text: "Main Menu?")
    /*
     *
     * END: NEW CODE
     *
     */


    var timeStart: Date!

    init(size: CGSize, difficulty: String) {
        super.init(size: size)
        gameDifficulty = difficulty
        timeStart = Date()
        /*
         *
         * START: NEW CODE
         *
         */
        pauseMenuSprite = SKShapeNode(rectOf: size)
        /*
         *
         * END: NEW CODE
         *
         */
    }

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

    override func didMove(to view: SKView) {
        backgroundColor = SKColor.white

        print("Game starting with \(gameDifficulty) difficulty")




        // Scale Sprites
        sprite.setScale(0.3)
        sprite2.setScale(0.3)

        sprite.position = CGPoint(x: size.width/4,y: size.height/2)
        sprite2.position = CGPoint(x: size.width/4 * 3,y: size.height/2)

        /*
         *
         * START: NEW CODE
         *
         */
        gameNode.addChild(sprite)
        gameNode.addChild(sprite2)
        addChild(gameNode)
        /*
         *
         * END: NEW CODE
         *
         */

        if gameDifficulty == "hard" {
            let sprite3 = SKSpriteNode(imageNamed: "sprite")
            sprite3.setScale(0.3)
            sprite3.position = CGPoint(x: size.width/4 * 2,y: size.height/2)
            addChild(sprite3)
        }



        pauseLabel.fontColor = SKColor.black
        pauseLabel.position = CGPoint(x: size.width/4 * 2,y: size.height/4)
        addChild(pauseLabel)

    }



    func touchDown(atPoint pos : CGPoint) {

    }

    func touchMoved(toPoint pos : CGPoint) {

    }

    func touchUp(atPoint pos : CGPoint) {

    }

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

        for t in touches { self.touchDown(atPoint: t.location(in: self)) }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        for t in touches { self.touchMoved(toPoint: t.location(in: self)) }
    }

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

        let pausedTouchLocation = touch?.location(in: pauseMenuSprite)

        if sprite.contains(touchLocation) {
            print("You tapped the blue sprite")
            /*
            let alert = UIAlertController(title: "Alert", message: "Message", preferredStyle: UIAlertControllerStyle.alert)
            let action = UIAlertAction(title: "Ok", style: .default) { action in
                // Handle when button is clicked
                let reveal = SKTransition.doorsOpenVertical(withDuration: 0.5)
                let menuScene = MenuScene(size: self.size)
                self.view?.presentScene(menuScene, transition: reveal)



            }
            alert.addAction(action)
            if let vc = self.scene?.view?.window?.rootViewController {
                vc.present(alert, animated: true, completion: nil)
            }
            */

        }

        if sprite2.contains(touchLocation) {
            print("You tapped the purple sprite")

            let now = Date()
            let howLong = now.timeIntervalSinceReferenceDate - timeStart.timeIntervalSinceReferenceDate

            let reveal = SKTransition.doorsOpenVertical(withDuration: 0.5)
            let scoreScene = ScoreScene(size: self.size, score: howLong)
            self.view?.presentScene(scoreScene, transition: reveal)
        }


        /*
         *
         * START: NEW CODE
         *
         */
        if pauseMenuContinueLabel.contains(pausedTouchLocation!) {
            pauseMenuSprite.removeFromParent()
            pauseMenuSprite.removeAllChildren()

            gameNode.isPaused = true
        }


        if pauseMenuToMainMenuLabel.contains(pausedTouchLocation!) {
            let reveal = SKTransition.doorsOpenVertical(withDuration: 0.5)
            let menuScene = MenuScene(size: self.size)
            self.view?.presentScene(menuScene, transition: reveal)
        }


        if pauseLabel.contains(touchLocation) {
            print("pause")
            setParametersForPauseMenu(size: size)
            addChild(pauseMenuSprite)

            gameNode.isPaused = true

        }

        /*
         *
         * END: NEW CODE
         *
         */

    }

    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        for t in touches { self.touchUp(atPoint: t.location(in: self)) }
    }


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

    /*
     *
     * START: NEW CODE
     *
     */
    func setParametersForPauseMenu(size: CGSize) {
        pauseMenuSprite.fillColor = SKColor.white
        pauseMenuSprite.alpha = 0.85
        pauseMenuSprite.position = CGPoint(x: size.width / 2, y: size.height / 2)
        pauseMenuSprite.zPosition = 100

        pauseMenuTitleLabel.fontColor = SKColor.black
        pauseMenuContinueLabel.fontColor = SKColor.black
        pauseMenuToMainMenuLabel.fontColor = SKColor.black


        pauseMenuTitleLabel.position = CGPoint(x: 0 ,y: size.height / 2 - pauseMenuSprite.frame.size.height / 6 )
        pauseMenuContinueLabel.position = CGPoint(x: 0 ,y: size.height / 2 - pauseMenuSprite.frame.size.height / 6 * 4 )
        pauseMenuToMainMenuLabel.position = CGPoint(x: 0 ,y:  size.height / 2 - pauseMenuSprite.frame.size.height / 6 * 5)

        pauseMenuSprite.addChild(pauseMenuTitleLabel)
        pauseMenuSprite.addChild(pauseMenuContinueLabel)
        pauseMenuSprite.addChild(pauseMenuToMainMenuLabel)

    }
    /*
     *
     * END: NEW CODE
     *
     */
}

1 ответ

Решение

Я боролся с проблемой приостановки игры на игровой сцене некоторое время.

Как некоторые другие предложили в комментариях, создание "сцены паузы" для перехода, когда игра ставится на паузу, а затем выходит из нее, является эффективным решением. Этот подход позволяет избежать проблем, с которыми вы можете столкнуться при срабатывании таймеров на игровой сцене, когда игра приостановлена ​​или анимация пропускается при пробуждении.

Чтобы реализовать сцену паузы, я использую пользовательский подкласс UIViewController обрабатывать переходы сцены.

В моем CustomViewController:

var sceneForGame: MyGameScene? //scene to handle gameplay
var paused: PauseScene? //scene to appear when paused

...

// presentPauseScene() and unpauseGame() handle the transition from game to pause and back

  func presentPauseScene() {
    //transition the outgoing scene
    let transitionFadeLength = 0.30
    let transitionFadeColor = UIColor.white
    let pauseTransition = SKTransition.fade(with: transitionFadeColor, duration: transitionFadeLength)
    pauseTransition.pausesOutgoingScene = true

    let currentSKView = view as! SKView
    currentSKView.presentScene(paused!, transition: pauseTransition)
  }

  func unpauseGame() {
    let transitionFadeLength = 0.30
    let transitionFadeColor = UIColor.white
    let unpauseTransition = SKTransition.fade(with: transitionFadeColor, duration: transitionFadeLength)
    unpauseTransition.pausesIncomingScene = false

    let currentSKView = view as! SKView
    currentSKView.presentScene(sceneForGame!, transition: unpauseTransition)
  }

В MyGameScene класс (подкласс SKScene):

var parentViewController: CustomViewController?  // ref to the managing view controller 

...

   // invoke this func when you want to pause
  func setScenePause() {
    parentViewController?.presentPauseScene()
    self.isPaused = true
  }

...

// you may need a snippet like this in your game scene's didMove(toView: ) to wake up when you come back to the game
    else if self.isPaused {
      self.isPaused = false
    }

Это мое PauseScene реализация. Эта версия будет делать паузу, когда пользователь нажимает в любом месте сцены паузы, за исключением endGameButton, которая завершает текущую игру:

struct PauseNames {
  static let endGameButton = "ENDGAME"
  static let pausedButton = "PAUSE"
}

class PauseScene: SKScene {

  var center : CGPoint?
  var pauseButton: SKSpriteNode?
  var endGameButton: SKSpriteNode?
  var parentViewController: CustomViewController?

  override func didMove(to view: SKView) {
    setUpScene()
  }

  func setUpScene() {
    self.backgroundColor = SKColor.white
    self.center = CGPoint(x: self.size.width / 2, y: self.size.height / 2)
    self.isUserInteractionEnabled = false

    setUpSceneNodes()
    showPauseEndButtons()

  } // end setup scene

  func setUpSceneNodes() {
    let buttonScale: CGFloat = 0.5
    let smallButtonScale: CGFloat = 0.25

    let pauseOffset = //some CGPoint
    let endGameOffset = //some CGPoint
    pauseButton = SKSpriteNode(imageNamed: PauseNames.pausedButton)
    pauseButton?.name = PauseNames.pausedButton
    pauseButton?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
    pauseButton?.position = self.center! + pauseOffset
    pauseButton?.alpha = 0
    pauseButton?.setScale(buttonScale)

    endGameButton = SKSpriteNode(imageNamed: PauseNames.endGameButton)
    endGameButton?.name = PauseNames.pausedButton
    endGameButton?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
    endGameButton?.position = self.center! + endGameOffset
    endGameButton?.alpha = 0
    endGameButton?.setScale(smallButtonScale)
  }

  func showPauseEndButtons() {
    let buttonFadeInTime = 0.25
    let pauseDelay = 1.0

    self.addChild(pauseButton!)
    self.addChild(endGameButton!)

    pauseButton?.run(SKAction.fadeIn(withDuration: buttonFadeInTime))
    endGameButton?.run(SKAction.fadeIn(withDuration: buttonFadeInTime))
    self.run(SKAction.sequence([
      SKAction.wait(forDuration: pauseDelay),
      SKAction.run{ self.isUserInteractionEnabled = true }]))
  }

  func endGamePressed() {
    // add confrim logic
    parentViewController?.endGame()
  }

  func unpausePress() {
    parentViewController?.unpauseGame()
  }

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

      if endGameButton!.contains(touchLocation) {
        endGamePressed()
        return
      }
      else {
        unpausePress()
      }

    } // end for each touch
  } // end touchesBegan

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

} //end class PauseScene

(The pauseButton действительно больше баннера для информирования пользователя о состоянии паузы в этой версии)

Другие вопросы по тегам