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).
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 just recently started on iOS Spritekit. Most tutorials and StackOverflow topics mentioned that I can't use Storyboard for arranging SKSpriteNode so I've been struggling on layout my views, especially I also have some other components like UILabels.
I hack this by placing invisible UIViews (and do autolayout on them with other components), and pass the values into the SKScene which will place SpriteNode based on the value--after converting the coordinate systems, etc.
Would there be potential issues on this approach? It works for simple cases but it is obviously a hack... :P
Or if there are better way of layout SpriteNode? I am going through RayWenderlich tutorials, but so far many of them seems to be hardcoded positions?
Well, it's certainly more work than needs to be done so there could be performance issues. As an alternative (and the correct way) to do this is use the scene editor in Xcode.
Create a new SpriteKit Scene File and add your nodes to it. Create a new Swift file of class SKScene and set the custom class for the scene file.
Then all you have to do to get those nodes into your class (like IBOutlet) is the following...
override func didMove(to: view){
let shipNode = self.childNode(withName: "shipNodeName") as! SKSpriteNode
}
I am a beginer and starting my first swift game. It seems like the GameScene.sks interface would be extrememly easy to position my labels and nodes for each level, but I am having trouble figuring out how to reference the nodes with swift.
For example I dragged over a label to act as a timer, but I dont know how to update the label text with code.
I was thinking something like:
func timerDidFire(){
countDown--
SKLabelNode.name("countDownTimerLabel").text = countDown
}
You need to use the childNodeWithName method on SKNode. For example:
func timerDidFire() {
let label = childNodeWithName("countDownTimerLabel") as! SKLabelNode
label.text = --countDown
// I believe this is an appropriate case for force unwrapping since you're going to
// want to know if your scene doesn't contain your label node.
}
Since you're going to be accessing the SKLabelNode often, and it takes time to search through the node tree (not very much but something to bear in mind), it may be a good idea to keep a reference to the label. For example, in your SKScene subclass:
lazy var labelNode: SKLabelNode = self.childNodeWithName("countDownTimerLabel") as! SKLabelNode
On a related note, if you're looking for multiple nodes there's the
enumerateChildNodesWithName(_:usingBlock:), also on SKNode.
Finally, for more information have a look at Apple's WWDC 2014 talk: Best Practices for Building SpriteKit Games where they cover both the methods I mentioned.
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.