1. Code
  2. Mobile Development
  3. iOS Development

SpriteKit Basics: Putting It All Together

Scroll to top
9 min read
This post is part of a series called SpriteKit Basics.
SpriteKit Basics: Actions and Physics
Final product imageFinal product imageFinal product image
What You'll Be Creating

In this post we'll build a simple game from scratch. Along the way, we'll touch on some of the most important aspects of the SpriteKit library.

This post builds on what we've learned earlier in the SpriteKit Basics series. If you want to refresh your SpriteKit knowledge, take a look at some of my other posts.

New Project

Open Xcode and start a new project from the menu File > New Project. Make sure iOS is selected and choose Game as your template.

new projectnew projectnew project

Give your project a name, and make sure that Language is set to Swift, Game Technology is set to SpriteKit, and Devices is set to iPad.

project optionsproject optionsproject options

Planning the Game Scenes

One of the first things I like to do when creating a project is to determine how many scenes I will need for the project. I will usually have at least three scenes: an intro scene, a main game scene, and a scene to show high scores, etc.

For this example, we just need an intro and main gameplay scene since we won't be keeping track of lives, scores, etc. SpriteKit already comes with one scene when you create a new project, so we just need an intro scene.

From Xcode's menu, choose File > New > File. Make sure iOS is selected, and choose Cocoa Touch Class.

new cocoa touch classnew cocoa touch classnew cocoa touch class

Name the class StartGameScene, and make sure that Subclass of is set to SKScene and Language is set to Swift.

startgamescene classstartgamescene classstartgamescene class

Setting Up GameViewController

Open GameViewController.swift. Delete everything in that file and replace it with the following.

1
import UIKit
2
import SpriteKit
3
import GameplayKit
4
5
class GameViewController: UIViewController {
6
7
    override func viewDidLoad() {
8
        super.viewDidLoad()
9
        
10
        let scene = StartGameScene(size: view.bounds.size)
11
        let skView = self.view as! SKView
12
        skView.showsFPS = false
13
        skView.showsNodeCount = false
14
        skView.ignoresSiblingOrder = false
15
        scene.scaleMode = .aspectFill
16
        skView.presentScene(scene)
17
    }
18
19
    override var prefersStatusBarHidden: Bool {
20
        return true
21
    }
22
}

When you create a new project, GameViewController.swift is set up to load GameScene.sks from disk. GameScene.sks is used along with SpriteKit's built-in scene editor, which allows you to visually lay out your projects. We will not be using GameScene.sks, and will instead create everything from code, so here we initiate a new instance of StartGameScene and present it.

Create the Intro Scene

Add the following to the newly created StartGameScene.swift.

1
import UIKit
2
import SpriteKit
3
4
class StartGameScene: SKScene {
5
6
    override func didMove(to view: SKView){
7
        scene?.backgroundColor = .blue
8
        let logo = SKSpriteNode(imageNamed: "bigplane")
9
        logo.position = CGPoint(x: size.width/2, y: size.height/2)
10
        addChild(logo)
11
        
12
        let newGameBtn = SKSpriteNode(imageNamed: "newgamebutton")
13
        newGameBtn.position = CGPoint(x: size.width/2, y: size.height/2 - 350)
14
        newGameBtn.name = "newgame"
15
        addChild(newGameBtn)        
16
    }
17
    
18
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
19
        guard let touch = touches.first else {
20
            return
21
        }
22
        
23
        let touchLocation = touch.location(in: self)
24
        let touchedNode = self.atPoint(touchLocation)
25
        
26
        if(touchedNode.name == "newgame"){
27
            let newScene = GameScene(size: size)
28
            newScene.scaleMode = scaleMode
29
            view?.presentScene(newScene)       
30
        }
31
    }}

This scene is pretty simple. In the didMove method, we add a logo and a button. Then, in touchesBegan, we detect touches on the new game button and respond by loading the main scene GameScene.

Planning Game Classes

The next thing I like to do when creating a new game is decide which classes I will need. I can tell right away that I will need a Player class and an Enemy class. Both of these classes will extend SKSpriteNode. I think for this project we will just create the player and enemy bullets right from within their respective classes. You could make separate player bullet and enemy bullet classes if you prefer, and I suggest you try to do that as an exercise on your own. 

Lastly, there are the islands. These do not have any specific functionality but to move down the screen. In this case, since they're just decorations, I think it's also okay not to create a class, and instead just create them in the main GameScene.

Creating the Player Class

From Xcode's menu, choose File > New > File.  Make sure iOS is selected and choose Cocoa Touch Class.

