Swift Timer SK.wait - ios

I have balls spawning every 3 seconds and I want the balls to spawn faster if the score increases. The problem is my spawnrate stays the same 4 seconds and does not change when my score increases. Thanks for the helps m8s
var spawnRate : TimeInterval = 4
var wait = SKAction .wait(forDuration: spawnRate, withRange: 0.2)
if score >= 6 {
spawnRate = 3
wait = SKAction .wait(forDuration: spawnRate, withRange: 0.2)
}
let spawning = SKAction.sequence([wait,spawn])

I would rather refactor the code, and double check that score is really increased , plus cancel previous actions, in case they have not finished after previous launch. My assumption that you produce several actions and they are "accumulated" in the future.
var spawnRate : TimeInterval
var wait:SKAction!
if score >= 6 {
spawnRate = 3
wait = SKAction .wait(forDuration: spawnRate, withRange: 0.2)
}
else {
spawnRate = 4
wait = SKAction .wait(forDuration: spawnRate, withRange: 0.2)
}
let spawningKey ="spawningKey"
self.spinnyNode?.removeAction(forKey: waitKey)
let spawning = SKAction.sequence([wait,spawn])
self.spinnyNode?.run(spawning, withKey: spawningKey)

Related

Swift Timer() trouble

I've done a simple timer in Swift. All is well apart from when the seconds reach 59 seconds. Instead of going back to zero they just carry on going. Would someone would be able to point out where I'm going wrong and why this is happening?
#IBAction func startButtonDidTouch(_ sender: Any) {
if !timerIsRunning{
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.updateTimer), userInfo: nil, repeats: true)
timerIsRunning = true
}
}
#objc func updateTimer() {
totalSeconds += 0.01
let totalSecondsTimes100: Int = Int(totalSeconds*100)
let minutes = Int(totalSeconds/60)
let timerChoice = Double(minutes)
let minStr = (minutes == 0) ? "00" : "0\(minutes)"
let secStr = (totalSeconds < 9) ? "0\(Float(totalSecondsTimes100)/100)" : "\(Float(totalSecondsTimes100)/100)"
switch Int(timerChoice) {
case Int(timerCountdownLabel.text!)!:
timerLabel.text = "\(minStr):\(secStr)"
audioPlayer.play()
timer.invalidate()
timerIsRunning = false
default:
timerLabel.text = "\(minStr):\(secStr)"
}
}
You should calculate the seconds as:
let seconds = totalSeconds % 60
and then use seconds in your calculation of secStr instead of using totalSeconds.
There are better ways to write your code:
#objc func updateTimer() {
totalSeconds += 0.01
let minutes = Int(totalSeconds) / 60
let seconds = totalSeconds.remainder(dividingBy: 60)
let timeStr = String(format: "%02d:%06.3f", minutes, seconds)
timerLabel.text = timeStr
if Int(timerCountdonwLabel.text!)! == minutes {
audioPlayer.play()
timer.invalidate()
timerIsRunning = false
}
}
And you really shouldn't keep track of time simply by adding 0.01 to totalSeconds. A Timer is not accurate. Your clock will drift over time. It's best to save a timestamp (Date()) when you start the timer and get the current timestamp (Date()) inside updateTimer and get the difference between the two.
Here is a timer function that outputs format minutes:seconds:milliseconds, compare with your code and you'll find what's wrong with your code.
private weak var timer: Timer?
private var startTime: Double = 0
private var elapsed: Double = 0
private var time: Double = 0
private func startTimer(){
startTime = Date().timeIntervalSinceReferenceDate - elapsed
timer = Timer.scheduledTimer(timeInterval: (0.01), target: self, selector: #selector(updateTimeLabel), userInfo: nil, repeats: true)
}
private func stopTimer(){
elapsed = Date().timeIntervalSinceReferenceDate - startTime
timer?.invalidate()
}
#objc func updateTimeLabel(){
time = Date().timeIntervalSinceReferenceDate - startTime
let minutes = UInt8(time / 60.0)
let timeNoMin = time - (TimeInterval(minutes) * 60)
let seconds = UInt8(timeNoMin)
let timeNoSec = timeNoMin - (TimeInterval(seconds))
let milliseconds = UInt16(timeNoSec * 100)
let strMinutes = String(minutes)
var strSeconds = ""
if strMinutes == "0" {
strSeconds = String(seconds)
}
else {
strSeconds = String(format: "%02d", seconds)
}
let strMilliseconds = String(format: "%02d"), milliseconds)
if strMinutes != "0" {
timerLabel.text = "\(strMinutes):\(strSeconds).\(strMilliseconds)"
}
else {
timerLabel.text = "\(strSeconds).\(strMilliseconds)"
}
}
To get minutes and seconds from a floating point total number of seconds elapsed, elapsed you can:
To get minutes, divide by 60.0 and truncate to the nearest integer:
let minutes = Int(elapsed / 60)
To get seconds, get the remainder, either via:
let seconds = elapsed - Double(minutes) * 60
Or
let seconds = elapsed.truncatingRemainder(dividingBy: 60)
A couple of other observations:
There's no point in running a timer every 0.01 seconds when the screen refresh rate is usually capped at 60 frames per second. If you want to update it with the greatest frequency, use a CADisplayLink which is timed not only for maximum screen refresh rate, but also fires optimally to allow the update to happen before the next frame is to be rendered.
You should not use timer to increment the elapsed time by 0.01 (or any fixed interval) because you have no assurances that it will actually fire with that frequency. If something, for example, momentarily blocks the main thread by 200 milliseconds, you don't want this to affect your calculation of the amount of time that has elapsed.
Instead, save the start time when the timer starts, and every time the timer fires recalculate the elapsed time and format the results accordingly.
To complicate this further, you should not even be comparing Date() instances (or CFAbsoluteTimeGetCurrent() values) because, as the documentation warns us:
Repeated calls to this function do not guarantee monotonically increasing results. The system time may decrease due to synchronization with external time references or due to an explicit user change of the clock.
Instead, you should use a mach_absolute_time based calculation (such as returned by CACurrentMediaTime()), for which repeated calls are assured to return accurately elapsed time calculations.
The only time you should use Date() or CFAbsoluteTimeGetCurrent() if your app is saving the start time in persistent storage, to be retrieved later when the app is restarted (possibly after a device reboot) to render the effect of the elapsed time between starts of an app. But this is a pretty narrow edge case.
Anyway, this yields:
var start: CFTimeInterval?
weak var displayLink: CADisplayLink?
func startTimer() {
self.displayLink?.invalidate() // just in case timer had already been started
start = CACurrentMediaTime()
let displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
displayLink.preferredFramesPerSecond = 100 // in case you're using a device that can render more than 60 fps
displayLink.add(to: .main, forMode: .commonModes)
self.displayLink = displayLink
}
#objc func handleDisplayLink(_ displayLink: CADisplayLink) {
let elapsed = CACurrentMediaTime() - start!
let minutes = Int(elapsed / 60)
let seconds = elapsed - Double(minutes) * 60
let string = String(format: "%02d:%05.2f", minutes, seconds)
label.text = string
}
func stopTimer() {
displayLink?.invalidate()
}

