How I Used Swift to Build a Simple Touch Game for iOS

Intro

How the heck do you make a simple touch game for iOS in Swift? I wasn’t quite sure, so I made this:

Let me show you how I built this, and share some gotcha’s that I’ve learnt along the way :)

Preface: I have only been doing Swift (in my leisure) for about a week now. I am sure there are better ways to do most of things shown here. I thought that sharing my journey as a beginner would be helpful for other people learning the language.

Initial Game Design

Initially, I spent some time trying to think of what kind of game I’d be capable of making that wouldn’t be that difficult to write a tutorial about, but also something that could act as a stepping stone towards a real game.

So the basic game design for Kamikaze type game I’m gonna call “Kamakazee” ended up as the following:

  1. Spawn planes/ship at the top of the screen
  2. Have them fly towards the bottom of the screen at an increasing pace.
  3. Touch the ships to have them destroyed/disappear.
  4. Show a score screen after 5 ships have crashed.
  5. Give the ability to retry and start over.

I have done some game dev before, but most of my work has been done in Unity with C#. Surprisingly, a lot of the concepts from Unity carry over to Swift quite nicely :)

Setting up our project

Lets create our project.

1
File -> New -> Project

Then select …

1
iOS -> Application -> Game

Make sure we are using Swift as our language, SpriteKit as our technology, and iPhone as our Device. images

A Sprite is defined as a two-dimensional pre-rendered figure. Thus, SpriteKit will be our best choice for most 2D games. I hope to cover SceneKit, OpenGL ES, and Metal in future tutorials.

On the general tab, in our project settings, make sure we have portrait and upside down unselected for device orientation. We only want the user to be able to play this in landscape view.

images

Thats it! We are going to use Apple’s SpaceShip graphic that already comes with the default project, so no need to import any artwork. I encourage you to try adding graphics on your own.

PS -Friend’s faces work the best ;)

GameScene.swift - Complete Code

Lets open our GameScene.swift file.

Here is our complete game code. Yes, its a bit longer than my previous tutorials, but we are doing a lot of things. Don’t worry, I will fully explain EVERYTHING.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import SpriteKit

class GameScene: SKScene {

    var score = 0
    var health = 5
    var gameOver : Bool?
    let maxNumberOfShips = 10
    var currentNumberOfShips : Int?
    var timeBetweenShips : Double?
    var moverSpeed = 5.0
    let moveFactor = 1.05
    var now : NSDate?
    var nextTime : NSDate?
    var gameOverLabel : SKLabelNode?
    var healthLabel : SKLabelNode?
    
    /*
    Entry point into our scene
    */
    override func didMoveToView(view: SKView) {
        initializeValues()
    }
    
    /* 
    Sets the initial values for our variables.
    */
    func initializeValues(){
        self.removeAllChildren()

        score = 0
        gameOver = false
        currentNumberOfShips = 0
        timeBetweenShips = 1.0
        moverSpeed = 5.0
        health = 5
        nextTime = NSDate()
        now = NSDate()
        
        healthLabel = SKLabelNode(fontNamed:"System")
        healthLabel?.text = "Health: \(health)"
        healthLabel?.fontSize = 30
        healthLabel?.fontColor = SKColor.blackColor()
        healthLabel?.position = CGPoint(x:CGRectGetMinX(self.frame) + 80, y:(CGRectGetMinY(self.frame) + 100));
        
        self.addChild(healthLabel)
    }
    