new cocoa touch classnew cocoa touch classnew cocoa touch class

Make sure that Class is set to Player, Subclass of: is set to SKSpriteNode, and Language is set to Swift.

player classplayer classplayer class

Now add the following to Player.swift.

1
import UIKit
2
import SpriteKit
3
4
class Player: SKSpriteNode {
5
    private var canFire = true
6
    private var invincible = false
7
    private var lives:Int = 3 {
8
        didSet {
9
            if(lives < 0){
10
                kill()
11
            }else{
12
                respawn()
13
            }
14
        }
15
    }
16
    init() {
17
        let texture = SKTexture(imageNamed: "player")
18
        super.init(texture: texture, color: .clear, size: texture.size())
19
        self.physicsBody = SKPhysicsBody(texture: self.texture!,size:self.size)
20
        self.physicsBody?.isDynamic = true
21
        self.physicsBody?.categoryBitMask = PhysicsCategories.Player
22
        self.physicsBody?.contactTestBitMask = PhysicsCategories.Enemy | PhysicsCategories.EnemyBullet
23
        self.physicsBody?.collisionBitMask = PhysicsCategories.EdgeBody
24
        self.physicsBody?.allowsRotation = false
25
        generateBullets()
26
    }
27
    
28
    required init?(coder aDecoder: NSCoder) {
29
        super.init(coder: aDecoder)
30
    }
31
   
32
    func die (){
33
        if(invincible == false){
34
            lives -= 1
35
        }
36
    }
37
        
38
    func kill(){
39
        let newScene = StartGameScene(size: self.scene!.size)
40
        newScene.scaleMode = self.scene!.scaleMode
41
        let doorsClose = SKTransition.doorsCloseVertical(withDuration: 2.0)
42
        self.scene!.view?.presentScene(newScene, transition: doorsClose)
43
    }
44
    
45
    func respawn(){
46
        invincible = true
47
        let fadeOutAction = SKAction.fadeOut(withDuration: 0.4)
48
        let fadeInAction = SKAction.fadeIn(withDuration: 0.4)
49
        let fadeOutIn = SKAction.sequence([fadeOutAction,fadeInAction])
50
        let fadeOutInAction = SKAction.repeat(fadeOutIn, count: 5)
51
        let setInvicibleFalse = SKAction.run {
52
            self.invincible = false
53
        }
54
        run(SKAction.sequence([fadeOutInAction,setInvicibleFalse]))        
55
    }
56
    
57
    func generateBullets(){
58
        let fireBulletAction = SKAction.run{ [weak self] in
59
            self?.fireBullet()
60
        }
61
        let waitToFire = SKAction.wait(forDuration: 0.8)
62
        let fireBulletSequence = SKAction.sequence([fireBulletAction,waitToFire])
63
        let fire = SKAction.repeatForever(fireBulletSequence)
64
        run(fire)
65
    }
66
    
67
    func fireBullet(){
68
        let bullet = SKSpriteNode(imageNamed: "bullet")
69
        bullet.position.x = self.position.x
70
        bullet.position.y = self.position.y + self.size.height/2
71
        bullet.physicsBody = SKPhysicsBody(rectangleOf: bullet.size)
72
        bullet.physicsBody?.categoryBitMask = PhysicsCategories.PlayerBullet
73
        bullet.physicsBody?.allowsRotation = false
74
        scene?.addChild(bullet)
75
        let moveBulletAction = SKAction.move(to: CGPoint(x:self.position.x,y:(scene?.size.height)! + bullet.size.height), duration: 1.0)
76
        let removeBulletAction = SKAction.removeFromParent()
77
        bullet.run(SKAction.sequence([moveBulletAction,removeBulletAction]))
78
    }
79
}

Within the init() method, we set up the physicsBody and invoke generateBullets(). The generateBullets method repeatedly calls fireBullet(), which creates a bullet, sets its physicsBody, and moves it down the screen.

When the player loses a life, the respawn() method is invoked. Within the respawn method, we fade the plane in and out five times, during which time the player will be invincible. One the player has exhausted all the lives, the kill() method is invoked. The kill method simply loads the StartGameScene.

Creating the Enemy Class

Choose File > New > File from Xcode's menu. Make sure iOS is selected and choose Cocoa Touch Class.

new cocoa touch classnew cocoa touch classnew cocoa touch class

Make sure that Class is set to EnemySubclass of: is set to SKSpriteNode, and Language is set to Swift.

Add the following to Enemy.swift.

