Creating a Countdown Timer in SpriteKit with Swift

We are going to create a version of a label that will countdown from a specified number. To do this we subclass the SKLabelNode class, and add some functionality of our own. The advantage of doing this is that we could later reuse the same class in another game if we wanted to.

Creating the CountdownLabel class

  1. Create a new project in Xcode using the iOS Application Game template, name the product Timer, and ensure the the Language is set to Swift, the Game Technology is set to SpriteKit and the Devices to iPhone
  2. Create a new Swift File and name it CountdownLabel
  3. Inside the CountDownLabel.swift file we are going to create a class, which be (subclass) a SKLabelNode. Replace the existing code in the file with the following:
    import SpriteKit
    class CountdownLabel: SKLabelNode{
        
    }
    
  4. As it stands the CountdownLabel will have all the existing functionality of a label, but we want to add some more, so in the CountdownLabel.swift file, inside the braces add the following code:
    func update(){
            
    }
        
    func startWithDuration(duration: NSTimeInterval){
            
    }
        
    func hasFinished() -> Bool{
        
        //for now return false to avoid seeing a warning
        return false
    }
  5. We need to know when the timer is due to expire, to calculate how much longer it will run for. We will use an instance variable to keep track of this. Inside the class, but outside of any functions add the following code:
    var endTime:NSDate!
  6. When the timer starts, we need to take the number of seconds that the timer should run for, add it to the current time and store it in the endTime instance variable. Add the following code inside the startWithDuration method to do that:
    let timeNow = NSDate()
    endTime = timeNow.dateByAddingTimeInterval(duration)
  7. Our timer will need to determine the time remaining, to do this, we will find the difference in time between now and the timer’s end. Add a new method as follows:
    private func timeLeft() -> NSTimeInterval{
            
            let now = NSDate()
            let remainingSeconds = endTime.timeIntervalSinceDate(now)
            return max(remainingSeconds, 0)
        }
  8. Because the label cannot update itself on a regular interval, we need to write a method to be called when the scene is updated, which updates the label. Inside the update method, add the following code:
    //convert remaining time to an integer
    let timeLeftInteger = Int(timeLeft())
    //update the label text, converting the Int to a String
    text = String(timeLeftInteger)
  9. Finally, it is likely that our game will want to be able to check when the timer has finished. Replace the code in the hasFinished method with that shown below to check if the timer has reached zero:
    return timeLeft() == 0

Adding a label to our game scene

  1. You can now setup and use your timer in your scene by creating a timer and adding it to the scene, (keeping track of it in an instance variable) then updating it every time the scene is updated (approx. 60x per second) using the example code below (you can replace the existing code in GameScene.swift with what is shown below):
    import SpriteKit
    class GameScene: SKScene {
        
        //create a variable for the time and instantiate it
        let timer = CountdownLabel()
        override func didMoveToView(view: SKView) {
           
            //create, position and start the time when the game moves to the view
            let centre = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame))
            timer.position = centre
            timer.fontSize = 65
            addChild(timer)
            timer.startWithDuration(20)
            
        }
      
        override func update(currentTime: CFTimeInterval) {
            //tell the timer to update
            timer.update()
        }
    }
    
    Note that the override keyword is used to signify that we are overriding functionality which is implemented in a parent class (in this case the SKScene class). Sometime we need to use both sets of functionality and we should therefore call the method from the parent class (in this example we might call super.didMoveToView() ).

Using the timer for a game

We can turn the time into a very simple game to see how many times we can tap a button in a short space of time. To do this, we need to add a button (e.g. a SpriteNode) and a score (e.g. a LabelNode) to the scene, and also keep track of the high score.

  1. Inside the curly braces after the line declaring the GameScene class, add the following lines of code:
    var highScore = 0
    let scoreLabel = SKLabelNode()
    let button = SKSpriteNode()
    We will use these to keep track of the score, display the high score, and allow the user to play the game respectively.
  2. Add a method as shown below to setup the button
    func setupButton(){
        button.size = CGSizeMake(100, 100)
        var x = CGRectGetMidX(frame)
        var y = CGRectGetMidY(frame) - 150       
        button.position = CGPointMake(x,y)
        button.color = UIColor.redColor()
        addChild(button)
    }
  3. Secondly, add a method to setup a score label as follows:
    func setupScoreLabel(){
        var x = CGRectGetMidX(frame)
        var y = CGRectGetMidY(frame) + 150
        scoreLabel.position = CGPointMake(x, y)
        scoreLabel.horizontalAlignmentMode = .Right
        scoreLabel.text = "0"
        addChild(scoreLabel)
    }
  4. Thirdly, add a method that we can call to update the score label (as each tap occurs)
    func updateScoreLabel(){
    	scoreLabel.text = String(highScore)
    }
  5. Next we want to implement the touchesBegan method, which will, provided the timer hasn't finished, and the user has tapped inside the bounds of the button, update the score and its corresponding label:
    override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent?) {
        //check the timer has not finished
        if !timer.hasFinished(){
            //go through all the possible touches
            for touch in touches{
                //if a touch is inside the button
                if CGRectContainsPoint(button.frame, touch.locationInNode(self)){
                    //increment the high score and update the label
                    highScore += 1
                    updateScoreLabel()
                }
            }
        }
    }
  6. All that remains is to call the methods which create the button and score label. Add the following lines of code in the didMoveToView method
    setupButton()
    setupScoreLabel()
    

You should now be able to run the game, though you may want to shorten the length of time the timer runs to make the game easier (and more comfortable) to play!

Further Tasks

Advanced Tasks

Implement different levels with varying size buttons, moving buttons or varying periods of time, along with a target number of presses for each level.