    /* 
    Called before each frame is rendered 
    */
    override func update(currentTime: CFTimeInterval) {
        
        healthLabel?.text="Health: \(health)"
        if(health <= 3){
            healthLabel?.fontColor = SKColor.redColor()
        }
        
        now = NSDate()
        if (currentNumberOfShips < maxNumberOfShips &&
            now?.timeIntervalSince1970 > nextTime?.timeIntervalSince1970 &&
            health > 0){
            
            nextTime = now?.dateByAddingTimeInterval(NSTimeInterval(timeBetweenShips!))
            var newX = Int(arc4random()%1024)
            var newY = Int(self.frame.height+10)
            var p = CGPoint(x:newX,y:newY)
            var destination =  CGPoint(x:newX, y:0.0)
            
            createShip(p, destination: destination)
            
            moverSpeed = moverSpeed/moveFactor
            timeBetweenShips = timeBetweenShips!/moveFactor
        }
        checkIfShipsReachTheBottom()
        checkIfGameIsOver()
    }
    
    /*  
    Creates a ship
    Rotates it 90º
    Adds a mover to it go downwards
    Adds the ship to the scene
    */
    func createShip(p:CGPoint, destination:CGPoint) {
        let sprite = SKSpriteNode(imageNamed:"Spaceship")
        sprite.name = "Destroyable"
        sprite.xScale = 0.5
        sprite.yScale = 0.5
        sprite.position = p
        
        let duration = NSTimeInterval(moverSpeed)
        let action = SKAction.moveTo(destination, duration: duration)
        sprite.runAction(SKAction.repeatActionForever(action))
        
        let rotationAction = SKAction.rotateToAngle(CGFloat(3.142), duration: 0)
        sprite.runAction(SKAction.repeatAction(rotationAction, count: 0))
        
        currentNumberOfShips?+=1
        self.addChild(sprite)
    }
    
    /* 
    Called when a touch begins 
    */
    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {

        
        for touch: AnyObject in touches {
            let location = (touch as UITouch).locationInNode(self)
            if let theName = self.nodeAtPoint(location).name {
                if theName == "Destroyable" {
                    self.removeChildrenInArray([self.nodeAtPoint(location)])
                    currentNumberOfShips?-=1
                    score+=1
                }
            }
            if (gameOver?==true){
                initializeValues()
            }
        }
    }
    
    /*
    Check if the game is over by looking at our health
    Shows game over screen if needed
    */
    func checkIfGameIsOver(){
        if (health <= 0 && gameOver == false){
            self.removeAllChildren()
            showGameOverScreen()
            gameOver = true
        }
    }
    
    /*
    Checks if an enemy ship reaches the bottom of our screen
    */
    func checkIfShipsReachTheBottom(){
        for child in self.children {
            if(child.position.y == 0){
                self.removeChildrenInArray([child])
                currentNumberOfShips?-=1
                health -= 1
            }
        }
    }
    
    /*
    Displays the actual game over screen
    */
    func showGameOverScreen(){
        gameOverLabel = SKLabelNode(fontNamed:"System")
        gameOverLabel?.text = "Game Over! Score: \(score)"
        gameOverLabel?.fontColor = SKColor.redColor()
        gameOverLabel?.fontSize = 65;
        gameOverLabel?.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
        self.addChild(gameOverLabel)
    }
}

If you copy and paste this code into your file, you should be able to run the game already. Lets talk about what we’ve done.

Top level variables

We declare a lot of variables at the top of our code. Lets take a look at each one.

1
2
var score = 0
var health = 5

score and health should be fairly self explanatory. We are setting their initial values to 0 and 5. For every ship we touch we will increase score by 1. We will decrease our health by 1 for every ship that reaches the bottom of the screen.

One thing I discovered, is if you set the text of a label to be the value of an optional variable it will literally display “(Optional)” on the screen. Since we are displaying these values, I am not making them optional. I’d love if someone could describe a work around for this use case.

1
var gameOver : Bool?

This variable will be set it to true if the game is over and we are showing the score screen. It will be false while we are playing the game.

1
2
let maxNumberOfShips = 10
var currentNumberOfShips : Int?

We don’t want to overwhelm the user too much, so maxNumberOfShips is the maximum number of ships we allow on the screen at any given moment.

To ensure this, we will create a variable named currentNumberOfShips which will keep track of how many ships are currently on the screen.

