CD-3
CD-3

Reputation: 63

Creating a stopwatch in swift

I have tried searching other answers and I cannot find one that applies to my scenario. I am writing a game in swift and want to create a stopwatch that determines how the long the player is playing. When the user touches down, the stopwatch will start and when a certain action happens, then the timer stops and resets. I'd like to use minutes, seconds, milliseconds (i.e. 00:00.00).

Currently, the time function kind of works. It doesn't start at 0, it starts at the current seconds in time (I know thats when I start it, but I don't know how to start it at 0). It also only updates when I touch the screen, I need it to count from 00:00.00 and update on its own until the cancel action is fired.

Thank you for your time.

Here is what I have so far:

class GameScene: SKScene {

var activeTimer = SKLabelNode()
//var bestTime = SKLabelNode()

var startTime = TimeInterval()
//var currentTime = TimeInterval()
//var countingTime = TimeInterval()
var updateTimerAction = SKAction()


override func didMove(to view: SKView) {

    activeTimer = self.childNode(withName: "active_timer") as! SKLabelNode
    //bestTime = self.childNode(withName: "best_time") as! SKLabelNode

    startTime = TimeInterval(Calendar.current.component(.second, from:Date()))
    updateTimerAction = SKAction.sequence([SKAction.run(updateTimer), SKAction.wait(forDuration: 1.0)])
}


func startGame() {
    // Other code
    startGameTimer()
}

func resetGame() {
    // Other code
    stopGameTimer()
}

func startGameTimer() {
    run(SKAction.repeatForever(updateTimerAction), withKey: "timer")
}

func updateTimer() {
    activeTimer.text = stringFromTimeInterval(interval: startTime) as String
}

func stringFromTimeInterval(interval: TimeInterval) -> NSString {

    let ti = NSInteger(interval)

    let ms = Int((interval.truncatingRemainder(dividingBy: 1)) * 1000)

    let seconds = ti % 60
    let minutes = (ti / 60) % 60

    return NSString(format: "%0.2d:%0.2d.%0.2d",minutes,seconds,ms)
}


func stopGameTimer() {
    removeAction(forKey: "timer")
}

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

    startGame()

    for touch in touches {
        // other code
    }
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {

    for touch in touches {
        // other code
    }
}


override func update(_ currentTime: TimeInterval) {

    updateTimer()

    if <action>  {
        // stop timer and reset game
        resetGame()
    }
}

Upvotes: 2

Views: 10827

Answers (3)

CD-3
CD-3

Reputation: 63

Here is what I ended up doing. While El Tomato's answer appears to work, given the setup of my code I went a little different route. After talking with some other friends, I chose to user a timer due to the design of my application. If I happen to run into any problems using a Timer object I will update this question.

The code below will start on a touch, update in the format: "00:00.00" and stop when the action is fired. This time will pause until another touch and the timer will then start from zero.

class GameScene: SKScene {

var seconds = 0
var timer = Timer()
var timeStarted = Bool()
var activeTimer = SKLabelNode()
//var bestTime = SKLabelNode()

override func didMove(to view: SKView) {

    activeTimer = self.childNode(withName: "active_timer") as! SKLabelNode
    //bestTime = self.childNode(withName: "best_time") as! SKLabelNode
    timeStarted = false

}

func startGame() {

    startGameTimer()
    timeStarted = true
}

func resetGame() {

    timeStarted = false
    stopGameTimer()
    seconds = 0
}

func startGameTimer() {
    timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(updateTimer)), userInfo: nil, repeats: true)
}

func updateTimer() {
    seconds += 1
    activeTimer.text = timeString(time: TimeInterval(seconds))
}

func timeString(time:TimeInterval) -> String {
    let hours = Int(time) / 3600
    let minutes = Int(time) / 60 % 60
    let seconds = Int(time) % 60
    return String(format:"%02i:%02i.%02i", hours, minutes, seconds)
}


func stopGameTimer() {
    timer.invalidate()
    removeAction(forKey: "timer")
}

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

    if !timeStarted {
        startGame()
    }

    for touch in touches {
        // other code
    }
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {

    for touch in touches {
        // other code
    }
}


override func update(_ currentTime: TimeInterval) {

    if timeStarted {
        updateTimer()
    }

    if <action>  {
    // stop timer and reset game
    resetGame()
    }
}
}

Upvotes: 1

El Tomato
El Tomato

Reputation: 6707

The following lines of code come from an actual macOS game that I submitted to Mac App Store some 3 weeks ago. It counts down a number to 0. In your case, change

self.currentSeconds -= 1

to

self.currentSeconds += 1

timeBackNode is an empty node that holds SKLabelNode objects timeNode0 and timeNode1. So create it when the game starts with didMove.

var currentSeconds = Int() // currentSeconds
var timeNode0 = SKLabelNode() // timeNode0
var timeNode1 = SKLabelNode() // timeNode1
func setupTimeGoal() {
    enumerateChildNodes(withName: "//TimerBack", using: { timeBackNode, _ in
        let goal = self.timeDict["num"] as! Int
        self.currentSeconds = goal // In your case, goal is 0 since you are going to count up.
        self.timeNode0 = SKLabelNode(fontNamed: "Your font's font name")
        self.timeNode0.text = self.makeTimeWithSeconds(secs: goal)
        self.timeNode0.fontSize = 26
        self.timeNode0.fontColor = SKColor.black
        self.timeNode0.horizontalAlignmentMode = .center
        self.timeNode0.position = CGPoint(x: 1, y: -22)
        timeBackNode.addChild(self.timeNode0)

        self.timeNode1 = SKLabelNode(fontNamed: "Your font's font name")
        self.timeNode1.text = self.makeTimeWithSeconds(secs: goal) // this function translate a counting number into a time code like 00:00:00
        self.timeNode1.fontSize = 26
        self.timeNode1.fontColor = SKColor.cyan
        self.timeNode1.horizontalAlignmentMode = .center
        self.timeNode1.position = CGPoint(x: 0, y: -23)
        timeBackNode.addChild(self.timeNode1)
    })
}

func repeatTime() {
    let waitAction = SKAction.wait(forDuration: 1.0)
    let completionAction = SKAction.run {
        if self.currentSeconds == 0 && self.gameState == .playing {
            self.removeAction(forKey: "RepeatTime")
            self.proceedLoss()
            return
        }
        self.currentSeconds -= 1
        self.timeNode0.text = self.makeTimeWithSeconds(secs: self.currentSeconds)
        self.timeNode1.text = self.makeTimeWithSeconds(secs: self.currentSeconds)
    }
    let seqAction = SKAction.sequence([waitAction, completionAction])
    let repeatAction = SKAction.repeatForever(seqAction)
    self.run(repeatAction, withKey: "RepeatTime")
}

Upvotes: 2

Daniel Isaac
Daniel Isaac

Reputation: 745

Instead of using a TimeInterval object, it might be better to use a Timer object which calls a function that increments the value of a label (or an internal variable) from 0 onwards accordingly after every 1 second(or can be made lesser). And for stopping the clock just call the Timer object's invalidate function. Check the documentation for more info: https://developer.apple.com/documentation/foundation/timer

Upvotes: 2

Related Questions