Sprite Kit creating count down timer issues - ios

The timeLabel should count down from 60 to 0 but I have yet to implement a duration. For instance timeLabel.text = String(i) //implement every 1 second So that it will resemble a real count down timer. How would I do that. The other issue is that the game won't start in the simulator when running this code. I get an error and I am redirected to the AppDelegate.swift file: class AppDelegate: UIResponder, UIApplicationDelegate { //error: Thread 1: signal SIGABRT
class GameScene: SKScene {
var timeLabel = SKLabelNode()
override func didMoveToView(view: SKView) {
for var i = 60; i > 0; i-- {
timeLabel.text = String(i)
timeLabel.position = CGPointMake(frame.midX, frame.midY)
timeLabel.fontColor = UIColor.blackColor()
timeLabel.fontSize = 70
timeLabel.fontName = "Helvetica"
self.addChild(timeLabel)
}
}
}

You can do this in a few ways, and here is an example on how to update label text (counter) using SKAction:
import SpriteKit
class GameScene: SKScene {
let timeLabel = SKLabelNode(fontNamed: "Geneva")
var counter = 60
override func didMoveToView(view: SKView) {
timeLabel.text = "60"
timeLabel.position = CGPointMake(frame.midX, frame.midY)
timeLabel.fontColor = UIColor.blackColor()
timeLabel.fontSize = 40
self.addChild(timeLabel)
}
func countdown(){
let updateCounter = SKAction.runBlock({
self.timeLabel.text = "\(self.counter--)"
if(self.counter == 0){
self.counter = 60
}
})
timeLabel.text = "60"
timeLabel.position = CGPointMake(frame.midX, frame.midY)
timeLabel.fontColor = UIColor.blackColor()
timeLabel.fontSize = 40
let countdown = SKAction.repeatActionForever(SKAction.sequence([SKAction.waitForDuration(1),updateCounter]))
//You can run an action with key. Later, if you want to stop the timer, are affect in any way on this action, you can access it by this key
timeLabel.runAction(countdown, withKey:"countdown")
}
func stop(){
if(timeLabel.actionForKey("countdown") != nil){
timeLabel.removeActionForKey("countdown")
}
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
if(timeLabel.actionForKey("countdown") == nil){
self.countdown()
}
}
}
What I am doing here is updating a label's text property each second. To achieve that, I've created a block of code which updates a counter variable. That block of code is called each second using the action sequence.
Note that your current code trying to add label in each loop. Node can have only one parent, and like the app will crash with following error message :
Attemped to add a SKNode which already has a parent
Also you are not running updating label's text property once in a second. You are executing the whole for loop at once (which is done in much less time then a second).

Related

How to start time-based score when player taps on the screen to begin the game

I've been following this solution (How can I increase and display the score every second?) to add scoring to my game based on how much time has passed. I have it working perfectly; the score stops when the player loses and it restarts back to 0 when the player restarts the game.
However, the "timer" begins automatically before the user taps to begin, and I'm trying to have the "timer" start when the player taps on the game to begin playing (the user first has to tap on the screen to start running, beginning the game).
In my didMove method, I have
scoreLabel = SKLabelNode(fontNamed: "Press Start K")
scoreLabel.text = "Score: 0"
scoreLabel.position = CGPoint(x: 150.0, y: 620.0)
scoreLabel.zPosition = GameConstants.ZPositions.hudZ
addChild(scoreLabel)
and in my override func update method, I have
if gameStateIsInGame {
if counter >= 10 {
score += 1
counter = 0
} else {
counter += 1
}
}
I figured that by adding the if gameStateIsInGame {} to the touchesBegan method, it would start when the user taps on the screen but that didn't work. I tried adding it under case.ready: and under case.ongoing: but neither worked.
This is what I have at the top of my touchesBegan method.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
switch gameState {
case .ready:
gameState = .ongoing
spawnObstacles()
case .ongoing:
touch = true
if !player.airborne {
jump()
startTimers()
}
}
}
Any ideas on how to fix this small issue? I can't seem to figure it out.
****EDIT****
Here's the updated override func update method. I got rid of if gameStateIsInGame {} and added the if counter >= 10 statement under if gameState.
override func update(_ currentTime: TimeInterval) {
if lastTime > 0 {
dt = currentTime - lastTime
} else {
dt = 0
}
lastTime = currentTime
if gameState == .ongoing {
worldLayer.update(dt)
backgroundLayer.update(dt)
backgroundGround.update(dt)
backgroundSunset.update(dt)
if counter >= 10 {
score += 1
counter = 0
} else {
counter += 1
}
}
}
Simplified Solution
Added the score counter under override func update.
override func update(_ currentTime: TimeInterval) {
if gameState == .ongoing {
if counter >= 10 {
score += 1
counter = 0
} else {
counter += 1
}
}
}