1
2
3
var timeBetweenShips : Double?
var moverSpeed = 5.0
let moveFactor = 1.05

timeBetweenShips is going to be a cooldown we will use to specify the amount of time between which each ship is generated.

moverSpeed is how long it takes for our ship to fly to the bottom of the screen.

After each plane spawns, we will divide timeBetweenShips and moverSpeed by our moveFactor. I came up with this number through experimentation, it just felt right :)

1
2
var now : NSDate?
var nextTime : NSDate?

For our ship generation cooldown to work, we need to always know what the current time is, and we need to know when is the next time we can spawn a ship.

1
2
var gameOverLabel : SKLabelNode?
var healthLabel : SKLabelNode?

These are our two labels you will see on the screen.

The gameOverLabel will be shown when the game is over.
images

The healthLabel will show our health in the lower left hand corner of the screen.
images

didMoveToView

This is an overridden function from SKScene that acts as our entry point into our game.

Simply put, this code gets called first.

More specifically, this function is fired immediately after a view is presented. If we had more than one view, they would be fired in whatever order the views were shown.

We use this to call our initializeValues functions.

1
2
3
  override func didMoveToView(view: SKView) {
        initializeValues()
    }

initializeValues

There is a principle in programming called DRY or Don’t Repeat Yourself.

I was initially initializing my variables in 3 places, so to DRY up my code I created this function.

1
2
func initializeValues(){
    self.removeAllChildren()

An SKScene has children. Children can be anything from our ships to our labels. For a sanity check and for when we restart the game, the first thing I do is remove all the children from the scene.

1
2
3
4
5
6
7
8
    score = 0
    gameOver = false
    currentNumberOfShips = 0
    timeBetweenShips = 1.0
    moverSpeed = 5.0
    health = 5
    nextTime = NSDate()
    now = NSDate()

These are all the initial values we want for our variables.

It is worth noting that moverSpeed and timeBetweenShips is in seconds.

1
2
3
4
5
    healthLabel = SKLabelNode(fontNamed:"System")
    healthLabel?.text = "Health: \(health)"
    healthLabel?.fontSize = 30
    healthLabel?.fontColor = SKColor.blackColor()
    healthLabel?.position = CGPoint(x:CGRectGetMinX(self.frame) + 80, y:(CGRectGetMinY(self.frame) + 100));

Next we set up our healthLabel that will display our health at the bottom left hand corner of the screen as shown above.

We set up which font to use, what the text should say, what font size to use, and what color our font should be.

The last line sets the position of our text. Notice that I’m using CGRectGetMinX to find the left side of the screen, and I’m using CGRectGetMinY to find the bottom. The values 80 and 100 are just used as offsets so our label is not hidden off of the screen.

1
2
    self.addChild(healthLabel)
}

Finally, we add our healthLabel to scene, and it should be should after the update() is called.

Lets talk about that function next.

update

This is another function overridden by SKScene. update gets called for every frame we draw. If we are running our game at 60 frames per second, this function is being called 60 times during that second.

It is worth considering any performance impact that might occur for any code you place here.

