I have a button to pause the game on my code. What I want is that pausing the game with that button makes a message that says "Paused" to appear. However, since the scene is paused, the message does not appear.
What I have right now is a SKLabelNode with the alpha on 0.0 at the beginning and when the user pauses the game, it changes to 1.0 with fadeInWithDuration(). Then when the user presses the button again, it changes back to 0.0 with fadeOutWithDuration(). The problem is that the SKAction with fadeInWithDuration() does not run when the scene is paused.
How could I achieve this?
The best way, one Apple also uses in "DemoBots", is to create a world node that you pause instead of the scene.
Create a worldNode property
class GameScene: SKScene {
let worldNode = SKNode()
}
add it to the scene in didMoveToView
addChild(worldNode)
and than add everything you need paused to the worldNode. This includes actions that are normally run by the scene (eg. timers, enemy spawning etc)
worldNode.addChild(someNode)
worldNode.run(someSKAction)
Than in your pause func you say
worldNode.isPaused = true
physicsWorld.speed = 0
and in resume
worldNode.isPaused = false
physicsWorld.speed = 1
You can also add an extra check in your Update function if you have stuff there that you want to ignore when paused.
override func update(_ currentTime: CFTimeInterval) {
guard !worldNode.isPaused else { return }
// your code
}
This way it's much easier to add your paused label or other UI when your game is paused because you haven't actually paused the scene. You can also run any action you want, unless that action is added to the worldNode or to a child of worldNode.
Hope this helps
Instead of pausing the scene, you could layer some nodes your scene like this
SKScene
|--SKNode 1
| |-- ... <--place all scene contents here
|--SKNode 2
| |-- ... <--place all overlay contents here
Then when you want to pause the game, you pause only SKNode 1.
This allows node SKNode 2 to continue to run, so you can do things like have animations going, and have a button that unpauses the scene for you, without having the need to add some non Sprite Kit object into the mix.
A quick workaround would be to pause your game after the SKLabelNode appears on screen:
let action = SKAction.fadeOutWithDuration(duration)
runAction(action) {
// Pause your game
}
Another option would be to mix UIKit and SpriteKit and inform the ViewController back that it needs show this label.
class ViewController: UIViewController {
var gameScene: GameScene!
override func viewDidLoad() {
super.viewDidLoad()
gameScene = GameScene(...)
gameScene.sceneDelegate = self
}
}
extension ViewController: GameSceneDelegate {
func gameWasPaused() {
// Show your Label on top of your GameScene
}
}
protocol GameSceneDelegate: class {
func gameWasPaused()
}
class GameScene: SKScene {
weak var sceneDelegate: GameSceneDelegate?
func pauseGame() {
// Pause
// ...
sceneDelegate?.gameWasPaused()
}
}
So you want to pause the game AFTER the action execution has completed.
class GameScene: SKScene {
let pauseLabel = SKLabelNode(text: "Paused")
override func didMoveToView(view: SKView) {
pauseLabel.alpha = 0
pauseLabel.position = CGPoint(x: CGRectGetMaxY(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(pauseLabel)
}
func pause(on: Bool) {
switch on {
case true: pauseLabel.runAction(SKAction.fadeInWithDuration(1)) {
self.paused = true
}
case false:
self.paused = false
pauseLabel.runAction(SKAction.fadeOutWithDuration(1))
}
}
}
I would add the label with
self.addChild(nameOfLabel)
and then pause the game with
self.scene?.paused = true
This should all go in the if pauseButton is touched portion of your code.
Related
so I have a menu screen in my app. Swift, SpriteKit, iOS 7-10,11,12,13..
All I want is a user to tap the button on my screen, and have it move them to another screen. This works in other references in my code. However All I did was change the first loading scene in the GameViewController to my own swift file, and this is how the code looks:
import Foundation
import SpriteKit
import GameplayKit
import UIKit
class FrontMenu : SKScene {
var play = SKSpriteNode(imageNamed: "pbut")
var credit = SKSpriteNode()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let node : SKNode = self.atPoint(location)
if node.name == "play" {
print("play tapped.")
// this is where I would have placed the transition, but this did not work.
}
else if node.name == "cred" {
NSLog("credits tapped")
}
if location.x <= -90 && location.y >= -160 {
NSLog("Area hit for play")
// This area is still not detected within the app, and there is no button function.
let gameScene = GameScene(fileNamed: "GameScene")
gameScene?.scaleMode = .aspectFill
self.view?.presentScene(gameScene!, transition: SKTransition.fade(withDuration: 0.86))
Basically that does that. You can see where I implemented the transition. Am I missing an import? Am I missing something to change to the overall Plist file? or Maybe something else within GameViewController.swift, I changed the first loading swift file, so that it does load this code first in the app. However it does not detect the touches.
Thank you for your help!
It looks like you are missing code that would set the name for your node. Make sure that in your didMove function that you are setting the name of the node and adding it to the scene.
//inside scene
override func didMove(to view: SKView) {
play.name = "play"
self.addChild(play)
}
The other thing you have to make sure of is that you created a SpriteKit scene file in your project and set the custom class to be FrontMenu
As for switching the file in your GameViewController, you should just be using the name of the file and not the file extension for fileNamed::
//inside GameViewController
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'MneuScene.sks'
if let scene = SKScene(fileNamed: "MenuScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .resizeFill
// Present the scene
view.presentScene(scene)
}
}
}
Since I'm working in SpriteKit, my buttons are SKSpriteNodes...however I find myself in a situation where I need to set focus in my viewController via overriding preferredFocusedView. Is there a way to downcast an SKSpriteNode to a UIView? If so I haven't been able to figure out yet...any alternative?
let playButton = SKSpriteNode(imageNamed: "PlayButton")
playButton.position = CGPoint(x: scene.size.width * 0.25, y: scene.size.height * 0.25)
playButton.zPosition = Layer.UI.rawValue
scene.worldNode.addChild(playButton)
override var preferredFocusedView: UIView? {
get {
return //playButton how?
}
}
Focus navigation is only now supported with tvOS 10 and SpriteKit, prior to that you had to do it manually using your own focus system. For that reason preferred focus view is deprecated because it only supports UIViews. You should now use preferred focus environments instead.
First thing you do is in your GameViewController set the preferred focus environment to the currently presented SKScene. This essentially means that your SKScenes will handle the preferred focus instead of GameViewController. In a SpriteKit game the SKScenes should handle the UI such as buttons using SpriteKit APIs such as SKLabelNodes, SKSpriteNodes etc. Therefore you need to pass the preferred focus to the SKScene.
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// default code to present your 1st SKScene.
}
}
#if os(tvOS)
extension GameViewController {
/// Tell GameViewController that the currently presented SKScene should always be the preferred focus environment
override var preferredFocusEnvironments: [UIFocusEnvironment] {
if let scene = (view as? SKView)?.scene {
return [scene]
}
return []
}
}
#endif
Your playButton should be a subclass of SKSpriteNode that you will use for all your buttons in your game. Use enums and give them different names/ identifiers to distinguish between them when they are pressed (checkout Apples sample game DemoBots).
class Button: SKSpriteNode {
var isFocusable = true // easy way to later turn off focus for your buttons e.g. when overlaying menus etc.
/// Can become focused
override var canBecomeFocused: Bool {
return isFocusable
}
/// Did update focus
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
if context.previouslyFocusedItem === self {
// SKAction to reset focus animation for unfocused button
}
if context.nextFocusedItem === self {
// SKAction to run focus animation for focused button
}
}
}
Than in your SKScenes you can set the focus environment to your playButton or other UI.
e.g Start Scene
class StartScene: SKScene {
....
}
#if os(tvOS)
extension StartScene {
override var preferredFocusEnvironments: [UIFocusEnvironment] {
return [playButton]
}
}
#endif
e.g GameScene (e.g transfer focus to game menu when needed)
class GameScene: SKScene {
....
}
#if os(tvOS)
extension GameScene {
override var preferredFocusEnvironments: [UIFocusEnvironment] {
if isGameMenuShowing { // add some check like this
return [gameMenuNode]
}
return []
}
}
#endif
You will also have to tell your GameViewController to update its focus environment when you transition between SKScenes (e.g StartScene -> GameScene). This is especially important if you use SKTransitions, it took me a while to figure this out. If you use SKTransitions than the old and new scene are active during the transition, therefore the GameViewController will use the old scenes preferred focus environments instead of the new one which means the new scene will not focus correctly.
I do it like this every time I transition between scenes. You will have to use a slight delay or it will not work correctly.
...
view?.presentScene(newScene, transition: ...)
#if os(tvOS)
newScene.run(SKAction.wait(forDuration: 0.1)) { // wont work without delay
newScene.view?.window?.rootViewController?.setNeedsFocusUpdate()
newScene.view?.window?.rootViewController?.updateFocusIfNeeded()
}
#endif
You should read this article
https://medium.com/folded-plane/tvos-10-getting-started-with-spritekit-and-focus-engine-53d8ef3b34f3#.x5zty39pc
and watch the 2016 apple keynote called "Whats New in SpriteKit" where they talk about it half way through.
Hope this helps
When returning to my App after closing it the applicationDidBecomeActive(application: UIApplication) automatically fires in AppDelegate.swift.
This fires a method that handles the paused status of the app:
GameViewController().pause(true)
The method looks like this:
func pause(paused: Bool) {
if paused == true {
scene?.paused = true
print("paused")
} else if paused == false {
scene?.paused = false
print("unparsed")
}
}
When first launching the app the Game is automatically paused which is exactly what should happen. When returning to the app it unpauses though. Still, the Console prints "paused".
I have also tried using scene?.view?.paused instead of scene?.paused. This does work, but leads to lag in the animations running on the scene.
Any help would be highly appreciated
EDIT
I managed to solve the problem by calling the pause() method in the update(currentTime: NSTimeInterval) function but I don't like this solution as it means the method is called once per frame. Other solutions would be highly appreciated
This code makes no sense
GameViewController().pause(true)
because you are creating a new instance of GameViewController rather than accessing the current one.
Rather than pausing the whole scene you should just pause the nodes that you would liked paused. Usually you create some kind of worldNode in your game scene (Apple also does this in DemoBots)
class GameScene: SKScene {
let worldNode = SKNode()
// state machine, simple bool example in this case
var isPaused = false
....
}
than add it to the scene in DidMoveToView
override func didMoveToView(view: SKView) {
addChild(worldNode)
}
Than all nodes that you need paused you add to the worldNode
worldNode.addChild(YOURNODE1)
worldNode.addChild(YOURNODE2)
Than your pause function should look like this
func pause() {
worldNode.paused = true
physicsWorld.speed = 0
isPaused = true
}
and resume like this
func resume() {
worldNode.paused = false
physicsWorld.speed = 1
isPaused = false
}
Lastly to make sure the game is always paused when in paused add this to your update method. This ensures that you game does not resume by accident e.g due to app delegate, iOS alerts etc.
override func update(currentTime: CFTimeInterval) {
if isPaused {
worldNode.paused = true
physicsWord.speed = 0
return
}
// Your update code
...
}
To call these from your AppDelegate you should use delegation or NSNotificationCenter as has been mentioned in one of the comments.
In gameScene create the NSNotifcationObserver in didMoveToView
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(pause), name: "Pause", object: nil) // in your app put the name string into a global constant property to avoid typos when posting
and in appDelegate post it at the correct spot
NSNotificationCenter.defaultCenter().postNotificationName("Pause", object: nil)
The main benefit with the worldNode approach is that you can easily add pause menu sprites etc while the actual game is paused. You also have more control over your game, e.g having the background still be animated while game is paused.
Hope this helps.
I am having trouble figuring out the solution to this and am starting to get very frustrated with it.
I have a pause button and an unpause button in my game scene to allow the player to pause the game, which is the following code
else if (node == pauseButton) {
pauseButton.removeFromParent()
addChild(unpauseButton)
addChild(restartButton)
self.runAction (SKAction.runBlock(self.pauseGame))
}
func pauseGame(){
pauseButton.hidden = true
unpauseButton.hidden = false
scene!.view!.paused = true // to pause the game
}
Problem
The problem is that when I pause the game then unpause the game my player sprite seems to move two spaces forward automatically
I also have a tap and swipe gesture that allows me to move the player up left right and down when I tap anywhere on the screen.
func tapUp(){
let amountToMove:CGFloat = levelUnitHeight
let move:SKAction = SKAction.moveByX(0, y: amountToMove, duration: 0.1)
menubutton.hidden = true
settingsButton.hidden = true
highscoreLabel.hidden = true
pauseButton.hidden = false
thePlayer.runAction(move)
}
func swipedRight(){
let amountToMove:CGFloat = levelUnitHeight
let move:SKAction = SKAction.moveByX(amountToMove, y: 0, duration: 0.1)
thePlayer.runAction(move) // links the action with the players
}
As the member above me said the player is not really moving 2 spaces.
Also you should maybe change your strategy when pausing your game, because pausing the scene.view makes it very hard to add SpriteKit elements afterwards.
I think a better way is to create a worldNode in your GameScene and add all the sprites that need to be paused to that worldNode. It basically gives you more flexibility pausing a node rather than the whole scene.
First create a world node property
let worldNode = SKNode()
and add it to the scene in ViewDidLoad
addChild(worldNode)
Than add all the sprites you need paused to the worldNode
worldNode.addChild(sprite1)
worldNode.addChild(sprite2)
...
Create a global enum for your game states
enum GameState {
case Playing
case Paused
case GameOver
static var current = GameState.Playing
}
Than make a pause and resume func in your game scene
func pause() {
GameState.current = .Paused
// show pause menu etc
}
func resume() {
GameState.current = .Playing
self.physicsWorld.speed = 1
worldNode.paused = false
}
And finally add the actual pause code to your updateMethod. This way it will not resume the game even if spriteKit itself tries to resume (e.g. reopened app, dismissed alert etc)
override func update(currentTime: CFTimeInterval) {
if GameState.current == .Paused {
self.physicsWorld.speed = 0
worldNode.paused = true
}
}
In regards to your tapGesture recogniser, in the method that gets called after a tap you can add this before the rest of the code
guard GameState.current != .Paused else { return }
....
Hope this helps
I am having trouble figuring out how to change view controllers when your player collided with an object.
I want to like a menu to pop-up displaying a menu button and a replay button, also so extra buttons that are not important at this moment of time. I am not sure how some of those end of game menus are made, I am thinking switching view controllers, if you know exactly how they are made please tell me.
This is the code I have at the moment, and the only thing it does is display a label that the game is over and when that label is tapped the game will restart:
import Foundation
import AVFoundation
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var movingGround: PPMovingGround!
var square1: PPSquare1!
var wallGen: PPWallGen!
var diamondGen: PPDiamondGen!
var isStarted = false
var isGameOver = false
var isDiamondContact = false
var playerNode: SKNode!
override func didMoveToView(view: SKView) {
//code that is not important was deleted
func collisionWithDiamond() {
isDiamondContact = true
}
func restart() {
let newScence = GameScene(size: view!.bounds.size)
newScence.scaleMode = .AspectFill
view!.presentScene(newScence)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if isGameOver {
restart()
} else {
square1.flip()
}
}
override func update(currentTime: CFTimeInterval) {
}
// MARK: - SKPhysicsContactDelegate
func didBeginContact(contact: SKPhysicsContact) {
if !isGameOver {
gameOver()
} else {
!isDiamondContact
collisionWithDiamond()
}
}
Note: I have deleted code that is unrelated or not necessary.
Updates:
Link to a game play of a game: https://www.youtube.com/watch?v=WUibTETfEQY
SKIP TO 2:32 TO SEE THE GAME OVER SCREEN
Link to image of game over screen: Image
(I was unable to post an image here because I don't have the required 10 rep points yet.)
// Edited Answer
This will be the easiest. Create a new GameOverScene.swift that is a SKScene. Then customize that scene however you want with background image, SKLabelNodes for buttons. Checkout creating buttons in skview to point to different scenes
When the game ends in GameScene,
let gameOverScene: GameOverScene = GameOverScene(size: self.size)
self.view!.presentScene(gameOverScene, transition: SKTransition.doorsOpenHorizontalWithDuration(1.0))
Here is a project that has this implemented, http://www.raywenderlich.com/76741/make-game-like-space-invaders-sprite-kit-and-swift-tutorial-part-2
// First Answer -----------------------------------------------
If you want to switch viewControllers, you will have to present the new viewController like this or with segue,
self.view?.window?.rootViewController?.presentViewController(newView, animated: true, completion: nil)
self.view?.window?.rootViewController?.performSegueWithIdentifier("id", sender: AnyObject)
Otherwise create a SKView and add buttons, then add it to the scene when game is over or add it before, hide it, then show it. Once user picks a choice, remove it or hide it with,
SKView.hidden = false
SKView.hidden = true
Add SKView with,
self.view?.addSubview(SKView)
Simple SKView overlay,
let view1 = SKView(frame: CGRectMake(0, 0, 200, 200))
view1.center = self.view!.center
self.view?.addSubview(view1)
If the game is over and you want to present a selective menu, you could present a UIAlertView that presents the user with whatever options that you want. From there they could restart the game, or they could choose to go to some other view like one that manages their player stats or something ( I don't know what exactly your game is).