Touch is not being detected in Swift

I have been developing a simple game in Swift in order to increase my exposure towards the language syntax and concepts. I am currently facing a problem in which the touches are not detected in the game application. The application uses action for a key method to start the game but unfortunately, when I tap on the screen the SKSpriteNodes are not spawning from the top of the screen (at-least visually). I have entered a few print states to see if the code has reached certain methods but it looks like the cause of the problem is within the touch method as the print line is not executed at the touch method. Can someone help me identify what is my mistake and how to prevent this?
For reference purposes I have included the class:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var counter: Int = 0
private var level: Int = 0
private var debug: SKLabelNode?
// Here we set initial values of counter and level. Debug label is created here as well.
override func didMove(to view: SKView) {
counter = 0
level = 1
backgroundColor = SKColor.gray
debug = SKLabelNode(fontNamed: "ArialMT")
debug?.fontColor = SKColor.purple
debug?.fontSize = 30.0
debug?.position = CGPoint(x: frame.midX, y: frame.midY)
debug?.text = "Counter : [ \(counter) ], Level [ \(level) ]"
if let aDebug = debug {
addChild(aDebug)
}
print(action(forKey: "counting") == nil)
}
//Method to start a timer. SKAction is used here to track a time passed and to maintain the current level
func startTimer() {
print("TIMER STARTED...")
weak var weakSelf: GameScene? = self
//make a weak reference to scene to avoid retain cycle
let block = SKAction.run({
weakSelf?.counter = (weakSelf?.counter ?? 0) + 1
//Maintaining level
if (weakSelf?.counter ?? 0) < 5 {
//level 1
weakSelf?.level = 1
} else if (weakSelf?.counter ?? 0) >= 5 && (weakSelf?.counter ?? 0) < 10 {
//level 2
weakSelf?.level = 2
} else {
//level 3
weakSelf?.level = 3
}
weakSelf?.debug?.text = "Counter : [ \(Int(weakSelf?.counter ?? 0)) ], Level [ \(Int(weakSelf?.level ?? 0)) ]"
})
run(SKAction.repeatForever(SKAction.sequence([SKAction.wait(forDuration: 1), block])), withKey: "counting")
}
//Method for stopping the timer and reset everything to the default state.
func stopTimer() {
print("TIMER STOPPED.")
if action(forKey: "counting") != nil {
removeAction(forKey: "counting")
}
counter = Int(0.0)
level = 1
debug?.text = "Counter : [ \(counter) ], Level [ \(level) ]"
}
//Get current speed based on time passed (based on counter variable)
func getCurrentSpeed() -> CGFloat {
if counter < 5 {
//level 1
return 1.0
} else if counter >= 5 && counter < 10 {
//level 2
return 2.0
} else {
//level 3
return 3.0
}
}
//Method which stops generating stones, called in touchesBegan
func stopGeneratingStones() {
print("STOPPED GENERATING STONES...")
if action(forKey: "spawning") != nil {
removeAction(forKey: "spawning")
}
}
func randomFloatBetween(_ smallNumber: CGFloat, and bigNumber: CGFloat) -> CGFloat {
let diff: CGFloat = bigNumber - smallNumber
//return (CGFloat(arc4random_uniform(UInt32(CGFloat(RAND_MAX) + 1 / CGFloat(RAND_MAX) * diff )))) + smallNumber
return CGFloat(arc4random() % (UInt32(RAND_MAX) + 1)) / CGFloat(RAND_MAX) * diff + smallNumber
}
//Method for generating stones, you run this method when you want to start spawning nodes (eg. didMoveToView or when some button is clicked)
func generateStones() {
print("GENERATING STONES...")
let delay = SKAction.wait(forDuration: 2, withRange: 0.5)
//randomizing delay time
weak var weakSelf: GameScene? = self
//make a weak reference to scene to avoid retain cycle
let block = SKAction.run({
let stone: SKSpriteNode? = weakSelf?.spawnStone(withSpeed: weakSelf?.getCurrentSpeed() ?? 0.0)
stone?.zPosition = 20
if let aStone = stone {
weakSelf?.addChild(aStone)
}
})
run(SKAction.repeatForever(SKAction.sequence([delay, block])), withKey: "spawning")
}
//Returns stone with moving action added. Inside, you set standard things, like size, texture, physics body, name and position of a stone
func spawnStone(withSpeed stoneSpeed: CGFloat) -> SKSpriteNode? {
print("SPAWNNING STONES...")
let stoneSize = CGSize(width: 30, height: 30) //size of shape.
//you can randomize size here
let stonePosition = CGPoint(x: randomFloatBetween(0.0, and: frame.size.width), y: frame.maxY) //initial position
//you can randomize position here
let stone = SKSpriteNode(color: SKColor.green, size: stoneSize) //setting size and color.
stone.name = "stone" //named shape so we can check collision.
//this helps if you want to enumerate all stones by name later on in your game
stone.position = stonePosition //set position.
let move = SKAction.moveBy(x: 0, y: -200, duration: 3.25)
//one way to change speed
move.speed = stoneSpeed
let moveAndRemove = SKAction.sequence([move, SKAction.removeFromParent()])
stone.run(moveAndRemove, withKey: "moving")
//access this key if you want to stop movement
return stone
}
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
/* Called when a touch begins */
//just a simple way to start and stop a game
/**
TOUCH METHOD NOT WORKING.
*/
if action(forKey: "counting") == nil {
print("HERE")
startTimer()
generateStones()
} else {
print("OR HERE")
stopTimer()
stopGeneratingStones()
}
}
}
I have fixed the problem to my own question after some debugging. I have realised that the touchesBegan method does not use override the super, hence the application is mistaking it for a local method. To overcome this problem simply adding the override will fix problem.
change this:
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
/* Called when a touch begins */
//just a simple way to start and stop a game
/**
TOUCH METHOD NOT WORKING.
*/
if action(forKey: "counting") == nil {
print("HERE")
startTimer()
generateStones()
} else {
print("OR HERE")
stopTimer()
stopGeneratingStones()
}
}
to this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
/* Called when a touch begins */
//just a simple way to start and stop a game
/**
TOUCH METHOD NOT WORKING.
*/
if action(forKey: "counting") == nil {
print("HERE")
startTimer()
generateStones()
} else {
print("OR HERE")
stopTimer()
stopGeneratingStones()
}
}