1
2
3
4
5
6
override func update(currentTime: CFTimeInterval) {
    
    healthLabel?.text="Health: \(health)"
    if(health <= 3){
        healthLabel?.fontColor = SKColor.redColor()
    }

This first thing I do in here is update our healthLabel text that we added to the scene in our initializeValues function.

I also added some fun code that will change the color of our healthLabel from black to red if the players health drops to 3. Its a good way to call attention to the user subtly and tells them to be careful!.

Next, lets check to see if we can create a ship!

1
2
    now = NSDate()
    if (currentNumberOfShips < maxNumberOfShips &&

This condition is checking if we have room to make more ships. If true, we move on.

1
        now?.timeIntervalSince1970 > nextTime?.timeIntervalSince1970 &&

This condition is checking if its time to make a new ship. If true, we move on.

1
        health > 0){

Last, we check to make sure we not dead. If we don’t have health, we shouldn’t bother spawning any more ships.

1
        nextTime = now?.dateByAddingTimeInterval(NSTimeInterval(timeBetweenShips!))

Ok then, LETS MAKE A SHIP!

The first thing we do is specify when the next ship can be made. We do this by adding a some time to our now value and assigning it to nextTime.

1
2
3
4
        var newX = Int(arc4random()%1024)
        var newY = Int(self.frame.height+10)
        var p = CGPoint(x:newX,y:newY)
        var destination =  CGPoint(x:newX, y:0.0)

In this code, we are creating a spawn point and a destination point for our ship. To do this, I am using the arc4random() function and giving it a value of 1024 which is the number of horizontal pixels we have. This will generate a random number between 0 and 1024.

I had a difficult time trying to pass self.frame.width to arc4random, so I moved on. If anyone has any ideas, please let me know :)

The Y value for our spawn point is the self.frame height + 10. The 10 part is to provide it some buffer room.

The destination will have the same x value as our spawn point, but the y value is simply 0.

1
2
3
4
5
        createShip(p, destination: destination)
        
        moverSpeed = moverSpeed/moveFactor
        timeBetweenShips = timeBetweenShips!/moveFactor
    }

We will create our ship using our createShip function and pass our spawn point and destination to it as values. We will talk about that function next.

We also divide our moverSpeed and timeBetweenShips by moveFactor, which will make our ships spawn faster and faster!

1
2
3
    checkIfShipsReachTheBottom()
    checkIfGameIsOver()
}

Regardless of whether a ship is ready to spawn or not, we will also check if any ships reach the bottom of the screen and if the game is over.

createShip

1
2
3
4
5
6
func createShip(p:CGPoint, destination:CGPoint) {
    let sprite = SKSpriteNode(imageNamed:"Spaceship")
    sprite.name = "Destroyable"
    sprite.xScale = 0.5
    sprite.yScale = 0.5
    sprite.position = p

For this first part of our function, we create our SKSpriteNode and pass it an image name. We will use “SpaceShip” as Apple provides this image for free as part of our project. I encourage you to use a different image here. Simply drag it into our project and change this name.

Next, we give the sprite some values. I’m specifying its name value as “Destroyable” to identify it as a destroyable object. This part will make more sense in our touchesBegan() function.

1
2
3
    let duration = NSTimeInterval(moverSpeed)
    let action = SKAction.moveTo(destination, duration: duration)
    sprite.runAction(SKAction.repeatActionForever(action))

We need to make the sprite move, so I’m using an SKAction.moveTo function. This function needs 2 parameters, a destination point and a duration. We have already created our destination point in update(), and our duration is created from making an NSTimeInterval object with our moverSpeed variable.

We tell our sprite to run this action, and to repeat forever.

1
2
    let rotationAction = SKAction.rotateToAngle(CGFloat(3.142), duration: 0)
    sprite.runAction(SKAction.repeatAction(rotationAction, count: 0))

If you ran our program without these 2 lines of code above, the ship would be pointed upward instead of downward.

To fix this, I create another SKAction but this time I use rotateToAngle. The first value it accepts is a radian value. I know that I want to rotate the ship 180º, but I wasn’t sure what the radian value for it. To be honest, I asked Google.

images

It is interesting that this is the value of Pi. I might need to revisit my grade school Geometry books again :)

It is also worth noting that I wanted this rotation to happen instantly, so I set the duration to 0 and the amount of times to repeat the action to 0.

1
2
3
    currentNumberOfShips?+=1
    self.addChild(sprite)
}

Lastly, we increase the number of current ships on the screen, and add the sprite to our scene.

touchesBegan

touchesBegan is another overloaded method from SKScene. It is called whenever touching happens in our scene. We already know what we want this method to do. We want it to destroy ships we touch or restart our game.

