I am trying to reset my game to the start screen every time this function is called by just creating a new scene:
func restart() {
let newScene = GameScene(size: view!.bounds.size)
newScene.scaleMode = .AspectFill
view!.presentScene(newScene)
}
The first time it is called, it works correctly. However, the second time it is called I get an (apparently very common) error:
fatal error: unexpectedly found nil while unwrapping an Optional value
I don't understand why I recieve this error in this scenario.
Is there a better way to reload the entire GameScene? Do I need to do anything with the old discarded GameScene instance?
You have probably defined this method inside your GameScene class, right?
Let's see what the API doc says about the view property of SKScene.
The view that is currently presenting the scene. (read-only) To
present a scene, you call the presentScene: method or
presentScene:transition: method on the SKView class. If the scene is
not currently presented, this property holds nil.
So I guess you are calling this method after the current scene has been removed from the view.
Is there a better way to reload the entire GameScene?
This approach is right, but you should call restart before the current scene has been removed from the view, otherwise it has no reference to your view.
Do I need to do anything with the old discarded GameScene instance?
Nope.
Once you replace the current scene with the new one, if you don't have strong references to the old scene, ARC does remove it from memory.
To be sure the current scene is being removed you have several easy tools:
1. Debug Navigator
While the game is running take a look ⌘ + 6 at the Debug Navigator in Xcode, then invoke restart several times.
If the used memory indicator grows up, then probably you are not releasing the GameScene from memory (or some other object).
Not releasing the GameScene is one of the worst thing that can happen in a videogame because a scene holds references (directly or indirectly) to every other node in you current level/screen. It happened during the development of my game so I know what I'm talking about :D
2. deinit
Add the deinit method to you GameScene
class GameScene : SKScene {
deinit {
print("The GameScene has been removed from memory")
}
}
deinit is automatically called when the object is removed from memory. Now each time you restart your scene, you should see the message
The GameScene has been removed from memory
on the console coming from the dead scene.
Related
I understand that there are 3 ways to create a scene in SpriteKit, init(), didMove, and sceneDidLoad.
But I can't understand to separate the 3 ways. Reading other questions I understood the order to call is init -> SceneDidLoad -> didMove.
How can I use them to use effectively?
You're right about the order those functions are called in. But only init(size:) actually creates a scene.
init(size:) initializes a new scene object with the given CGSize as its bounds. Anything that must be set up prior to the scene becoming visible should happen here. That's the important bit because a newly initialized scene isn't visible to the user until it's presented by a view.
sceneDidLoad() is called as a result of init(size:) and can be used to do any more setup required before the scene is presented. init(size:) can be called from wherever you want to make a new scene, but sceneDidLoad() happens in the scene itself. This is useful for any setup that you want all scenes of this class to use.
didMove(to:) is different because it doesn't have to do with the initialization. This function is called when the scene is presented by a view. Basically, when it becomes visible to the user. UI adjustments and layout for elements inside the scene are typically handled here.
So I have objects that in turn have sprites associated to them. a snippet of my Object class:
import SpriteKit
class Block {
var sprite : ColourSprite
}
So as you can see, it has a variable that is in fact a SKSprite (ColourSprite is my custom class that inherits from SKSpriteNode).
Now, at some points during the game, these sprites are deleted (i.e sprite.removeFromParent()), but the objects are obviously still somewhere.
I want to be able to send the objects to garbage collection once their sprites are gone. I am thinking that I can do something like sprite.getOwner() but I can't seem to find it. Is this possible?
The only other option I can think of is to manually check all objects and check each one's sprite but I feel that is long and wasteful.
You can check whether the Blocks are still in memory by using Xcode 8.3's new debug panel.
Just after you remove your sprites pause the program and go to that panel. See if there is any Block instances in the left panel. If there is, click on it to check what is retaining it.
If for example your GameScene is retaining the Block, you go to your GameScene and find the property. Then you can just set that to nil after you remove your sprite.
I am developing a game for iOS using Swift. The game is played in rounds, and when a round is over, it is no longer needed. So unlike conventional applications where a storyboard scene may stick around when someone navigates away from it, I want the scene for the round to be destroyed when the game navigates to the scene following the round.
I am pretty confident I have figured out a way to do this because I’m instantiating certain objects in the scene and I have verified that the deinit method for these objects gets call when the round ends and navigation to the next scene occurs. The problem is that for one of the objects, the deinit method is not being called, and I have not been able to figure out why. The storyboard scene should be the only thing referencing the object, and searching through to source code has not revealed any other usages. It seems like something beside the storyboard scene somehow has a reference to the object and keeps it alive when the scene goes away.
I have verified the object is being created by setting a breakpoint in its init method, and that breakpoint gets hit when starting the round.
I’ve tried using the Allocations instrument to track what might be referencing the object. However when I run the game and finish the round. I can find no evidence that the Allocation instrument ever saw the object at all. I suspect the instrument may not track objects that are created by a storyboard scene, especially since I can not find traces of the other storyboard objects that do get deleted with the storyboard.
What can be done to determine why this object seems to survive past the lifetime of the storyboard scene that should own it?
I think found out why the object was not being destroyed. In it's initializer it was applying a closure to something else. The closure contained a reference to a member of the object, and so I think the closure was capturing self strongly even though self was never explicitly needed for things to compile successfully. This might be a bug with the Swift compiler; it could have emitted a warning or error about this; it would have been easier the find and/or prevent the problem. Anyway I changed the closure so that it captures the object's member as unowned and now the object appears to get destroyed.
I'm working on a game. The game's code is almost entirely in a PlayScene.swift file. However, since I need to recognize pan gestures, I had to put a UIPanGestureRecognizer to my ViewController.swift. As the player drags the screen, I would like to rotate a SpriteNode that is declared and used in the PlayScene.swift. The name of this spriteNode is Tommy, but when I do PlayScene().rotateTommyLeft() from the ViewController (which is a custom function of mine located in PlayScene.swift), the function rotateTommyLeft is called, but it doesn't rotate the same sprite that is currently running. It creates a new instance of class PlayScene, creates new SpriteNode tommy and instead rotates that one.
How can I access the SpriteNode tommy from the PlaySceen that is currently running?
Edit: I'm posting the parts of code so we can solve out problem regarding storing the scene and transferring it between other classes.
At the top of my GameScene class, I declared variable storedPlayScene to store the scene in which the gaming occurs.
var storedPlayScene = PlayScene()
then, I create the scene and present it when play button is pressed (also in GameScene class)
var scene = PlayScene(size: self.size)
let skView = self.view as SKView
skView.ignoresSiblingOrder = true
scene.scaleMode = .ResizeFill
scene.size = skView.bounds.size
scene.backgroundColor = UIColor(red: 106/255.0, green: 180/255.0, blue: 1.0, alpha: 1.0)
storedPlayScene = scene
skView.presentScene(scene)
Now the surprising part occurs when I try to obtain two variables belonging to PlayScene class, from the ViewController class.
When I write this in the ViewController class
var rotation = GameScene().getStoredPlayScene().tommy.zRotation I obtain the correct zRotation.
However, when I write this in the same ViewController class var position = GameScene().getStoredPlayScene().getPos() the data is incorrect. It says that the position is (0,0).
I have problem figuring out why the position is incorrect while the zRotation is good.
When you call PlayScene().rotateTommyLeft(), you're creating a new instance of PlayScene and not accessing the playScene that's currently running in your game. So, you need to store playScene somewhere so that you can access it. Since you're creating playScene inside gameScene, the GameScene class is a good place to put add a property for playScene.
Side note: You mentioned trying to store it in NSUserDefaults in the comments. That's not only not going to work, but that's not what NSUserDefaults is for. NSUserDefaults is designed for simple preferences and not much else. You might store the last name they used for their high score, or maybe what level they're currently on, but usually not things that are much more complex than that.
Now that we have a property for playScene in gameScene, you need to be able to access it from your view controller. To do that, you're going to need to be able to access gameScene from your view controller. So, just like you added a playScene property to gameScene, add a gameScene property to your view controller. Once that is in place and set correctly, you'll be able to access the original method you were trying to call: self.gameScene.playScene.rotateTommyLeft()
Re: rotation and position in your edit. You're doing the same thing there: you're creating a new instance of GameScene on each call. If you change that to use the properties we created above, it should work fine.
I use a dispatch_once NSObject to create data pointers. So all game asset pointers are made when the main viewcontroller appears. In order to play a game, the user taps a UIButton corresponding to a particular level on a UIViewController. Let me call it LevelSelectionController. When the game is over, the user will tap a label (SKLabel). And all actions and nodes will be removed.
[self removeAllActions];
[self removeAllChildren];
[self removeFromParent];
Moreover, an SKScene subclass for a particular level delegates the task of returning the user to LevelSelectionController to the viewcontroller presenting game SKView as follows.
- (void)closeScene {
SKView *spriteView = [[SKView alloc] init];
[spriteView presentScene:nil];
[self.navigationController popViewControllerAnimated:YES];
}
The only issue that I have is that the memory remains high when the user leaves the game scene (SKScene). The game requires a lot of assets. So when the game starts, the memory usage will jump to 200 MB. When the user returns to the original level selection view controller, the game simulator is still consuming 200 MB according to Activity Monitor. When the user enters a different level, the memory usage will jump by another 10 MB. So how can I release the memory for the last game once the user leaves SKScene?
I'm using ARC. The Xcode version is 5.1. The development target is iOS 7.1.
Thank you for your help.
-- Edit 1 --
I'm silly. I know what the problem is. When I close the scene, I'm creating a new SKView, which I then set it to nil to get of out the current scene. It works. But that should not be the way of doing it. Instead, I need to set the current SKView to a variable before presenting it. When I close the scene, I need to set that variable to nil. Hmm... I wasn't thinking.
-- Edit 2 --
There's little change when the current scene is presented with nil. Removing it from removeFromSuperview doesn't do much.
It has been noticed by a few people on SO that an SKScene gets deallocated when the containing SKView is removed from it's superview.
Have a look at these questions and their answers:
Deallocate SKScene after transition to another SKScene in SpriteKit
iOS 7 Sprite Kit freeing up memory
Also, try modifying the closeScene method like so:
- (void)closeScene {
SKView *spriteView = (SKView*)self.view;
[spriteView presentScene:nil];
[self.navigationController popViewControllerAnimated:YES];
}
Put NSLog() to SKScene's dealloc method to be sure that it's deallocating.
Also, resources may be not released just after reference count of your scene reaches 0. Due to internal optimizations assets may stay in memory until receiving Memory warning signal.