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
Related
I am trying to create a pause menu on the game layer. I'm doing this just by adding two buttons on top when the pause button is pressed so the player can still see the stage of the game
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if atPoint(location).name == "pause" {
let button: SKSpriteNode?
let button2: SKSpriteNode?
button = SKSpriteNode(imageNamed: "button_continue")
button?.name = "button_continue"
button?.size.height = 55
button?.size.width = 215
button?.physicsBody = SKPhysicsBody(rectangleOf: button!.size)
button?.physicsBody?.allowsRotation = false
button?.position = CGPoint(x: 0, y: 160)
button?.zPosition = 100
self.addChild(button!)
button2 = SKSpriteNode(imageNamed: "button_finish")
button2?.name = "button_finish"
button2?.size.height = 55
button2?.size.width = 215
button2?.physicsBody = SKPhysicsBody(rectangleOf: button2!.size)
button2?.physicsBody?.allowsRotation = false
button2?.position = CGPoint(x: 0, y: -50)
button2?.zPosition = 100
self.addChild(button2!)
// The two buttons are created but here I am trying to stop everything until the continue button is pressed and I don't find a way
}
}
}
I have tried sleep() or timer.invalidate() but neither of them work with if statements, and I can't use while loops because then buttons don't appear until the while loop finishes:
while atPoint(location).name != "button_cotinue" {
timer1.invalidate()
}
That is what I tried and doesn't work.
Then when the continue button is pressed I will also delete the buttons but I can code that correctly. As well the finish button sends the player to the main menu scene.
what I do is put the controls (buttons) in a layer named controlsLayer that is separate from all of the game play items such as player, enemies and obstacles. Then I put all of the game items that I want to be paused in a layer named gameLayer. I make the controlsLayer have a high zPosition like 100.
then when I click my pause button I call gameLayer.isPaused = true and when I click continue I would call the opposite. gameLayer.isPaused = false. by just pausing the gameLayer, you can still run other actions like transitions or effects on your pause buttons. If you pause the whole scene you won't be able to run any actions or effects on any items.
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 have Sound button (On/Off) in main scene and its works perfectly
but I have problem , off the background music playing when I was disqualified I return to the main screen, but the image of the button changes the music is turned on and stays on the image of the music is off.
Main Scene :
var SoundOnOff = SKSpriteNode()
override func didMoveToView(view: SKView) {
backgroundColor = UIColor(red:0.09, green:0.63, blue:0.52, alpha:1.0)
//Main Scene:
SoundOnOff.texture = SKTexture(imageNamed:"Sound-on.png")
SoundOnOff.position = CGPoint(x: self.size.width/2 - 40 , y: self.size.height/2 - 500)
SoundOnOff.size = CGSizeMake(60 , 60)
SoundOnOff.runAction(SKAction.moveToY(140, duration: 0.5))
SoundOnOff.removeFromParent()
addChild(SoundOnOff)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
let location = touch!.locationInNode(self)
if(SoundOnOff.containsPoint(location)) {
// ---------------------------------------------
// Play Background Music
// ---------------------------------------------
if ((NSUserDefaults.standardUserDefaults().objectForKey("onoroff")) !== true)
{
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "onoroff")
Singleton.sharedInstance().pauseBackgroundMusic()
SoundOnOff.texture = SKTexture(imageNamed:"Sound-off.png")
}else {
NSUserDefaults.standardUserDefaults().setBool(false, forKey: "onoroff")
Singleton.sharedInstance().resumeBackgroundMusic()
SoundOnOff.texture = SKTexture(imageNamed:"Sound-on.png")
}
}
} }
}
Basically, you have to check in your Menu scene if music is off or on, and based on that to show appropriate texture. Right now, it seems that you are not making that check inside of a menu scene.
HINT: Obviously, your Singleton class has been implemented as a singleton. And if everything is done correctly, it is instantiated once and alive through the whole app's life time. So it has an info about music on/off state. What I wanted to point is that, when the app is closed, this singleton gets deallocated. So before that happen, you might store the info about music on/off into persistent storage, like NSUserDefaults, so the next time when user open up the app, he has his settings saved. Just a though... This really depends on you and what you want to offer in your app.
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.
Referring to my last question:
Sprite moves two places after being paused and then unpaused
Hi, I have a tap gesture which moves a sprite in my game forward 1 space and when I press the pause button it continues to register the tap gesture and then when I resume the gameplay It moves two spaces.
so I managed to define a bool variable that detects (using if statements) if I have paused the tap gesture
var tapIsPaused: Bool = false
func tapUp(){
if(tapIsPaused == true) {
//do nothing
} else if (tapIsPaused == false) {
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)
clearNodes()
}
}
But the problem I have now is that when I press the resume button to resume the gameplay it still moves the sprite, but this time it's only moving one space up, which is because when I press the resume button it turns the tap on which then registers the tap of the resume button to move the player up.
How can I fix this?
Here is my pause button:
else if (node == pauseButton) {
tapIsPaused = true
pauseButton.removeFromParent()
addChild(resumeButton)
addChild(restartButton)
self.runAction (SKAction.runBlock(self.pauseGame))
}
Here is my resume button:
else if (node == resumeButton) {
resumeButton.removeFromParent()
restartButton.removeFromParent()
addChild(pauseButton)
self.runAction (SKAction.runBlock(self.resumeGame))
tapIsPaused = false
}
Here is my tap gesture handler code:
let TapUpRec = UITapGestureRecognizer()
TapUpRec.addTarget(self, action: "tapUp")
self.view!.addGestureRecognizer(TapUpRec)
You can remove Gesture on Pause click using following:
self.view.removeGestureRecognizer(YOUR_GESTURE_RECOGNISER)
and add it again if resume game
Modify your resume function as:
else if (node == resumeButton) {
resumeButton.removeFromParent()
restartButton.removeFromParent()
addChild(pauseButton)
tapIsPaused = false
self.runAction (SKAction.runBlock(self.resumeGame))
}
Very simple and easiest way.No need to add or remove Gesture.
You can do it with enable or disable your gesture.
For swift 2.3
TapUpRec.enabled = false //pause click
TapUpRec.enabled = true //resume click
For swift 3.0
TapUpRec.isEnabled = false //pause click
TapUpRec.isEnabled = true //resume click