Why won't the scene pause? - swift

I am trying to pause my scene when my SCNPyramid reaches a certain coordinate to setup my tutorial for game. However, the scene never pauses and I am not sure why.
if (leftSpike5.position.x == 0.4) {
self.isPaused = true
}

Related

How to update the pointOfView in ARKit

I'm trying to build my first ARKit app. The purpose of the app is to shoot little blocks in the direction that the camera is facing. Right now, here is the code I have.
sceneView.scene.physicsWorld.gravity = SCNVector3(x: 0, y: 0, z: -9.8)
#IBAction func tapScreen() {
if let camera = self.sceneView.pointOfView {
let sphere = NodeGenerator.generateCubeInFrontOf(node: camera, physics: true)
self.sceneView.scene.rootNode.addChildNode(sphere)
var isSphereAdded = true
print("Added box to scene")
}
}
The gravity works fine, whenever I tap on the screen the block shoots out each and every time I tap. However, they all shoot to the same point, no matter which direction the camera is facing. I'm trying to understand how pointOfView works, would I need to re-render the whole scene? Something else that I can't quite think of? Thanks for any help!
Change this line from
self.sceneView.scene.rootNode.addChildNode(sphere)
to
self.sceneView.pointOfView?.addChildNode(sphere)

Swift 3 (SpriteKit): Reseting the GameScene after the game ends

I am wanting to 'reset' and 'restart' the GameScene so it is as if the GameScene was first called. I have looked at different methods for doing this, but each time I get a warning that I'm trying to add a node to a parent which already has a parent. However, in my code I delete all my existing nodes so I'm really confused as to how to reset the GameScene. This is how I do it now (this code is called when I want to restart the GameScene from scratch and it is called within the GameScene class):
let scene = GameScene(size: self.size)
scene.scaleMode = .aspectFill
let animation = SKTransition.fade(withDuration: 1.0)
self.view?.presentScene(scene, transition: animation)
self.removeAllChildren()
self.removeAllActions()
self.scene?.removeFromParent()
1.Edited: I realised that why I was getting this warning: "I'm trying to add a node to a parent which already has a parent" was because I had all the variables for the scene outside of the class and as global variables. However, now when the game restarts, the game is in the bottom left corner. Why is this the case and how do I fix this? - FIXED
2.Edited: Everything works fine now, but now my concern is that deinit{} isn't called even though every node is deleted and the fps doesn't drop over time. Here is what I have in my GameViewController for setting the scene and in my GameScene (every instance relating to the scenes so basically all that is relevant):
import UIKit
import SpriteKit
import GameplayKit
var screenSize = CGSize()
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
screenSize = scene.size
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
Then my GameScene is basically:
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
//Declare and initialise variables and enumerations here
deinit{print("GameScene deinited")}
override func didMove(to view: SKView) {
//Setup scene and nodes
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//Do other things depending on when and where you touch
//When I want to reset the GameScene
let newScene = GameScene(size: self.size)
newScene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
newScene.scaleMode = self.scaleMode
let animation = SKTransition.fade(withDuration: 1.0)
self.view?.presentScene(newScene, transition: animation)
}
Any answers would be greatly appreciated :)
How to reset the scene?
You just have to present a new, same scene again whenever you want. So, you are doing it fine.
Possible leaking problems?
Also, if you don't have leaks in your game, means no strong reference cycles, you don't even need self.removeAllChildren() and self.removeAllActions()... Of course if you explicitly want to stop actions before transition animation starts, the using this method make sense. The point is, when scene deallocates, all objects that depends on it should / will deallocate as well.
Still, if you don't know from the beginning what you are doing and how to prevent from leaks, eg. you are using strong self in block which is a part of an action sequence, which repeats forever, then you certainly have a leak, and self.removeAllActions() might help (in many cases, but it is not an ultimate solution). I would recommend to read about capture lists and ARC in general because it can be useful to know how all that work just because of these situations.
Scene is a root node
Calling removeFromParent() on a scene itself has no effect. Scene is a root node, so it can't be removed in your current context. If you check scene's parent property you will notice that it is nil. Of course it is possible to add a scene to another scene, but in that case, the scene which is added as a child, will act as an ordinary node.
And finally, how to present the new scene ? Easy, like this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let newScene = GameScene(size: self.size)
newScene.scaleMode = self.scaleMode
let animation = SKTransition.fade(withDuration: 1.0)
self.view?.presentScene(newScene, transition: animation)
}
If something doesn't work for you, it is likely that you have leaks (means your scene isn't deallocated). To check this, somewhere in your GameScene override deinit method, like this:
deinit{print("GameScene deinited")}
To explain you this a bit further... What should happen is that you should present a new scene, a transition should occur, an old scene should be deallocated, and you should see a new scene with an initial state.
Also overriding deinit will just tell you if the scene is deallocated properly or not. But it will not tell you why. It is up to you to find what in your code retaining the scene.
There are 2 main ways that I can think of that do this. The main way that I go this is that if the game is over, (due to the character health falling to zero, or they collide with an object that causes the round to be over, or time is up or whatever), when that happens I like to transition to a new scene that is a summary screen of their score, how far they made it etc.
I do this by having a bool variable in the main GamePlay scene like this.
var gameOver: Bool = false
Then in the code that fires off to cause the game to end set that variable = true.
In the update function check to see if gameOver == true and transition to the GameOverScene.
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
// Initialize _lastUpdateTime if it has not already been
if (self.lastUpdateTime == 0) {
self.lastUpdateTime = currentTime
}
// Calculate time since last update
let dt = currentTime - self.lastUpdateTime
// Update entities
for entity in self.entities {
entity.update(deltaTime: dt)
}
self.lastUpdateTime = currentTime
if gameOver == true {
print("Game Over!")
let nextScene = GameOverScene(size: self.scene!.size)
nextScene.scaleMode = self.scaleMode
nextScene.backgroundColor = UIColor.black
self.view?.presentScene(nextScene, transition: SKTransition.fade(with: UIColor.black, duration: 1.5))
}
}
The update function will check at each frame render to see if the game is over and if it is found to be over it will perform any actions that you need it to and then present the next scene.
Then on the GameOverScene I put a button saying "Retry" and when they click that it fires off the GamePlayScene again, running the view DidLoad function and setting up the GamePlayScene from scratch the way that it should.
Here is an example of how I handle that. There are a few different ways to call a scene transtion. You can give this one a try if it isn't working quite right.
if node.name == "retryButton" {
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view! as SKView
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .aspectFill
scene.size = skView.bounds.size
skView.presentScene(scene, transition: SKTransition.fade(withDuration: 2.0))
}
}
That is my preferred method of handling the game over transitions.
The other method would be to create a function that resets all of the variables that have changed during the course of playing. Then you could use the same code from the Update function above, but instead of transitioning you could create a label on the scene. If the user clicks the label it would fire off of that function to reset all of the variables that have changed, reset the players locations, stops all actions, resets players health etc. Depending on how many things you have changing during the course of gameplay it'll probably be more practical, (as I've found), to transition to a new scene to give a summary and then reload the GamePlayScene through a button. Then everything will load up just the same as it does the first time that the user entered that main GamePlayScene.

