Spritekit - Creating a Timer

SpriteKit - Creating a timer

In Sprite Kit do not use NSTimer, performSelector:afterDelay: or Grand Central Dispatch (GCD, ie any dispatch_... method) because these timing methods ignore a node's, scene's or the view's paused state. Moreover you do not know at which point in the game loop they are executed which can cause a variety of issues depending on what your code actually does.

The only two sanctioned ways to perform something time-based in Sprite Kit is to either use the SKScene update: method and using the passed-in currentTime parameter to keep track of time.

Or more commonly you would just use an action sequence that starts with a wait action:

id wait = [SKAction waitForDuration:2.5];
id run = [SKAction runBlock:^{
// your code here ...
}];
[node runAction:[SKAction sequence:@[wait, run]]];

And to run the code repeatedly:

[node runAction:[SKAction repeatActionForever:[SKAction sequence:@[wait, run]]]];

Alternatively you can also use performSelector:onTarget: instead of runBlock: or perhaps use a customActionWithDuration:actionBlock: if you need to mimick the SKScene update: method and don't know how to forward it to the node or where forwarding would be inconvenient.

See SKAction reference for details.


UPDATE: Code examples using Swift

Swift 5

 run(SKAction.repeatForever(SKAction.sequence([
SKAction.run( /*code block or a func name to call*/ ),
SKAction.wait(forDuration: 2.5)
])))

Swift 3

let wait = SKAction.wait(forDuration:2.5)
let action = SKAction.run {
// your code here ...
}
run(SKAction.sequence([wait,action]))

Swift 2

let wait = SKAction.waitForDuration(2.5)
let run = SKAction.runBlock {
// your code here ...
}
runAction(SKAction.sequence([wait, run]))

And to run the code repeatedly:

runAction(SKAction.repeatActionForever(SKAction.sequence([wait, run])))

How to implement a SpriteKit timer?

I would stick to SKActions for these kind of tasks in SpriteKit due to fact that NSTimer is not affected by scene's, or view's paused state, so it might lead you into troubles. Or at least, it will require from you to implement a pause feature in order to pause your timers in certain situations, like when user pause the scene, or receive a phone call etc. Read more here about SKAction vs NSTimer vs GCD for time related actions in SpriteKit.

import SpriteKit

class GameScene: SKScene {

var levelTimerLabel = SKLabelNode(fontNamed: "ArialMT")

//Immediately after leveTimerValue variable is set, update label's text
var levelTimerValue: Int = 500 {
didSet {
levelTimerLabel.text = "Time left: \(levelTimerValue)"
}
}

override func didMoveToView(view: SKView) {



levelTimerLabel.fontColor = SKColor.blackColor()
levelTimerLabel.fontSize = 40
levelTimerLabel.position = CGPoint(x: size.width/2, y: size.height/2 + 350)
levelTimerLabel.text = "Time left: \(levelTimerValue)"
addChild(levelTimerLabel)

let wait = SKAction.waitForDuration(0.5) //change countdown speed here
let block = SKAction.runBlock({
[unowned self] in

if self.levelTimerValue > 0{
self.levelTimerValue--
}else{
self.removeActionForKey("countdown")
}
})
let sequence = SKAction.sequence([wait,block])

runAction(SKAction.repeatActionForever(sequence), withKey: "countdown")
}

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

//Stop the countdown action

if actionForKey("countdown") != nil {removeActionForKey("countdown")}
}
}

SpriteKit - How to transition to a new scene with a timer

You can create a Scheduled Timer and configure a function to call your method that's creates and present the new scene.

Example:

class LoadingScene: SKScene {
var timer = Timer()

override func didMove(to view: SKView) {
let background = SKSpriteNode(imageNamed: "")
background.position = CGPoint (x: self.size.width / 2, y: self.size.height / 2)
background.zPosition = -1
self.addChild(background)
//Create a Scheduled timer thats will fire a function after the timeInterval
timer = Timer.scheduledTimer(timeInterval: 5.0,
target: self,
selector: #selector(presentNewScene),
userInfo: nil, repeats: false)
}

@objc func presentNewScene() {
//Configure the new scene to be presented and then present.
let newScene = SKScene(size: .zero)
view?.presentScene(newScene)
}

deinit {
//Stops the timer.
timer.invalidate()
}
}

Timers in Sprite Kit

To achieve functionality similar to cocos scheduler you can use SKAction.

For example for the to achieve something like this

[self schedule:@selector(fireMethod:) interval:0.5];

using SKAction You would write this

SKAction *wait = [SKAction waitForDuration:0.5];
SKAction *performSelector = [SKAction performSelector:@selector(fireMethod:) onTarget:self];
SKAction *sequence = [SKAction sequence:@[performSelector, wait]];
SKAction *repeat = [SKAction repeatActionForever:sequence];
[self runAction:repeat];

It isn't best looking, and lacks some flexibility of CCScheduler, but it will pause upon backgrounding, pausing scene/view etc. + it is like playing with LEGOs :)

Changing the time interval of a Timer in swift spritekit

You should actually avoid using Timer, Sprite kit has its own time functionality, and Timer does not work well with it and is a real pain to manage.

Instead, use SKAction's to wait and fire:

let spawnNode = SKNode()
override func didMove(to view: SKView) {
let wait = SKAction.wait(forDuration:frequency)
let spawn = SKAction.run(spawnFallingObjects)
spawnNode.run(SKAction.repeatForever(SKAction.sequence([wait,spawn])))
addChild(spawnNode)
}

Then to make it faster, just do:

switch scoreLabel.number {

case 0...50:
spawnNode.speed = 1
print("speed has changed: \(spawnNode.speed)")

case 51...100:
spawnNode.speed = 1.5
print("speed has changed: \(spawnNode.speed)")

case 101...200000:
spawnNode.speed = 2
print("speed has changed: \(spawnNode.speed)")

default:
return
}


Related Topics



Leave a reply



Submit