timeInterval Variable won't work

I'm trying to change the timeInterval in a scheduledTimer. I'm trying to do this by changing a variable to the interval and than setting the timeInterval to this variable. I don't get any errors but the timeInterval won't change. Can someone help me?
var enemyTimer = Timer()
var playTime = 0
var enemySpawnTime: Double = 3
enemyTimer = Timer.scheduledTimer(timeInterval: Double(enemySpawnTime), target: self, selector: #selector(GameScene.enemySpawn), userInfo: nil, repeats: true)
playTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(GameScene.ingameTimer), userInfo: nil, repeats: true)
func enemySpawn() {
let enemy = SKSpriteNode(imageNamed: "Enemy")
let minValue = self.size.width / 8
let maxValue = self.size.width - 20
let spawnPoint = UInt32(maxValue - minValue)
enemy.position = CGPoint(x: CGFloat(arc4random_uniform(spawnPoint)), y: self.size.height)
let action = SKAction.moveTo(y: -70, duration: 5)
enemy.run(action)
self.addChild(enemy)
}
func ingameTimer() {
playTime += 1
if(playTime >= 10 && playTime < 30){
enemySpawnTime = 2
print(enemySpawnTime)
}else
if(playTime >= 30 && playTime < 60){
enemySpawnTime = 1
print(enemySpawnTime)
}else
if(playTime >= 60 && playTime < 120){
enemySpawnTime = 0.75
print(enemySpawnTime)
}else
if(playTime >= 120 && playTime < 180){
enemySpawnTime = 0.5
print(enemySpawnTime)
}else
if(playTime >= 180 && playTime < 240){
enemySpawnTime = 0.25
print(enemySpawnTime)
}
}
I hope someone can help me!
Thanks!
The reason why your code doesn't work is because the Timer object doesn't know that its interval needs to be in sync with your enemySpawnTime. The solution is simple, just recreate the timer when you change the enemy spawn time.
But...
You should NEVER use Timer (NSTimer prior to Swift 3) or GCD to delay stuff when you're using SpriteKit. See this for more details.
The correct way to do this is to create a sequence of SKActions.
Assuming self is a subclass of SKScene, you can do this:
override func didMove(to view: SKView) {
let waitAction = SKAction.wait(forDuration: enemySpawnTime)
let enemySpawnAction = SKAction.run { self.enemySpawn() }
let sequence = SKAction.sequence([waitAction, enemySpawnAction])
somePlaceholderNode.run(SKAction.repeatForever(sequence))
}
where somePlaceholderNode is just a node that does nothing but run the action. I'll explain this later.
And you should do this for the other timer as well.
Now whenever you change the timer interval, also do this:
somePlaceholderNode.removeAllActions()
let waitAction = SKAction.wait(forDuration: enemySpawnTime)
let enemySpawnAction = SKAction.run { self.enemySpawn() }
let sequence = SKAction.sequence([waitAction, enemySpawnAction])
somePlaceholderNode.run(SKAction.repeatForever(sequence))
Here I first remove the action that the node was running, and tell it to run almost the same action, but with a different time interval. You can add this block of code to the didSet block of enemySpawnTime:
var enemySpawnTime: Double = 3 {
didSet {
let waitAction = SKAction.wait(forDuration: enemySpawnTime)
let enemySpawnAction = SKAction.run { self.enemySpawn() }
let sequence = SKAction.sequence([waitAction, enemySpawnAction])
somePlaceholderNode.run(SKAction.repeatForever(sequence))
}
}
Now your code should work!
The reason why we want a placeholder node here is because when we remove the action by calling removeAllActions, we don't want to remove all the actions that is running.

