Swift: Deallocate GameScene after transition to new scene? - swift

So I've read several questions on this but most are in Objective-C and I haven't found any that address/answer this directly.
I am new to programming here so please explain any suggestions really thoroughly.
I need to understand how to deallocate my GameScene after game over is reached. The reason I need to do this is because after game over, I transition to a new SKScene (the game over scene) and when I transition back to my GameScene, either GameScene is not reset entirely or I'm generating a new GameScene each time.
I say this because My frames per second count goes down every time I get game over and transition back to the GameScene, so that if I do it enough times I can't even play the game anymore. This is a problem.
I don't understand why this happens because when I transition to my game over scene, I remove everything from my GameScene:
self.viewController!.performSegueWithIdentifier("goToGameOver", sender: nil)
self.removeAllChildren()
self.removeAllActions()
self.scene?.removeFromParent()
I also read here: After Closing SKScene, Memory Remains High that removing the SKView that contains an SKScene deallocates the SKScene, so when both my GameScene and game over scenes are left, I remove BOTH views from their superviews like this:
override func viewDidDisappear(animated: Bool) {
print("its gone")
self.view.removeFromSuperview()
}
But nothing has any affect on the decreasing frames per second count and the game keeps lagging. I don't understand what I'm not doing and can't tell whether the game over scene is the problem or I'm just not deallocating GameScene.
How do I deallocate the GameScene? What else should I be doing?
EDIT:
My node count and draws count remain about the same every time I transition back to GameScene, so that doesn't seem to be a problem. I tried testing whether deinit was called when transitioning between scenes (I didn't have a deinit before) using this within the GameScene class:
} //<**End of GAMESCENE**
deinit {
print("Deinit was called")
}
And nothing is printed so deinit is not being called.
EDIT 2:
#alex_p Here is what I have in my GameViewController-
class GameViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
var scene1: GameScene?
var scene2: GameScene?
var skView: SKView?
// Action to present next Scene
#IBAction func nextSceneAction(sender: AnyObject) {
print("Next scene")
// Create new GameScene object
scene2 = GameScene(fileNamed:"GameScene")
// Present scene2 object that replace the scene1 object
skView!.presentScene(scene2) //ERROR ON THIS LINE
scene1 = nil
}
And I call this method from my GameScene like this:
self.viewController.nextSceneAction(self)
I copied yours exactly but I am receiving the error unexpectedly found nil while unwrapping an Optional value on the line commented on above. Deinit in not being called.

In my project, I used the following mechanism and it worked well. All my scenes SKScene objects were optional variables. When i need the new scene to show, i create it and present at SKView. When i need to display the new scene, I set the previous object scene object to nil, this immediately reducing the reference count by 1, and becouse of at this moment no one object is not use my scene, the reference count becomes zero and scene was deleted.
The SKScene object is a ordinary class object and ARC works with them like with all reference type objects. You only need to monitor the number of references to the scene. All are finished with the various resources I was start in deinit of SKScene object
The simple example:
At UIViewController we have optional objects of GameScene:
class GameViewController: UIViewController {
var scene1: GameScene?
var scene2: GameScene?
var skView: SKView?
// Action to present next Scene
#IBAction func nextSceneAction(sender: AnyObject) {
print("Next scene")
// Create new GameScene object
scene2 = GameScene(fileNamed:"GameScene")
// Present scene2 object that replace the scene1 object
skView!.presentScene(scene2)
scene = nil
}
override func viewDidLoad() {
super.viewDidLoad()
// Create GameScene object
scene = GameScene(fileNamed:"GameScene")
skView = (self.view as! SKView)
skView!.showsFPS = true
skView!.showsNodeCount = true
skView!.ignoresSiblingOrder = true
scene!.scaleMode = .AspectFill
// Present current scene
skView!.presentScene(scene)
}
}
At GameScene in deinit print some text to show that it object will be deleted:
class GameScene: SKScene {
...
deinit {
print("Deinit scene")
}
}
Debug output after push the nextSceneAction button:
Next scene
Deinit scene

Related

how to implement a mini game using spritekit within an already developed project

the problem is how to represent the spritekit scene inside the view controller
public class GameScene: SKScene {
I was thinking of doing it with a public class, but I don't know how to get it started, I just started to progam
This is the initialisation of an SKScene inside a UIViewController.
import SpriteKit
class SomeViewController: UIViewController {
func viewDidLoad() {
super.viewDidLoad()
let sceneView = SKView(frame: view.frame)
view.addSubview(sceneView)
let scene = SKScene()
sceneView.presentScene(scene)
}
}
To open this screen you simply use the modal/navigationController presentation from the parent screen:
navigationController.push(SomeViewController(), animated: true)
or
present(SomeViewController(), animated: true, completion: nil)

Adding SKScene to UIViewController gives me errors

What I want
An infinite background that randomly adds objects (in my cases little stars) to the middle of the screen. Then those objects needs to move out of the screen with a certain speed. This way I want to give the user the feeling they are moving "into space", seeing stars they are passing.
What I have
A single view application with a UIViewController class.
Problem
I think the right way is to add a SKScene which will do the task I want. However, adding the SKScene class next to UIViewController gives me an error: "multiple inheritance from classes 'UIViewController' and 'SKScene'". I need to add the SKScene to an existence UIViewController, in order to call some important functions. When searching for this on Stackoverflow I came across this: SKScene in UIViewController But I can not call certain functions like "override func update(current time: CFTimeInterval){}", because the answer doesn't show how to implement the SKScene class. This answer deletes the whole UIViewController class: Adding an SKScene to a UIViewController?. The other answers are not helping me.
Question
What is the correct way of adding a SKScene class to an existence UIViewController class, so I can accomplish what I want?
Edit: Came across this on 9gag and its exactly what I want: http://9gag.com/gag/aWmZAPZ Those stars are coming from left top, the respawning should be in the middle of the screen at my app.
Certainly, it's possible. Instead of directly add SKScene to UIViewController, you need to warp SKScene by SKView first, and then add SKView to UIViewController
import UIKit
import SpriteKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Configure the view.
// Core:- convert the base view/UIView of UIViewController to SKView
let rootView = self.view as! SKView
// skView.showsFPS = true
// skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
rootView.ignoresSiblingOrder = true
rootView.frameInterval = 1
//init your SKScene
let rootMenuScene = YourScene(size: rootView.bounds.size)
rootMenuScene.scaleMode = .resizeFill
//warp in SKView
rootView.presentScene(rootMenuScene)
}
...
}

