skLabelNodes only working sometimes - swift

So I added two label nodes to my code, and want one to be hidden while the other is shown, and then vice versa once the game begins.
class GameScene: SKScene {
// Declarations
let startLabel:SKLabelNode = SKLabelNode()
let scoreLabel:SKLabelNode = SKLabelNode()
var score = 0
override func didMoveToView(view: SKView) {
// Properties
startLabel.fontSize = 20
startLabel.fontColor = UIColor.blackColor()
startLabel.position = CGPointMake(self.frame.width*0.5, self.frame.height*0.5)
startLabel.text = "Touch Paddle To Begin"
startLabel.hidden = false
self.addChild(startLabel)
scoreLabel.fontSize = 20
scoreLabel.fontColor = UIColor.blackColor()
scoreLabel.position = CGPointMake(self.frame.width*0.5, self.frame.height*0.75)
scoreLabel.text = "\(score)"
scoreLabel.hidden = true
self.addChild(scoreLabel)
Then, when the Paddle is touched, a moveBall() function is initiated and the game begins. This is where I swapped the labels.
func moveBall() {
// Setting Labels
self.startLabel.hidden = true
self.scoreLabel.hidden = false
// Starting game
self.ball.physicsBody?.dynamic = true
However, it only works sometimes. It will work perfectly, and then without changing anything I'll run it again and only one label will show up. Then again and the other label will show up, or the startLabel will show for only a frame then disappear.
Disclaimer: I do not really know how to code, just got an idea for a game and am trying to make it a reality. Apologies if solution is something simple. Also any advice for my code would be appreciated. Thanks!

From the information given it should work correctly. However you should call the moveBall() in the touchesBegan method. Not the update method.
Also if you want it to be called if the user touches the labelNode use the location of touches began. (But I would rather use a sprite instead of a label coz labels are a bit unresponsive coz sometimes finger doesn't touch the label.
So call the moveBall() in the correct method. Give some more info to give a more accurate answer.

Related

Sprite Kit Animations and Texture Atlases in Swift 4

Currently working on an application that requires a little bit of animations through an array of images. I've done tons of research online on how to resolve my issue but couldn't run into anything for Swift 4. Below is my code, it shows the first picture in the loop when I run the app but no animation at all. Everything looks fine, I don't see a problem with my code but maybe you guys can help. Appreciate it in advanced!
let atlas = SKTextureAtlas(named: “mypic”)
var TextureArray = [SKTexture]()
var person = SKSpriteNode()
override func didMove(to view: SKView) {
person = SKSpriteNode(imageNamed: "red_1.png")
person.size = CGSize(width: 150, height: 129)
person.position = CGPoint(x: 0, y: 0)
person = SKSpriteNode(imageNamed: atlas.textureNames[0])
for i in 1...atlas.textureNames.count {
let Name = "red_\(i).png"
TextureArray.append(SKTexture(imageNamed: Name))
}
self.addChild(person)
}
override func update(_ currentTime: TimeInterval) {
let myAnimation = SKAction.animate(with: TextureArray, timePerFrame: 0.1)
person.run(SKAction.repeatForever(myAnimation))
}
The animation action is placed in update, which is executed once every frame. So if the game runs at 60 FPS, update gets called 60 times in one second. This means that every second person gets 60 new myAnimation actions that it needs to run.
To fix this, consider placing the animation action somewhere else in your code, e.g. right after adding the person to the scene. Since this is a repeatForever action, the animation will run as you intended until either the action is removed from the node, or the node is removed from the scene.
Hope this helps!

Swift 3 (SpriteKit): Reseting the GameScene after the game ends

I am wanting to 'reset' and 'restart' the GameScene so it is as if the GameScene was first called. I have looked at different methods for doing this, but each time I get a warning that I'm trying to add a node to a parent which already has a parent. However, in my code I delete all my existing nodes so I'm really confused as to how to reset the GameScene. This is how I do it now (this code is called when I want to restart the GameScene from scratch and it is called within the GameScene class):
let scene = GameScene(size: self.size)
scene.scaleMode = .aspectFill
let animation = SKTransition.fade(withDuration: 1.0)
self.view?.presentScene(scene, transition: animation)
self.removeAllChildren()
self.removeAllActions()
self.scene?.removeFromParent()
1.Edited: I realised that why I was getting this warning: "I'm trying to add a node to a parent which already has a parent" was because I had all the variables for the scene outside of the class and as global variables. However, now when the game restarts, the game is in the bottom left corner. Why is this the case and how do I fix this? - FIXED
2.Edited: Everything works fine now, but now my concern is that deinit{} isn't called even though every node is deleted and the fps doesn't drop over time. Here is what I have in my GameViewController for setting the scene and in my GameScene (every instance relating to the scenes so basically all that is relevant):
import UIKit
import SpriteKit
import GameplayKit
var screenSize = CGSize()
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
screenSize = scene.size
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
Then my GameScene is basically:
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
//Declare and initialise variables and enumerations here
deinit{print("GameScene deinited")}
override func didMove(to view: SKView) {
//Setup scene and nodes
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//Do other things depending on when and where you touch
//When I want to reset the GameScene
let newScene = GameScene(size: self.size)
newScene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
newScene.scaleMode = self.scaleMode
let animation = SKTransition.fade(withDuration: 1.0)
self.view?.presentScene(newScene, transition: animation)
}
Any answers would be greatly appreciated :)
How to reset the scene?
You just have to present a new, same scene again whenever you want. So, you are doing it fine.
Possible leaking problems?
Also, if you don't have leaks in your game, means no strong reference cycles, you don't even need self.removeAllChildren() and self.removeAllActions()... Of course if you explicitly want to stop actions before transition animation starts, the using this method make sense. The point is, when scene deallocates, all objects that depends on it should / will deallocate as well.
Still, if you don't know from the beginning what you are doing and how to prevent from leaks, eg. you are using strong self in block which is a part of an action sequence, which repeats forever, then you certainly have a leak, and self.removeAllActions() might help (in many cases, but it is not an ultimate solution). I would recommend to read about capture lists and ARC in general because it can be useful to know how all that work just because of these situations.
Scene is a root node
Calling removeFromParent() on a scene itself has no effect. Scene is a root node, so it can't be removed in your current context. If you check scene's parent property you will notice that it is nil. Of course it is possible to add a scene to another scene, but in that case, the scene which is added as a child, will act as an ordinary node.
And finally, how to present the new scene ? Easy, like this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let newScene = GameScene(size: self.size)
newScene.scaleMode = self.scaleMode
let animation = SKTransition.fade(withDuration: 1.0)
self.view?.presentScene(newScene, transition: animation)
}
If something doesn't work for you, it is likely that you have leaks (means your scene isn't deallocated). To check this, somewhere in your GameScene override deinit method, like this:
deinit{print("GameScene deinited")}
To explain you this a bit further... What should happen is that you should present a new scene, a transition should occur, an old scene should be deallocated, and you should see a new scene with an initial state.
Also overriding deinit will just tell you if the scene is deallocated properly or not. But it will not tell you why. It is up to you to find what in your code retaining the scene.
There are 2 main ways that I can think of that do this. The main way that I go this is that if the game is over, (due to the character health falling to zero, or they collide with an object that causes the round to be over, or time is up or whatever), when that happens I like to transition to a new scene that is a summary screen of their score, how far they made it etc.
I do this by having a bool variable in the main GamePlay scene like this.
var gameOver: Bool = false
Then in the code that fires off to cause the game to end set that variable = true.
In the update function check to see if gameOver == true and transition to the GameOverScene.
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
// Initialize _lastUpdateTime if it has not already been
if (self.lastUpdateTime == 0) {
self.lastUpdateTime = currentTime
}
// Calculate time since last update
let dt = currentTime - self.lastUpdateTime
// Update entities
for entity in self.entities {
entity.update(deltaTime: dt)
}
self.lastUpdateTime = currentTime
if gameOver == true {
print("Game Over!")
let nextScene = GameOverScene(size: self.scene!.size)
nextScene.scaleMode = self.scaleMode
nextScene.backgroundColor = UIColor.black
self.view?.presentScene(nextScene, transition: SKTransition.fade(with: UIColor.black, duration: 1.5))
}
}
The update function will check at each frame render to see if the game is over and if it is found to be over it will perform any actions that you need it to and then present the next scene.
Then on the GameOverScene I put a button saying "Retry" and when they click that it fires off the GamePlayScene again, running the view DidLoad function and setting up the GamePlayScene from scratch the way that it should.
Here is an example of how I handle that. There are a few different ways to call a scene transtion. You can give this one a try if it isn't working quite right.
if node.name == "retryButton" {
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view! as SKView
/* 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
scene.size = skView.bounds.size
skView.presentScene(scene, transition: SKTransition.fade(withDuration: 2.0))
}
}
That is my preferred method of handling the game over transitions.
The other method would be to create a function that resets all of the variables that have changed during the course of playing. Then you could use the same code from the Update function above, but instead of transitioning you could create a label on the scene. If the user clicks the label it would fire off of that function to reset all of the variables that have changed, reset the players locations, stops all actions, resets players health etc. Depending on how many things you have changing during the course of gameplay it'll probably be more practical, (as I've found), to transition to a new scene to give a summary and then reload the GamePlayScene through a button. Then everything will load up just the same as it does the first time that the user entered that main GamePlayScene.

Swift SKSpriteNode will disappear but is still clickable

I have a problem with SpriteKit and Swift.
I'm adding SKSpriteNodes at different points of the code to the scene - some of them are clickable, some are not. I use the clickable Nodes as a Menu for the player. So, for example - if he clicks the InventoryButtonNode he jumps into the Inventory. In the Inventory he can touch the PlayButton and jumps back into the game. So, first i add the Nodes:
override func didMoveToView(view: SKView) {
PlayButton = SKSpriteNode(imageNamed: "PlayButton")
PlayButton.size = CGSize(width: 100, height: 100)
PlayButton.position = CGPoint ... // not important
PlayButton.zPosition = 200
PlayButton.name = "PlayButton"
self.addChild(PlayButton)
InventoryButton = SKSpriteNode(imageNamed: "InventoryButton")
InventoryButton.size = CGSize(width: 100, height: 100)
InventoryButton.position = CGPoint ... // different position than PlayButton
InventoryButton.zPosition = 200
InventoryButton.name = "PlayButton"
self.addChild(InventoryButton)
In the override func touchesBegan I use these "menu"-Nodes, here for example the InventoryButton-Node.
if InventoryButton.containsPoint(touch.locationInNode(self)) {
print("Show Inventory")
ShowInventory()
}
Now in the ShowInventory() function i want to remove those "menu"-Buttons from view so that i can add other nodes to show the Inventory of the Player.
func ShowInventory(){
PlayButton.removeFromParent()
InventoryButton.removeFromParent()
}
If I build and run this, the Nodes will be removed - or lets better say - they will get invisible.
Because, if i touch now at the position of InventoryButton I still get the print "Show Inventory" - so the function still reacts to my touch even if the node is not visible.
My problem is, that i have like 4 different functions like ShowInventory() .. I have ShowGame() and so on .. and i want in these functions to completely remove the Nodes and the "Touch"-Ability ..
I need a function which can remove the Nodes completely ..
I have even tried:
func ShowInventory(){
self.removeAllChildren()
}
I get a grey Background without any nodes .. but still - if I touch where the Inventory Buttons position was i get the function called and the print "Show Inventory" ... it's frustrating.
It is because your check for which button is pressed does not take into account whether the button is visible.
Even if a node has been removed from its parent, it still has a position and size. Those two properties are used by containsPoint: to determine if a point is in a node
The easiest way to fix it would be to just check if the button has a parent node before checking to see if the button contains the point.
if InventoryButton.parrent != nil && InventoryButton.containsPoint(touch.locationInNode(self)) {
print("Show Inventory")
ShowInventory()
}

Swift Sprite Kit Add the amount of sprites per level

I don't have any code, but I was just wondering how you would add sprites to your scene based on what level the game was at. For example: Level 1 would have 1 sprite, level 2 would have 2, level 3 would have 3....
I don't want to have to write out every single level as the game idea could go up to 100's of levels.
I have a very simple idea in mind, but I can't seem to figure it out.
I'm sorry if this is too vague, but I don't have any real code to include. Any help would be greatly appreciated.
If each level is essentially the same, except the number sprites, this is what I would recommend:
1. Create a subclass of SKScene that holds logic common to all your levels. For example:
class Level : SKScene {
var numberOfSprites: Int = 0
private func createSprites() {
for _ in 0..<numberOfSprites {
let mySprite = SKSpriteNode(imageNamed: "YourSprite")
// Setup anything else with the sprite, like the position.
self.addChild(mySprite)
}
}
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
createSprites()
}
private func levelIsComplete() {
// Cell this when the level is complete to go to the next level.
if let view = self.view {
let nextLevel = Level(size: self.size)
nextLevel.numberOfSprites = self.numberOfSprites + 1
view.presentScene(nextLevel)
}
}
}
2. When creating your first level set the number of sprites to 1 and let it handle everything else.
let myLevel = Level(size: view.bounds.size)
myLevel.numberOfSprites = 1
Here's it in action
In my version I gave the sprites random positions and called levelIsComplete in touchesBegan.

How to add new SKSprideNodes off the screen and sync their animation with existing objects?

I am trying to animate multiple SKSpriteNode's of the same type using single SKAction.
What I would like to do, is to display four SKSpriteNode's of the same type on the screen.
Then animate them all when the button is pressed and move them to the left.
As soon as the first SKSpriteNode would leave the screen I want to add another SKSpriteNode off the screen position on the right side and add him to this SKAction loop.
Image below shows in detail what I am after.
So far I was able to display four SKSpriteNode's of the same type on the screen and add them to the array.
var squareBox = SKSpriteNode()
func addSquares(size:CGSize){
for var i = 0; i < 4; i++ {
// Create a new sprite node from an image
squareBox = SKSpriteNode(imageNamed: "noBox")
// Square pysics settings
squareBox.physicsBody = SKPhysicsBody(rectangleOfSize: self.squareBox.frame.size)
squareBox.physicsBody!.dynamic = false
squareBox.physicsBody!.categoryBitMask = squareBoxCategory
squareBox.physicsBody!.contactTestBitMask = leftEdgeCategory | edgeCategory
// SquareBox positioning on X
var xPos = size.width/5 + squareBox.size.height/2
var xPosInt = Int(xPos) * (i + 1)
xPos = CGFloat(xPosInt)
var yPos = size.height/2 + (squareBox.size.height/2)
squareBox.position = CGPointMake(xPos - squareBox.size.height/2, yPos)
self.addChild(squareBox)
squareArray.append(squareBox)
}
}
I am not sure if that is correct way to animate four SKSpriteNode's of the same type, but when the button is pressed they all seem to move to the left as desired.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
self.nodeAtPoint(location)
if self.nodeAtPoint(location) == self.button {
let move = SKAction.moveByX(-size.width/5 - squareBox.size.height/2, y: 0, duration: 1)
print("button clicked!")
for box in squareArray {
box.runAction(move)
}
}
}
}
I created an invisible 1px line on the left edge of the screen to define when the first SKSpriteNode leaves the screen.
func addLeftEdge(size:CGSize){
let leftEdge = SKNode()
leftEdge.physicsBody = SKPhysicsBody(edgeFromPoint: CGPointMake(1, 1), toPoint: CGPointMake(1, size.height))
leftEdge.physicsBody!.categoryBitMask = leftEdgeCategory
self.addChild(leftEdge)
}
The problem with this approach is that SKSpriteNode's in the array when I animate them don't respond on didBeginContact when they touch the 1px line.
func didBeginContact(contact: SKPhysicsContact) {
println("contact!")
}
In the end I want to be able to apply the Force to all SKSpriteNode's on the screen for a few seconds to move them to the left and descend the force speed over the time until they all stop. My idea was to create four objects of the same type and add them to the screen + add them to the array var squareArray = [SKSpriteNode]().
Then when the first SKSpriteNode leaves the screen I would remove it from array and add it again at the end and position it off the screen on the right so it will be moved in seamless animation.
I have a feeling that my entire approach is wrong.
Please advise what is the best approach to animate multiple SKSpriteNode's to achieve my goal as described above. Am I on the right track ?
Please help, thank you.
In order to detect contact, one of the SKSpriteNodes must have physicsBody.Dynamic= YES;
Since you want neither to be dynamic, there is a quick fix I have found:
leftEdge.physicsBody.Dynamic=YES; //This allows it to enter onContact
leftEdge.physicsBody.collisionBitMask=0; //Disables edge from being moved by collision
leftEdge.physicsBody.AffectedByGravity=NO; //Prevents any in game gravity from moving the edge
This allows you have an edge that is dynamic and can trigger onContact yet the edge will still act as if it is not dynamic.
I think this solution isn't very clean and requires a lot more code than what is necessary to get this effect. In the update() function since there are only 4 or 5 boxes why don't you simply loop through the objects and check if any of their frame's minX's are less than 0 and if they are reset them to the starting point which I'm assuming is (view.frame.width + box.size.width) because that's completely offscreen. That way there's no array manipulation in that you don't have to remove and append objects to the array. Also you don't need to attach a physicsBody where none is required. And you don't need the leftEdge