AVPlayer seektotime with Pangesturerecognizer

I'm trying to use seektotime with Pangesture recognizer.But its not seeking as expected.
let totalTime = self.avPlayer.currentItem!.duration
print("time: \(CMTimeGetSeconds(totalTime))")
self.avPlayer.pause()
let touchDelta = swipeGesture.translationInView(self.view).x / CGFloat(CMTimeGetSeconds(totalTime))
let currentTime = CMTimeGetSeconds((avPlayer.currentItem?.currentTime())!) + Float64(touchDelta)
print(currentTime)
if currentTime >= 0 && currentTime <= CMTimeGetSeconds(totalTime) {
let newTime = CMTimeMakeWithSeconds(currentTime, Int32(NSEC_PER_SEC))
print(newTime)
self.avPlayer.seekToTime(newTime)
}
What I'm doing wrong in here ?
Think about what's happening in this line here:
let touchDelta = swipeGesture.translationInView(self.view).x / CGFloat(CMTimeGetSeconds(totalTime))
You're dividing pixels (the translation in just the x-axis) by time. This really isn't a "delta" or absolute difference. It's a ratio of sorts. But it's not a ratio that has any meaning. Then you're getting your new currentTime by just added this ratio to the previous currentTime, so you're adding pixels per seconds to pixels, which doesn't give a logical or useful number.
What we need to do is take the x-axis translation from the gesture and apply a scale (which is a ratio) to it in order to get a useful number of seconds to advance/rewind the AVPlayer. The x-axis translation is in pixels so we'll need a scale that describes seconds per pixels and multiple the two in order to get our number of seconds. The proper scale is the ratio between the total number of seconds in the video and the total number of pixels that the user can move through in the gesture. Multiplying pixels times (seconds divided by pixels) gives us a number in seconds. In pseudocode:
scale = totalSeconds / totalPixels
timeDelta = translation * scale
currentTime = oldTime + timeDelta
So I would rewrite your code like this:
let totalTime = self.avPlayer.currentItem!.duration
print("time: \(CMTimeGetSeconds(totalTime))")
self.avPlayer.pause()
// BEGIN NEW CODE
let touchDelta = swipeGesture.translationInView(self.view).x
let scale = CGFloat(CMTimeGetSeconds(totalTime)) / self.view.bounds.width
let timeDelta = touchDelta * scale
let currentTime = CMTimeGetSeconds((avPlayer.currentItem?.currentTime())!) + Float64(timeDelta)
// END NEW CODE
print(currentTime)
if currentTime >= 0 && currentTime <= CMTimeGetSeconds(totalTime) {
let newTime = CMTimeMakeWithSeconds(currentTime, Int32(NSEC_PER_SEC))
print(newTime)
self.avPlayer.seekToTime(newTime)
}
I have same issue, then i create the UISlider and set the action method is given below,
declare AVPlayer is var playerVal = AVPlayer()
#IBAction func sliderAction(sender: UISlider) {
playerVal.pause()
displayLink.invalidate()
let newTime:CMTime = CMTimeMakeWithSeconds(Double(self.getAudioDuration() as! NSNumber) * Double(sender.value), playerVal.currentTime().timescale)
playerVal.seekToTime(newTime)
updateTime()
playerVal.play()
deepLink()
}
And another method is,
func updateTime() {
let currentTime = Float(CMTimeGetSeconds(playerItem1.currentTime()))
let minutes = currentTime/60
let seconds = currentTime - minutes * 60
let maxTime = Float(self.getAudioDuration() as! NSNumber)
let maxminutes = maxTime / 60
let maxseconds = maxTime - maxminutes * 60
startValue.text = NSString(format: "%.2f:%.2f", minutes,seconds) as String
stopValue.text = NSString(format: "%.2f:%.2f", maxminutes,maxseconds) as String
}
I have used CADisplayLink and declare var displayLink = CADisplayLink(), its used continue(automatically) playing audios. code is
func deepLink() {
displayLink = CADisplayLink(target: self, selector: ("updateSliderProgress"))
displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
}
func updateSliderProgress(){
let progress = Float(CMTimeGetSeconds(playerVal.currentTime())) / Float(self.getAudioDuration() as! NSNumber)
sliderView.setValue(Float(progress), animated: false)
}
if you see this above answer, you have get idea, hope its helpful