1
import UIKit
2
import SpriteKit
3
4
class Enemy: SKSpriteNode {
5
6
    init() {
7
        let texture = SKTexture(imageNamed: "enemy1")
8
        super.init(texture: texture, color: .clear, size: texture.size())
9
        self.name = "enemy"
10
        self.physicsBody = SKPhysicsBody(texture: self.texture!, size: self.size)
11
        self.physicsBody?.isDynamic = true
12
        self.physicsBody?.categoryBitMask = PhysicsCategories.Enemy
13
        self.physicsBody?.contactTestBitMask = PhysicsCategories.Player | PhysicsCategories.PlayerBullet
14
        self.physicsBody?.allowsRotation = false
15
        move()
16
        generateBullets()        
17
    }
18
    
19
    required init?(coder aDecoder: NSCoder) {
20
        super.init(coder: aDecoder)
21
    }
22
    
23
    func fireBullet(){
24
        let bullet = SKSpriteNode(imageNamed: "bullet")
25
        bullet.position.x = self.position.x
26
        bullet.position.y = self.position.y - bullet.size.height * 2
27
        bullet.physicsBody = SKPhysicsBody(rectangleOf: bullet.size)
28
        bullet.physicsBody?.categoryBitMask = PhysicsCategories.EnemyBullet
29
        bullet.physicsBody?.allowsRotation = false
30
        scene?.addChild(bullet)
31
        let moveBulletAction = SKAction.move(to: CGPoint(x:self.position.x,y: 0 - bullet.size.height), duration: 2.0)
32
        let removeBulletAction = SKAction.removeFromParent()
33
        bullet.run(SKAction.sequence([moveBulletAction,removeBulletAction])
34
        )
35
    }
36
    
37
    func move(){   
38
        let moveEnemyAction = SKAction.moveTo(y: 0 - self.size.height, duration: 12.0)
39
        let removeEnemyAction = SKAction.removeFromParent()
40
        let moveEnemySequence = SKAction.sequence([moveEnemyAction, removeEnemyAction])
41
        run(moveEnemySequence)
42
    }
43
    
44
    func generateBullets(){
45
        let fireBulletAction = SKAction.run{ [weak self] in
46
            self?.fireBullet()
47
        }
48
        let waitToFire = SKAction.wait(forDuration: 1.5)
49
        let fireBulletSequence = SKAction.sequence([fireBulletAction,waitToFire])
50
        let fire = SKAction.repeatForever(fireBulletSequence)
51
        run(fire)
52
    }
53
}

This class is pretty similar to the Player class. We set its physicsBody and invoke generateBullets(). The move() simply moves the enemy down the screen.

Creating the Main Game Scene

Delete everything within GameScene.swift and add the following.