How can I catch moment SKScene became paused or audioEngine became stopped?

How can I catch moment SKScene became paused or audioEngine became stopped?
I have two SKScenes: GameScene and EndScene, and I play sounds during the game using audioEngine property of GameScene (this property contains AVAudioEngine object). When the game is over, the scene changes from GameScene to EndScene:
self.view?.presentScene(EndScene())
After this, when user touches «Restart», the game starts again, scene changes back from EndScene to GameScene:
self.view?.presentScene(GameScene(), transition: SKTransition.crossFadeWithDuration(1.0))
When scene changes from EndScene to GameScene, GameScene became paused for one second (because of parameter value SKTransition.crossFadeWithDuration(1.0)). Because of GameScene became paused, audioEngine also became stopped. After this, GameScene automatically starts, but audioEngine don’t starts and stays stopped. Then, the game crashes at the first attempt to play the sound, and it’s clear: because audioEngine is stopped.
To avoid this I need to start audioEngine after it was stopped, but there is a problem to catch the moment it happens: I’ve tried to catch it in didMoveToView() of GameScene, but there GameScene is not paused, it became paused later, after didMoveToView() was already finished.
And this is my question:
How can I catch moment SKScene became paused or audioEngine became stopped? Are there some suitable notifications or event handlers for this case, maybe?
Finally, i've dropped attempts to find some suitable notifications or events and decided to check audioEngine status every time before sound playback, in function plays sound. I rate this way as reasonably efficient:
func playSound(soundType: Sounds) {
if !audioEngine.running {
do {
try audioEngine.start()
} catch {
}
}
var soundNode = SKAudioNode()
var waitingDuration = Double()
switch soundType {
case .enemyDestroyed:
soundNode = SKAudioNode(fileNamed: "Blip001.wav")
waitingDuration = 3
case .playerDestroyed:
soundNode = SKAudioNode(fileNamed: "Noise002.wav")
waitingDuration = 5
}
soundNode.autoplayLooped = false
self.addChild(soundNode)
soundNode.runAction(SKAction.changeVolumeTo(0.5, duration: 0))
soundNode.runAction(SKAction.changeReverbTo(0.5, duration: 0))
soundNode.runAction(SKAction.sequence([SKAction.play(), SKAction.waitForDuration(waitingDuration), SKAction.removeFromParent()]))
}
Use the update function to check for this every time a frame is called (60 times per second). Inside it, put a conditional statement (if, while, etc.) that checks for the condition you want. For example, use this to check for if the scene is paused:
override func update(currentTime: CFTimeInterval) {
if isPaused == true {
// Code to run when scene is paused
}
}
Note that this is in Swift 3 and in lower versions isPaused is just paused
Another option could be to use loops like while and do-while to continuously check for the conditions