1
2
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
    for touch: AnyObject in touches {

The first thing I do is start iterating through every touch. Technically, we could be touching with multiple fingers at the same time.

1
2
        let location = (touch as UITouch).locationInNode(self)
        if let theName = self.nodeAtPoint(location).name {

The first thing we do is get the location of the touch. We do this so we can call self.nodeAtPoint which will give us any nodes at the location of the touch.

Notice how we have put our let statement in an if. This will allow us to avoid any problems we might have if no nodes exist at this point. The condition is only met if we can get a node and a name value from the touched location.

1
2
3
4
5
6
            if theName == "Destroyable" {
                self.removeChildrenInArray([self.nodeAtPoint(location)])
                currentNumberOfShips?-=1
                score+=1
            }
        }

The above code illustrates why we name our sprites “Destroyable” as it will only destroy things based on this name. If this condition did not exist, we could destroy our healthLabel.

We pass removeChildInArray an array that contains only one node in it, however we could have built an array and sent a single command after we iterated through all the touches. I encourage you to try this as an exercise.

Since we are destroying a ship, we decrease currentNumberOfShips and increase our score!

1
2
3
4
5
        if (gameOver?==true){
            initializeValues()
        }
    }
}

This last bit of this code checks if we are on the gameOver screen. If we are, we call our initalizeValues function, which will remove all children in the scene and start our game over again.

checkIfGameIsOver

If you remember, this function is called every time update() is called.

1
2
3
4
5
6
7
func checkIfGameIsOver(){
    if (health <= 0 && gameOver == false){
        self.removeAllChildren()
        showGameOverScreen()
        gameOver = true
    }
}

We are simply checking if we have no health left and our if game thinks its not game over yet.

If it is, we remove everything in the scene, display the game over screen, and set game over to true.

checkIfShipsReachTheBottom

This function is also called every time update() is called.

1
2
3
4
5
6
7
8
9
func checkIfShipsReachTheBottom(){
    for child in self.children {
        if(child.position.y == 0){
            self.removeChildrenInArray([child])
            currentNumberOfShips?-=1
            health -= 1
        }
    }
}

We iterate through every child in our scene and look at its y position. If its y position is zero, we remove the child from the scene, decrement the current number of ships, and decrement our health.

Another thing we could do here is check if “Destroyable” is the name of the child. I encourage you to try this.

showGameOverScreen

Finally, this function will display our game over screen.

1
2
3
4
5
6
7
8
func showGameOverScreen(){
    gameOverLabel = SKLabelNode(fontNamed:"System")
    gameOverLabel?.text = "Game Over! Score: \(score)"
    gameOverLabel?.fontColor = SKColor.redColor()
    gameOverLabel?.fontSize = 65;
    gameOverLabel?.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
    self.addChild(gameOverLabel)
}

This should look familiar to the code for our healthLabel. The only difference is we are using CGRectGetMidX and CGRectGetMidY.

Conclusion

Sweeet! We’ve written our first game in Swift. At least, it was MY first game with Swift. The game is a little boring, and ends rather quickly, however we know know how to set up 2D sprite games, and how to click and interact with the objects inside of them.

There are many things we could do to make this game better.

Sound. Everything is better with sound effects and music.

Common game design convention states that if we give the user a button, we should give them a reason to not press it. Our game does not support this yet. For homework, we could try decrementing the score every time a ship is missed.

Instead of going straight down, our ships could fly diagonally or follow curves like sine waves. This would be much more difficult and probably more fun.

We could put an actual battleship at the bottom of the screen and give the player a sense of identity.

We could add explosions and various other game juice. You would be surprised what some subtle screen shake and particle engines can add to a relatively boring game.

Anyways, I hope you enjoyed this tutorial. If you have any suggestions for future tutorials or just advice how to make my code better, please feel free to leave a comment. Thanks for you time, and enjoy learning Swift!

Comments