How do I make my enemySprite respawn as new bad guy once its been killed?

I am having trouble with my game, I've managed to have my enemySprites all shoot at the hero in a synchronized matter and I've gotten them to play the "killed" animation once they've been hit. Although I've run into a rather small matter which I was really hoping you guys could help me with. The problem I have is that when my badGuy is killed they move off the screen and I don't know how I can program it so that a 'new' badGuy appears forever until the Hero sprite is killed.
This is my function to spawn my enemy:
func spawnEnemy(targetSprite: SKNode) -> SKSpriteNode {
if !gamePaused{
// create a new enemy sprite
let main = GameScene()
newEnemy = SKSpriteNode(imageNamed:"BNG1_1.png")
enemySprite.append(newEnemy)//ADD TO THE LIBRARY OF BADGUYS
newEnemy.xScale = 1.2
newEnemy.yScale = 0.6
newEnemy.physicsBody?.dynamic = true
newEnemy.physicsBody = SKPhysicsBody(texture: newEnemy.texture, size: newEnemy.size)
newEnemy.physicsBody?.affectedByGravity = false
newEnemy.physicsBody?.categoryBitMask = BodyType.badguyCollision.rawValue
newEnemy.physicsBody?.contactTestBitMask = BodyType.beamCollison.rawValue
newEnemy.physicsBody?.collisionBitMask = 0x0
let muv : UInt32 = (200 + (arc4random()%500))
let actualDuration = NSTimeInterval(random(min: CGFloat(3.0), max: CGFloat(4.0)))
let randomNum = CGPoint(x:Int (muv), y:Int (arc4random()%500))
// Create the actions
var actionMove = SKAction.moveTo(randomNum, duration: NSTimeInterval(actualDuration))
newEnemy.runAction(SKAction.sequence([actionMove]))
// position new sprite at a random position on the screen
var posX = arc4random_uniform(UInt32(sizeRect.size.width))
var posY = arc4random_uniform(UInt32(sizeRect.size.height))
newEnemy.position = CGPoint(x: screenSize.width*2 + newEnemy.size.width, y: random(min: newEnemy.size.height, max: screenSize.height - newEnemy.size.height))
let atlas = SKTextureAtlas(named: "BG1.atlas")
let anime = SKAction.animateWithTextures([atlas.textureNamed("BG1_1.png"), atlas.textureNamed("BG1_2.png"),
atlas.textureNamed("BG1_3.png"),
atlas.textureNamed("BG1_2.png"),
atlas.textureNamed("BG1_1.png")], timePerFrame: 0.1)
dinoRun = SKAction.repeatActionForever(anime)
newEnemy.runAction(dinoRun)
}
return newEnemy
}
And this is my function for once they are hit (this function is called when the collisionTest between my hero's laser and the badguy is recognized):
func deadBadGuy(){
//animation
var dinoRun:SKAction
var newdes = CGPoint(x: Int(arc4random()%500), y:0)
var actionMoov = SKAction.moveTo(newdes, duration: 3)
var goaway = SKAction.removeFromParent()
let aTlas = SKTextureAtlas(named: "dedBG.atlas")
let anime = SKAction.animateWithTextures([aTlas.textureNamed("deadBG1.png"), aTlas.textureNamed("deadBG2.png"),
aTlas.textureNamed("deadBG3.png"),
aTlas.textureNamed("deadBG2.png"),
aTlas.textureNamed("deadBG1.png")], timePerFrame: 0.1)
dinoRun = SKAction.repeatActionForever(anime)
newEnemy.runAction(dinoRun)
newEnemy.runAction(SKAction.sequence([actionMoov,goaway]))
score++
self.scoreNode.text = String(score)
enemySprites.newPlace(neewEnemy)
dead = true //i created this Boolean because the sprite keeps shooting even if its dead
}
This is the way I called for the collisionTest in my program in case you need it for more information:
func didBeginContact(contact:SKPhysicsContact){
if !gamePaused {
let firstNode = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch(firstNode){
case BodyType.beamCollison.rawValue | BodyType.badguyCollision.rawValue:
deadBadGuy()
//enemySprites.spawnEnemy(sprite)
default:
print("hit")
}
Note: I tried having the func spawnEnemy being called during the collisionTest but that results in the BadGuy staying off screen shooting at my hero.
Update : I found out how to add a new enemy sprite once the other is dead all i had to do was
newEnemy.runAction(SKAction.sequence([actionMoov,goaway]), completion: {
self.addChild(self.enemySprites.spawnEnemy(sprite))
})
in the deadBadGuy function(the spawnEnemy function is in another class which I named enemySprites as a variable). However now I've run into a new issue and that is that it adds 10 enemySprites instead of one. How can I change that?
Update 2 : I figured out that issue too, I just needed to remove to dead Boolean method in the deadBadGuy function.
I found my answer and all I had to do was add this line to my deadBadGuy function and remove the dead boolean methods
newEnemy.runAction(SKAction.sequence([actionMoov,goaway]), completion: {
self.addChild(self.enemySprites.spawnEnemy(sprite))
})

SpriteKit - Creating a timer

How can I create a timer that fires every two seconds that will increment the score by one on a HUD I have on my screen? This is the code I have for the HUD:
#implementation MyScene
{
int counter;
BOOL updateLabel;
SKLabelNode *counterLabel;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
counter = 0;
updateLabel = false;
counterLabel = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
counterLabel.name = #"myCounterLabel";
counterLabel.text = #"0";
counterLabel.fontSize = 20;
counterLabel.fontColor = [SKColor yellowColor];
counterLabel.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;
counterLabel.verticalAlignmentMode = SKLabelVerticalAlignmentModeBottom;
counterLabel.position = CGPointMake(50,50); // change x,y to location you want
counterLabel.zPosition = 900;
[self addChild: counterLabel];
}
}
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])))
In Swift usable:
var timescore = Int()
var actionwait = SKAction.waitForDuration(0.5)
var timesecond = Int()
var actionrun = SKAction.runBlock({
timescore++
timesecond++
if timesecond == 60 {timesecond = 0}
scoreLabel.text = "Score Time: \(timescore/60):\(timesecond)"
})
scoreLabel.runAction(SKAction.repeatActionForever(SKAction.sequence([actionwait,actionrun])))
I've taken the swift example above and added in leading zeros for the clock.
func updateClock() {
var leadingZero = ""
var leadingZeroMin = ""
var timeMin = Int()
var actionwait = SKAction.waitForDuration(1.0)
var timesecond = Int()
var actionrun = SKAction.runBlock({
timeMin++
timesecond++
if timesecond == 60 {timesecond = 0}
if timeMin / 60 <= 9 { leadingZeroMin = "0" } else { leadingZeroMin = "" }
if timesecond <= 9 { leadingZero = "0" } else { leadingZero = "" }
self.flyTimeText.text = "Flight Time [ \(leadingZeroMin)\(timeMin/60) : \(leadingZero)\(timesecond) ]"
})
self.flyTimeText.runAction(SKAction.repeatActionForever(SKAction.sequence([actionwait,actionrun])))
}
Here's the full code to build a timer for SpriteKit with Xcode 9.3 and Swift 4.1
In our example the score label will be incrementd by 1 every 2 seconds.
Here's final result
Good, let's start!
1) The score label
First of all we need a label
class GameScene: SKScene {
private let label = SKLabelNode(text: "Score: 0")
}
2) The score label goes into the scene
class GameScene: SKScene {
private let label = SKLabelNode(text: "Score: 0")
override func didMove(to view: SKView) {
self.label.fontSize = 60
self.addChild(label)
}
}
Now the label is at the center of the screen. Let's run the project to see it.
Please note that at this point the label is not being updated!
3) A counter
We also want to build a counter property which will hold the current value displayed by the label. We also want the label to be updated as soon as the counter property is changed so...
class GameScene: SKScene {
private let label = SKLabelNode(text: "Score: 0")
private var counter = 0 {
didSet {
self.label.text = "Score: \(self.counter)"
}
}
override func didMove(to view: SKView) {
self.label.fontSize = 60
self.addChild(label)
// let's test it!
self.counter = 123
}
}
4) The actions
Finally we want to build an action that every 2 seconds will increment counter
class GameScene: SKScene {
private let label = SKLabelNode(text: "Score: 0")
private var counter = 0 {
didSet {
self.label.text = "Score: \(self.counter)"
}
}
override func didMove(to view: SKView) {
self.label.fontSize = 60
self.addChild(label)
// 1 wait action
let wait2Seconds = SKAction.wait(forDuration: 2)
// 2 increment action
let incrementCounter = SKAction.run { [weak self] in
self?.counter += 1
}
// 3. wait + increment
let sequence = SKAction.sequence([wait2Seconds, incrementCounter])
// 4. (wait + increment) forever
let repeatForever = SKAction.repeatForever(sequence)
// run it!
self.run(repeatForever)
}
}
The following code creates a new thread and waits 2 seconds before doing something on the main thread:
BOOL continueIncrementingScore = YES;
dispatch_async(dispatch_queue_create("timer", NULL);, ^{
while(continueIncrementingScore) {
[NSThread sleepForTimeInterval:2];
dispatch_async(dispatch_get_main_queue(), ^{
// this is performed on the main thread - increment score here
});
}
});
Whenever you want to stop it - just set continueIncrementingScore to NO

Resources