Adding Game Center Leaderboard

I am new to Swift and am having trouble adding a Game Center leaderboard into my Sprite Kit game. I created a button for the leaderboard but it is in an SKScene and it seems that the code for Game Center needs to be inside a View Controller? I have added the code into my single View Controller, but now I'm not sure where to go. Again, I am very new to this and completely lost -- any help would be greatly appreciated.
you could create a cocoa touch class with the UIViewController Subclass. In the viewdidLoad you could use something like this:
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "Level1") {
// Configure the scene
[...]
// Present the scene
view.presentScene(scene)
}
Don't forget to import GameKit.

How to call a function in viewController from GameScene

I am wanting to call a share function during game play. I have some code I can use in my GameViewController
func showTweetSheet() {
let tweetSheet = SLComposeViewController(forServiceType: SLServiceTypeTwitter)
tweetSheet.completionHandler = {
result in
switch result {
case SLComposeViewControllerResult.Cancelled:
//Add code to deal with it being cancelled
break
case SLComposeViewControllerResult.Done:
//Add code here to deal with it being completed
//Remember that dimissing the view is done for you, and sending the tweet to social media is automatic too. You could use this to give in game rewards?
break
}
}
tweetSheet.setInitialText("Test Twitter") //The default text in the tweet
tweetSheet.addImage(UIImage(named: "TestImage.png")) //Add an image if you like?
tweetSheet.addURL(NSURL(string: "http://twitter.com")) //A url which takes you into safari if tapped on
self.presentViewController(tweetSheet, animated: false, completion: {
//Optional completion statement
})
}
I have also set the ViewController in my GameScene Class...
var viewController: GameViewController!
... and set the scene.viewController to self in my ViewController
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
/* 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
skView.presentScene(scene)
scene.viewController? = self
}
}
However, When I call the function like so...
viewController.showTweetSheet()
... from my GameScene it gives me a "found nil when unwrapping an optional value" error.
I think I may need to set the scene.viewController to self later on, but I don't know how to do that in the viewController.
Any help would be much appreciated.
First of all, you don't need the question mark after scene.viewController.
Second, scene.viewController = self should come before skView.presentScene(scene). This will probably fix your problem.
Lastly, it's considered bad design (or at least sloppy) to make an SKScene have a property that is a UIViewController. The scene class is now tied to using a UIViewController, and if you want to extend your code to something that doesn't use UIViewController to control views (e.g. you want to make a Mac version of your game), it won't work right away because it's hard-coded to work with UIViewController.
The "pure" way to do this would be a technique called "delegation" by iOS programmers. You create a protocol that will be your delegate, and make your view controller implement that protocol. Then the SKScene uses the protocol, not UIViewController.
All that said, you might want to leave out this complexity.

In swift, how to get memory back to normal after an SKScene is removed?

I created a simple game with SpriteKit, however every time I run the game, the memory usage in simulator increases about 30mb, but never decreases when the game is finished.
When I run the game over ten times the simulator gets slower and slower and eventually crashes.
In this simple game I have two controllers and a gamescene:
MainController calls GameViewController via a button triggered
In GameViewController, gamescene is initialised in this way:
class GameViewController: UIViewController
{
var skView:SKView!
var scene:GameScene!
override func viewDidLoad() {
super.viewDidLoad()
scene = GameScene(size: view.bounds.size)
skView = view as SKView
skView.ignoresSiblingOrder = true
scene.scaleMode = .ResizeFill
scene.viewController = self
skView.presentScene(scene)
}
//with a prepareForSegue deinitialises the scene and skview:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "GameFinished"{
scene.removeAllActions()
scene.removeAllChildren()
scene.removeFromParent()
scene = nil
skView.presentScene(nil)
skView = nil
let target = segue.destinationViewController as MainController
}
}
}
In the GameScene, viewController is a property
var viewController:GameViewController? = GameViewController()
the segue is triggered with this:
self.viewController!.performSegueWithIdentifier("GameFinished", sender: nil)
I've also tried putting remove methods into deinit in GameScene:
deinit{
self.removeAllActions()
self.removeAllChildren()
}
Still wouldn't work
Your GameViewController has a strong reference to your GameScene. And your GameScene had a strong reference to your GameViewController. This leads to a strong reference cycle, which means that neither objects will be deallocated.
You need to declare your viewController property in your GameScene as weak.
weak var viewController:GameViewController? = GameViewController()
Using Swift 3, Xcode 8 and iOS 10.
After avoiding strong references, taken care of SKTextures, etc. memory level didn't recover after dismissing the scene and returning to the "menu" viewController.
I was using:
override func sceneDidLoad() {...}
This is available in iOS 10 but I wanted iOS 8&9 compatibility that's why I've
changed to the older:
override func didMove(to view: SKView) {...}
Beside getting compatible with older iOS versions, it turns out that the memory level drops after dismissing the scene. This was a surprise. I'm missing probably some leaks but it's working for me.
I hope it helps someone.