1
import SpriteKit
2
import GameplayKit
3
import CoreMotion
4
5
class GameScene: SKScene, SKPhysicsContactDelegate {
6
    let player = Player()
7
    let motionManager = CMMotionManager()
8
    var accelerationX: CGFloat = 0.0
9
    
10
    override func didMove(to view: SKView) {
11
        physicsWorld.gravity = CGVector(dx:0.0, dy:0.0)
12
        self.physicsWorld.contactDelegate = self
13
        scene?.backgroundColor = .blue
14
        physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
15
        physicsBody?.categoryBitMask = PhysicsCategories.EdgeBody
16
        player.position = CGPoint(x: size.width/2, y: player.size.height)
17
        addChild(player)
18
        setupAccelerometer()
19
        addEnemies()
20
        generateIslands()
21
    }
22
        
23
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {    
24
    }
25
    
26
    func addEnemies(){
27
        let generateEnemyAction = SKAction.run{ [weak self] in
28
            self?.generateEnemy()
29
        }
30
        let waitToGenerateEnemy = SKAction.wait(forDuration: 3.0)
31
        let generateEnemySequence = SKAction.sequence([generateEnemyAction,waitToGenerateEnemy])
32
        run(SKAction.repeatForever(generateEnemySequence))
33
    }
34
    
35
    func generateEnemy(){
36
        let enemy = Enemy()
37
        addChild(enemy)
38
        enemy.position = CGPoint(x: CGFloat(arc4random_uniform(UInt32(size.width - enemy.size.width))), y: size.height - enemy.size.height)
39
    }
40
    
41
    func didBegin(_ contact: SKPhysicsContact) {
42
        var firstBody: SKPhysicsBody
43
        var secondBody: SKPhysicsBody
44
        
45
        if(contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask){
46
            firstBody = contact.bodyA
47
            secondBody = contact.bodyB
48
        }else{
49
            firstBody = contact.bodyB
50
            secondBody = contact.bodyA
51
        }
52
        
53
        if((firstBody.categoryBitMask & PhysicsCategories.Player != 0) && (secondBody.categoryBitMask & PhysicsCategories.Enemy != 0)){
54
            player.die()
55
            secondBody.node?.removeFromParent()
56
            createExplosion(position: player.position)            
57
        }
58
                
59
        if((firstBody.categoryBitMask & PhysicsCategories.Player != 0) && (secondBody.categoryBitMask & PhysicsCategories.EnemyBullet != 0)){
60
            player.die()
61
            secondBody.node?.removeFromParent()            
62
        }
63
        if((firstBody.categoryBitMask & PhysicsCategories.Enemy != 0) && (secondBody.categoryBitMask & PhysicsCategories.PlayerBullet != 0)){
64
            if(firstBody.node != nil){
65
                createExplosion(position: (firstBody.node?.position)!)
66
            }
67
            firstBody.node?.removeFromParent()
68
            secondBody.node?.removeFromParent()            
69
        }
70
    }
71
    
72
    func createExplosion(position: CGPoint){
73
        let explosion = SKSpriteNode(imageNamed: "explosion1")
74
        explosion.position = position
75
        addChild(explosion)
76
        var explosionTextures:[SKTexture] = []
77
        
78
        for i in 1...6 {
79
            explosionTextures.append(SKTexture(imageNamed: "explosion\(i)"))
80
        }
81
        
82
        let explosionAnimation = SKAction.animate(with: explosionTextures,
83
                                                  timePerFrame: 0.3)
84
        explosion.run(SKAction.sequence([explosionAnimation, SKAction.removeFromParent()]))
85
    }
86
    
87
    func  createIsland() {
88
        let island = SKSpriteNode(imageNamed: "island1")
89
90
        island.position = CGPoint(x: CGFloat(arc4random_uniform(UInt32(size.width - island.size.width))), y: size.height - island.size.height - 50)
91
        island.zPosition = -1
92
        addChild(island)
93
        let moveAction = SKAction.moveTo(y: 0 - island.size.height, duration: 15)
94
        island.run(SKAction.sequence([moveAction, SKAction.removeFromParent()]))
95
    }
96
    
97
    func generateIslands(){
98
        let generateIslandAction = SKAction.run { [weak self] in
99
            self?.createIsland()
100
        }
101
        let waitToGenerateIslandAction = SKAction.wait(forDuration: 9)
102
        run(SKAction.repeatForever(SKAction.sequence([generateIslandAction, waitToGenerateIslandAction])))
103
    }
104
        
105
    func setupAccelerometer(){
106
        motionManager.accelerometerUpdateInterval = 0.2
107
        motionManager.startAccelerometerUpdates(to: OperationQueue(), withHandler: { accelerometerData, error in
108
        guard let accelerometerData = accelerometerData else {
109
            return
110
        }
111
            let acceleration = accelerometerData.acceleration
112
            self.accelerationX = CGFloat(acceleration.x)
113
        })
114
    }
115
    
116
    override func didSimulatePhysics() {        
117
        player.physicsBody?.velocity = CGVector(dx: accelerationX * 600, dy: 0)
118
    }
119
}

We create an instance of Player and an instance of CMMotionManager. We are using the accelerometer to move the player in this game.

Within the didMove(to:) method we turn off the gravity, set up the contactDelegate, add an edge loop, and set the player's position before adding it to the scene. We then invoke setupAccelerometer(), which sets up the accelerometer, and invoke the addEnemies() and generateIslands() methods.

The addEnemies() method repeatedly calls the generateEnemy() method, which will create an instance of Enemy and add it to the scene.

The generateIslands() method works similarly to the addEnemies() method in that it repeatedly calls createIsland() which creates an SKSpriteNode and adds it to the scene. Within createIsland(), we also create an SKAction that moves the island down the scene.

Within the didBegin(_:) method, we check to see which nodes are making contact and respond by removing the appropriate node from the scene and invoking player.die() if necessary. The createExplosion() method creates an explosion animation and adds it to the scene. Once the explosion is finished, it is removed from the scene.

Conclusion

During this series, we learned some of the most important concepts used in almost all SpriteKit games. We ended the series by showing how simple it is to get a basic game up and running. There are still some improvements that could be made, like a HUB, high scores, and sounds (I included a couple of MP3s you can use for this in the repo). I hope you learned something useful throughout this series, and thanks for reading!

If you want to learn more about game programming with SpriteKit, check out one of our comprehensive video courses! You'll learn how to build a SpriteKit game from A to Z.