Fade in when hidden

I'm trying to fade in an object into my game when I first touch the screen, but I think that because it was hidden before (when game was launched), it won't fade in, but only show without any animation.
Do you have any suggestions?
This is an example code:
import SpriteKit
class GameScene: SKScene {
var myLabel = SKLabelNode()
var gameStarted = Bool()
func setupMyLabel(){
myLabel = SKLabelNode(fontNamed:"Chalkduster")
myLabel.text = "Hello, World!"
myLabel.fontSize = 35
myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
}
override func didMoveToView(view: SKView) {
/* Setup your scene here */
setupMyLabel()
self.addChild(myLabel)
myLabel.hidden = true
gameStarted = false
}
func startGame(){
myLabel.hidden = false
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
if gameStarted == false{
gameStarted = true
startGame()
self.myLabel.runAction(SKAction.fadeInWithDuration(2.0))
}
else{
//do nothing
}
}
}
According to Apple's documentation on fadeInWithDuration it states that:
When the action executes, the node’s alpha property animates from its
current value to 1.0.
So you're right in thinking it's because your node is hidden when it starts. =)
One possible solution would be to instead of setting the node's hidden property to true, instead set it's alpha value to 0. Or you could even create your own method to perform that includes the runAction method that would set the alpha to 0, un-hide the node, and then call SKAction.fadeInWithDuration similar to something below (please forgive any syntax errors, this is free-hand pseudo code)...
startGame()
self.fadeIn(self.myLabel, duration: 2.0)
...
func fadeIn() {
self.myLabel.alpha = 0.0
self.myLabel.hidden = false
self.myLabel.runAction(SKAction.fadeInWithDuration(2.0))
}

How can I stop the animation to overlap while moving my character

