I am trying to make a survival game where each level the player has to survive the game for so long in order to get to advance to the next level. However, when I go from level to level, I cannot figure out how to update the score for each scene. I understand how to add and remove subviews, but that is not the problem. The problem is calling a variable in a different scene, and changing that variable from the new scene. I will leave some code here in order for reference. Thank you in advance.
In Level1.swift,
I declare the score var:
var Score = Int()
I then add to it when I want to, and represent it as shown as a label.
Score++
ScoreLabel.text = "\(Score)"
Then in Level2.swift
I declare the variable again
var ScoreLabel = UILabel()
And add to it as well whenever needed... However, the only thing that happens is a new label comes up and overlaps the score label already shown...
Score++
ScoreLabel.text = "\(Score)"
How do I keep a running score from scene to scene in swift?
1.This is where I declare the variable, I only do this in this scene
2.This is where I declare the change of scene, I haven't yet, but this is how I plan on changing each scene
3.This is where I add the subview of the label, I do this the same way in each scene
4.This is where I add to the variable, i call upon it this way in each scene
The UILabel is not part of your scene. It's an element of the container view. Therefore it is always visible and overtime you create a new one, it will be shown on top of the existing label.
The easiest way is to use global variables. Just place
var Score = Int()
var ScoreLabel = UILabel()
outside of the class definition (and add it only once to the container view).
Another option is to use an SKLabel and add it to the scene.
This stack overflow question is about a different issue, but shows a sample how to use an SKLabel for scoring information:
score label display value overwrites
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.
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've created this block of code to create an animation to make a selection visible. called in touchesBegan.
private func setSelectionAnim(atPoint point:CGPoint) {
selectAnim.removeActionForKey(ANIM_SELECT)
let action = SKAction.repeatActionForever( SKAction.animateWithTextures(WBTextures.Select(), timePerFrame: 0.33))
selectAnim.position = point
selectAnim.runAction(action, withKey: ANIM_SELECT)
}
WBTextures.Select() is an instance of a TexturePacker generated class and selecAnim is a SKNode() instantiated and added as a child to the GameScene in the init.
But if I cancel the the animation (when a target is selected ) with the following code:
private func stopSelectionAnim()
{
selectAnim.removeActionForKey(ANIM_SELECT)
}
The Problem is: that when i call stopSelectAnim() the last presented texture stays visible on screen. which after searching the SpriteKit Programming Guide
Canceling Running Actions To cancel actions that a node is running,
call its removeAllActions method. All actions are removed from the
node immediately. If a removed action had a duration, any changes it
already made to the node remain intact, but further changes are not
executed.
would be expected. After that a added the line selectAnim.removeFromParent() now the artifact disappears. But now the problem occurs that the next time setSelectionAnim(atPoint point:CGPoint) is called nothing happens. Logic if i deleted the node, but don't get a run time error ,so 'selectAnim' is not nil. Why does adding the extra line, solve the problem and introduces the next.
what is the best practice to interrupt an animation and still be able to enable it.
I've come up with a couple of ideas which none of I really like and fit into my scene setup.
re-instantiating and addChild' theselecAnimin the beginning of each call tosetSelectionAnim(atPoint point:CGPoint)`
create an action sequence with a runBlock() which checks a Bool whether to stop and if so how do I stop the animation with in a block
adding the animation to a new SKNode and add this as a child to the selecAnim node. ( I've tried this one but the animation did not show at all see code:
selectAnim.removeActionForKey(ANIM_SELECT)
let node = SKNode()
let action = SKAction.repeatActionForever( SKAction.animateWithTextures(WBTextures.Select(), timePerFrame: 0.33))
node.position = point
node.runAction(action, withKey: ANIM_SELECT)
selecAnim.addChild(node)
Do I miss the trivial solution or witch one is the best or do one of you please have a better solution.
I want to pause my game for 2 seconds and continue again. I tried the following but it didn't work.
var duration = NSTimeInterval(2)
var wait=SKAction.waitForDuration(duration)
self.runAction(wait)
self is SKScene. Is there a different method to achieve this?
You can set the SKNode.isPaused property. This is a Boolean value that determines whether actions on the node and its descendants are processed.
This means that if you set self.paused = true in the SKScene, all of the child nodes of the scene will be paused. You can also do it to individual nodes like sprites.
For more information
https://developer.apple.com/library/prerelease/ios/documentation/SpriteKit/Reference/SKNode_Ref/index.html
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.