How to Dismiss Skscene

How to dismiss SKScene?

"You can't go "Back to the View Controller" from a scene. The scene is a View, the view controller controls and displays views. Use the view controller to change views. Remember the view controller itself is not a view." -Wharbio

Best solution here is to create another View Controller. This view controller will be my menu. Then the other viewcontroller will act as a host for the skscene.

In my situation I then use my menu viewcontroller to dismiss the viewcontroller displaying in the skview.

dismiss SKScene go back to UIKit Menu

I agree with Whirwind comment's: why mixing two different frameworks and complicate your life when you can use one viewController only to do all your game?

Anyway, according to your storyboard screenshot,there are 2 viewControllers and you can go to the second viewController (and to the GameScene) only if you press a button.

There are two things to do: deallocate the current SKScene (in your case the GameScene) and present the "initial view controller" or your MenuViewController.

To do it, I use the "Hello world" Sprite-kit template with a protocol/delegate approach to extend the SKSceneDelegate class. As you can see we able to dismiss the scene (presenting nil) and call an external method on the GameViewController to present the MainViewController

To be sure that both of these operations go to be successful I use also two print just for debug:

GameViewController:

import UIKit
import SpriteKit
class GameViewController: UIViewController,TransitionDelegate {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
if let scene = SKScene(fileNamed: "GameScene") {
scene.scaleMode = .aspectFill
scene.delegate = self as TransitionDelegate
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
func returnToMainMenu(){
let appDelegate = UIApplication.shared.delegate as! AppDelegate
guard let storyboard = appDelegate.window?.rootViewController?.storyboard else { return }
if let vc = storyboard.instantiateInitialViewController() {
print("go to main menu")
self.present(vc, animated: true, completion: nil)
}
}
}

GameScene:

import SpriteKit
protocol TransitionDelegate: SKSceneDelegate {
func returnToMainMenu()
}
class GameScene: SKScene {
override func didMove(to view: SKView) {
self.run(SKAction.wait(forDuration: 2),completion:{[unowned self] in
guard let delegate = self.delegate else { return }
self.view?.presentScene(nil)
(delegate as! TransitionDelegate).returnToMainMenu()
})
}
deinit {
print("\n THE SCENE \((type(of: self))) WAS REMOVED FROM MEMORY (DEINIT) \n")
}
}

Output:

Sample Image

How to: Give SKScene access to dismiss() UIKit method

dismiss is a method on UIviewController and not on SKScene. However, your scene does have a view property that is its containing view (which is a SKView, which is a UIView, which is a UIResponder). You can use the view's next method inherited from UIResponder to walk up the responder chain until you hit the first view controller (because UIViewController is also a UIResponder):

extension UIResponder {
func firstParent<T: UIResponder>(ofType type: T.Type ) -> T? {
return next as? T ?? next.flatMap { $0.firstParent(ofType: type) }
}
}
//Use in your SKScene like so
view?.firstParent(ofType: UIViewController.self)?.dismiss(animated: true, completion: nil)

dismiss SKScene in SpriteKit and Swift

You're presenting a new GameScene because you're creating a new GameScene. If you want to keep the GameScene instance you were using and return to it, you'll need to present that instead. Either that, or you'll need a way for your GameScene class to save and restore its state, so that a new instance of it can be restored to replicate whatever was going on in the old instance.


For the first option, you could do something like this:

// in GameScene
let block = SKAction.runBlock {
let credits = CreditsScene(size: self.size)
credits.returnToScene = self // add this property to CreditsScene
credits.scaleMode = self.scaleMode
self.view?.presentScene(credits)
}
self.runAction(block)

// in CreditsScene
let block = SKAction.runBlock {
self.view?.presentScene(self.returnToScene)
}
self.runAction(block)

Here, you're passing a reference to the current GameScene to your CreditsScene, so that the CreditsScene knows what GameScene to return to once it's done. Alternatively, you could store a reference to the current GameScene in some place that's global (relative to the scenes, at least), like a view controller.


For the other option, you'll need to make sure that all the state in your GameScene class (or at least all the state that's meaningful to the player) can be saved and restored: current score, current player position, where the enemies are and what they're doing, etc... whatever your game does. SpriteKit classes already support NSCoding, so this is a good way to handle saving/restoring — just make sure that any custom subclasses of yours (like GameScene) encode whatever state they have.

Then, you can archive off your GameScene when transitioning away, and restore from that archive when you're ready to return. That'd look something like this:

// in GameScene
let block = SKAction.runBlock {
let credits = CreditsScene(size: self.size)
credits.returnToScene = self // add this property to CreditsScene
credits.scaleMode = self.scaleMode

let sceneData = NSKeyedArchiver.archivedDataWithRootObject(self)
sceneData.writeToFile(savedGamePath, atomically: true)

self.view?.presentScene(credits)
}
self.runAction(block)

// in CreditsScene
let block = SKAction.runBlock {
if let game = NSKeyedUnarchiver.unarchiveObjectWithFile(savedGamePath) {
self.view?.presentScene(game)
}
}
self.runAction(block)

(This presumes savedGamePath is a constant accessible to both classes.)


By the way, if your use of SKAction.runBlock here reflects what's actually in your game (as opposed to a simplification of your code for purposes of this SO question), it's redundant. You don't need to create a runBlock action for code you're going to run immediately. So, unless you actually need to stash that action for running later, you can simplify my first solution above down to something like this:

// in GameScene
let credits = CreditsScene(size: self.size)
credits.returnToScene = self // add this property to CreditsScene
credits.scaleMode = self.scaleMode
self.view?.presentScene(credits)

// in CreditsScene
self.view?.presentScene(self.returnToScene)


Related Topics



Leave a reply



Submit