I have 2 different textures for my character that overlap/are displayed too fast
while moving the character. How can I set a duration for the animation, so the textures always switch at the same speed while moving the character?
This is my code:
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let animatePlayerStart = SKAction.setTexture(SKTexture(imageNamed: "Player\(i).png"))
// Determine speed for character movement
var minDuration:CGFloat = 0.7;
var maxDuration:CGFloat = 1.8;
var rangeDuration:CGFloat = maxDuration - minDuration;
var actualDuration:NSTimeInterval = NSTimeInterval((CGFloat(arc4random())%rangeDuration) + minDuration)
let move = SKAction.moveTo(location, duration:actualDuration)
player.runAction(SKAction.sequence([animatePlayerStart, move]))
// i determines which texture is going to be displayed
if(self.i == 2) {
self.i = 1
}
else{
self.i++
}
}
}
You are changing texture in touchesMoved which is called fast, thus the effect you are currently getting. To change textures after pre-defined period of time you can use this method:
+ animateWithTextures:timePerFrame:
import SpriteKit
class GameScene: SKScene {
let hero = SKSpriteNode(imageNamed: "heroState_A")
let textureA = SKTexture(imageNamed: "heroState_A")
let textureB = SKTexture(imageNamed: "heroState_B")
override func didMoveToView(view: SKView) {
/* Setup your scene here */
//Because hero is already initialized with textureA, start from textureB
let animation = SKAction.animateWithTextures([textureB,textureA], timePerFrame:0.5)
hero.position = CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidY(frame))
addChild(hero)
//Start animation
hero.runAction(SKAction.repeatActionForever(animation),withKey:"heroAnimation")
//Removing heroAnimation
//You can stop this animation by hero.removeAllActions, but if you run animation with key, you can remove just that particular action, which gives you more control
let stop = SKAction.runBlock({
if(self.hero.actionForKey("heroAnimation") != nil){
self.hero.removeActionForKey("heroAnimation")
}
})
//Just an example, in real app you will do this at certain events (eg. when player stops his movement)
runAction(SKAction.sequence([SKAction.waitForDuration(5),stop]))
}
}

How to make buttons and labels appear and disappear

What I want to know:
I want to know that how to make buttons/labels appear and disappear. When my character collides with an object the buttons/labels will show up over the view and the game-view wont be working any more, only the buttons/labels that appeared can be interacted with.
What I have tried:
I have tried .hidden = false and .hidden = true but it didn't work but maybe I was not using it correctly.
CODE: I have delete unnecessary code!
import Foundation
import AVFoundation
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var movingGround: PPMovingGround!
var square1: PPSquare1!
var square2: PPSquare2!
var wallGen: PPWallGen!
var isStarted = false
var isGameOver = false
override func didMoveToView(view: SKView) {
addMovingGround()
addSquare1()
addWallGen()
start()
}
func addSquare1() {
square1 = PPSquare1()
square1.position = CGPointMake(70, movingGround.position.y + movingGround.frame.size.height/2 + square1.frame.size.height/2)
square1.zPosition = 1
playerNode.addChild(square1)
}
func addWallGen() {
wallGen = PPWallGen(color: UIColor.clearColor(), size: view!.frame.size)
wallGen.position = view!.center
addChild(wallGen)
}
func start() {
isStarted = true
//square2.stop()
square1.stop()
movingGround.start()
wallGen.startGenWallsEvery(1)
}
// MARK - Game Lifecycle
func gameOver() {
isGameOver = true
// everything stops
//square2.fall()
square1.fall()
wallGen.stopWalls()
diamondGen.stopDiamonds()
movingGround.stop()
square1.stop()
//square2.stop()
// create game over label
let gameOverLabel = SKLabelNode(text: "Game Over!")
gameOverLabel.fontColor = UIColor.whiteColor()
gameOverLabel.fontName = "Helvetica"
gameOverLabel.position.x = view!.center.x
gameOverLabel.position.y = view!.center.y + 80
gameOverLabel.fontSize = 22.0
addChild(gameOverLabel)
func restart() {
let newScence = GameScene(size: view!.bounds.size)
newScence.scaleMode = .AspectFill
view!.presentScene(newScence)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if isGameOver {
restart()
} else {
square1.flip()
}
}
override func update(currentTime: CFTimeInterval) {
// MARK: - SKPhysicsContactDelegate
func didBeginContact(contact: SKPhysicsContact) {
if !isGameOver {
gameOver()
} else {
println("error, not game over!"
}
Without seeing your code, this is a little hard to determine, but I would suggest the following:
Be sure you have connected the buttons to an Outlet variable. This is critical. Without connecting them, you can use the hidden boolean, but it would not have an effect on an actual button.
Be sure you are not somehow undoing your own changes. For example, further down in the code, you might have something which is setting hidden to false even after you set it to true, and so on.
In some cases, you might want to set your outlet variable as strong instead of weak. This may retain changes that are being lost with a view switch.
You can also use "alpha" such as:
myButton.alpha = 0
as an alternate way of controlling visibility. 0 would set the alpha to none (which would make the button invisible) and 1 would set the alpha to full (which would make the button visible again.)
Right after you set hidden (or alpha) put in:
println("i hid the button!")
just to be sure the code you think you are executing really is being executed. Sometimes code we think is not working is actually not even being called.
Please provide more info and I will gladly work to get this solved for you.

Resources