Swift, spritekit: How to restart GameScene after game over? Stop lagging?

Alright, so I have a sprite kit game in Swift here and I'm having trouble restarting my GameScene after it's game over.
Right now, when the user loses all their lives, a variable gameIsOver is set to true, which pauses specific nodes within the scene as well as sets off a timer. After this timer ends, I move to my Game Over scene. From the Game Over scene, the user can either go back to home or restart the game.
Here is how I transition to my game over scene:
countdown(circle, steps: 120, duration: 5) {
//Performed when timer ends
self.gameSoundTrack.stop()
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = mainStoryboard.instantiateViewControllerWithIdentifier("GOViewController")
self.viewController!.presentViewController(vc, animated: true, completion: nil)
//Resetting GameScene
self.removeAllChildren()
self.removeAllActions()
self.scene?.removeFromParent()
self.paused = true
gameIsOver = false
}
I pause my scene here is because, if I don't, when GameScene is reloaded gameIsOver is still set to true and my app crashes. I don't know why this occurs considering I set gameIsOver to false here.
After moving from GameScene to my game over scene and back to GameScene, or from GameScene to my home view controller and back to GameScene a couple times, my fps count has decreased so much that the whole game is lagging to the point where gameplay is impossible.
This leads me to believe that I am not removing/disposing of GameScene properly each time I present my game over scene.
I believe I'm having the same problem as here: In Swift on "game over" move from scene to another UIView and dispose the scene? , but I'm new to this and I can't figure out how they solved their problem.
How I can I completely reset/delete GameScene each time I present my Game Over scene in order to stop the lagging?
Your gameIsOver Bool will not keep a value while switching between scenes unless your make it a struct. So instead of
var gameIsOver = false
it should be
struct game {
static var IsOver : Bool = false
}
so when you change the value as things happen you call
game.IsOver = true
//if your calling it in a different View controller or scene than where you created it just put the name before it like so
GameViewController.game.IsOver = true
As for the transition back to your GameScene create a function
func goToGameScene(){
let gameScene:GameScene = GameScene(size: self.view!.bounds.size) // create your new scene
let transition = SKTransition.fadeWithDuration(1.0) // create type of transition (you can check in documentation for more transtions)
gameScene.scaleMode = SKSceneScaleMode.Fill
self.view!.presentScene(gameScene, transition: transition)
}
then whenever you want to reset to GameScene just call
goToGameScene()

show menu when paused a game ins spritekit

I created a game like as tower defence. I can successfully "pause" all game. And then i can resume the game with these code:
let startLocation = CGPoint(x: location.x, y: location.y);
let myNodeName = self.nodeAtPoint(location)
if(myNodeName.Name == "Start"){
self.view!.paused = false
}
if(myNodeName.Name == "pause"){
self.view!.paused = true
self.showPausedMenu()
}
In showPausedMenu function. I design a menu with SKLabelNodes and SKSpriteNodes. However when i paused the game all of these "node creation" things also paused. So nothing happen. How i can show my menu while game is paused?
thank you.
Problem is that you're pausing the view itself. Pausing the view stops all of the logic.
If you need to do a pause menu, you need to either add a subview that will appear on top of your view to manage all of the interactions with the user, or you need to not pause the view, but put the view ELEMENTS on pause and create things on top of it.
The best method would be to make use of a SKNode group which consist of all the objects that you want to be paused.
In this way, you retain full control of the elements that you want to pause or unpause
I had solve my problem. While paused the game, all screen freezed however gamescene still detect all "touch" and if it has node name then we can do some operations. At this point, the proble is only showing my menu. So here is my solution.before I paused game, i added my menu on the screen. then pause the game.
In live version is
let startLocation = CGPoint(x: location.x, y: location.y);
let myNodeName = self.nodeAtPoint(location)
if(myNodeName.Name == "Start"){
self.view!.paused = false
self.removePausedMenu()
}
if(myNodeName.Name == "pause"){
self.showPausedMenu()
}
showPausedMenu is:
func showPausedMenu(){
.....
.....
/* Create menu and buttons and labels with NODES. and add to parent as child nodes. */
self.view!.paused = true
}