init() vs didMove vs sceneDidLoad in SpriteKit - ios

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.

Related

Accessing SKView methods from an SKNode

In SpriteKit development, how is one supposed to get access to the SKView?
For example; lets say I create a custom SKNode class called Player for use throughout my game:
class Player: SKNode {
/*...*/
}
In this class, if I wanted to generate a SKSpriteNode from an SKShapeNode (in order to improve performance) I need to use the SKView.texture() method. But I don't have access to the SKView that my Player is residing in unless I pass the view to the Player from whoever is instantiating it.
Is there any way to get the current view that an SKNode is attached, so that I'm not having to pass references to the view around my application? Is this just an anomaly of the framework? What is the preferred way of dealing with this problem?
I'm using the Swift language.
Update
Ron Myschuk's answer is correct and I've accepted it as it is probably most useful to the community. However my use-case for wanting to access the view from within a node was in order to use the texture method to create an SKSpriteNode from an SKShapeNode.
Here's a description of the method I wanted to gain access to:
Renders a portion of a node’s contents and returns the rendered image as a SpriteKit texture.
https://developer.apple.com/documentation/spritekit/skview/1519994-texture
This method seems useful outside of the view. For example, when initialising/populating SKNode's that have not yet been added to the display tree.
My solution to this specific problem was simply to create an SKView on the fly and use it for the purposes of my needs, allowing it to be garbage collected by the engine.
let view = SKView()
let texture = view.texture(from: someSkShapeNode)
let sprite = SKSprite(texture: texture)
// we don't need view anymore
Hope this helps someone.
providing that you've added an instance of player to your scene and providing that you are not doing his in the initializer of Player you could use the .scene property
self.scene?.view
You can build a SKTexture from UIImageor CGImage and you can create images from every complex path/drawing you want (see SKTexture from path for example or Drawing and Creating images).

Cannot initialise GameScene more than once using SpriteKit in Swift

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.

swift: Access scene variable in use from controller

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.

Calling method when a node is removed from an SKScene

Is there a way to hook into the SKNode lifecycle in Sprite Kit? Specifically I would like to perform some code when the node gets removed from the scene.
The use case I would like to solve in a bit more detail :
I have some nodes that interact with each other, and I would like them to be notified of certain events that happen to the other nodes. For example, imagine a game where you can tap a node on the scene, and the node's details would appear on a HUD. I would like the HUD to disappear when the node gets removed from the scene.
I plan to use NSNotificationCenter as the notification engine.
Whenever a node gets removed from the scene I would like to post a notification. The easiest way would be to tie into a lifecycle method on SKNode (my nodes are subclasses of SKSpriteNode) like nodeWasRemovedFromParent, but I didn't find any such method.
How can this be done?
I put some thought into coding my own solution by overriding the removeFromParent method in my SKSpriteNode subclass, and posting a notification before calling the super implementation. I am not sure that the removeFromParent method will always be called though. For example, does it get called when I change scenes?
Thanks.
You need to subclass each node class. Override the removeFromParent method as you said. Use only the subclassed versions, otherwise your code won't take effect.
In addition you will want to override removeAllChildren and removeChildrenInArray: or just never use them.
The removeFromParent method will not be called when the scene changes. Instead, override the scene's willMoveFromView: method and send a message to registered observers or simply all child nodes recursively. Use the scene's enumeration function to do so. Note that I'm not 100% sure whether on willMoveFromView the scene's children are still attached, I assume they will.
Unfortunately it's impossible to just subclass SKNode and then expect the subclass' code to work for all other node classes, because those subclass from SKNode directly and not your custom SKNode subclass. Hence you need to subclass and add this code to every SK*Node subclass as well if you need it to be notified on removal.
See KoboldKit node classes for an example, which uses a macro to inject this "override" code into SK*Node subclasses to avoid duplicating the code. The actual functionality is in KKNodeShared: https://github.com/KoboldKit/KoboldKit/tree/master/KoboldKit/KoboldKitFree/Framework/Nodes/Framework

iOS7 + Sprite Kit: How can I keep one timer displayed constantly if I'm changing scenes?

I'm working on a game using Sprite Kit where the user needs to solve as many puzzles in a given period of time. Every time a puzzle is solved correctly, a new game scene is created with a new puzzle and displayed. I created a timer for the scenes, but it restarts every time a new scene is generated. Is there a way to keep one timer displayed for multiple scenes?
You should use a singleton file to store this so it will be the only one when you change the scene or anything.
http://www.raywenderlich.com/46988/ios-design-patterns
This link will give you the basic knowledge about how to work with Singleton like that.
If you have one view controller presenting all the scenes, you can attach the timer to that view controller rather than the scenes.
Create and store the timer object and the related data in your view controller instance, or subclass the SKView and store it there. This will retain any object for the lifetime of the view.
Hope helps

Resources