By Kevin PhamDecember 16, 2014

Simple Sprite Kit Tutorial in Swift, Part 1

In this two-part tutorial, we are going to make a basic 2D game in Swift using Sprite Kit.

We’ll be adding the hero of the game (the spaceship) and the background in the first part. In part 2, we’ll finish by adding the oncoming missiles and the end game screen. You can get the full source code of part 1 on GitHub.

Sprite Kit game in Swift

Getting started

Start Xcode and select File\New\Project from the main menu. Select the iOS\Application\Game template and click Next.

Sprite Kit Game in Swift Set Up 1

Enter your game name for Product Name. Make sure the language is set to Swift, the game technology is Sprite Kit, and the device of choice is iPhone. Click Next to create the project.

Sprite Kit Game in Swift Set Up 2

After the project is created, we get thrown into the Project Navigator—the first tab in the left sidebar. This is where you’ll find project details and a whole bunch of other settings that confuse most people. What we are concerned with for now is the Device Orientation under the Deployment Info section in the General tab. Deselect Portrait.

Sprite Kit Game in Swift Set Up 3

Before we continue, let’s run the code to see what Xcode has provided for us in this template. In the toolbar, change the device of the active scheme to iPhone 6 simulator (if it isn’t already) and press the Play button to build and run the code.

Sprite Kit Game in Swift First Test

As you’ll see in the middle of the simulator, there is a single label that says, “Hello, World!” When you click anywhere on the screen, a rotating spaceship should appear. If you’re familiar with gaming and performance, you’ll see a label on the bottom right that tells us fps (frames per second). We won’t be using that for this tutorial, but it’s nice to know that Sprite Kit provides us with that type of information.

Sprite Kit Game in Swift Hello World

Sprite Kit Physics and Collision Detection

One of the nice things about Sprite Kit is that it comes with a physics engine right out of the box, which helps in simulating realistic movements and collision detection. We’ll be using the physics engine to move our spaceship and detect the collision between the spaceship and oncoming missiles.

Adding the Hero

Before starting these steps, download the game art folder, and copy it to your Xcode project.

Open GameScene.swift and you will see the code for displaying the rotating spaceship. For now, delete everything in the file and replace it with:

import SpriteKit

class GameScene: SKScene, SKPhysicsContactDelegate {

    var ship = SKSpriteNode()
    var actionMoveUp = SKAction()
    var actionMoveDown = SKAction()

    override func didMoveToView(view: SKView) {
        /* Setup your scene here */
        self.backgroundColor = SKColor.whiteColor()

        // Making self delegate of physics world
        self.physicsWorld.gravity = CGVectorMake(0, 0)
        self.physicsWorld.contactDelegate = self
    }

}

Let’s go over what this does line by line. We set the variables (ship, actionMoveUp, actionMoveDown) above the methods and will be using them later on.

didMoveToView is the method that gets called when the scene is first created.

self.backgroundColor = SKColor.whiteColor()

We set the background color to white.

self.physicsWorld.gravity = CGVectorMake(0, 0)
self.physicsWorld.contactDelegate = self

These lines set up the physics world to have no gravity, and sets the scene as the delegate to be notified when two physics bodies collide. (Note: Setting the delegate requires that you subclass SKPhysicsContactDelegate, which is already done in the code above.)

Add these lines right under the other variable declarations:

let shipCategory = 0x1 << 1
let obstacleCategory = 0x1 << 2

These will set your two categories for collision detection. We’ll be using them later on. Now let’s add the spaceship onto the screen. Add this method:

func addShip() {
    // Initializing spaceship node
    ship = SKSpriteNode(imageNamed: "spaceship")
    ship.setScale(0.5)
    ship.zRotation = CGFloat(-M_PI/2)

    // Adding SpriteKit physics body for collision detection
    ship.physicsBody = SKPhysicsBody(rectangleOfSize: ship.size)
    ship.physicsBody?.categoryBitMask = UInt32(shipCategory)
    ship.physicsBody?.dynamic = true
    ship.physicsBody?.contactTestBitMask = UInt32(obstacleCategory)
    ship.physicsBody?.collisionBitMask = 0
    ship.name = "ship"
    ship.position = CGPointMake(120, 160)

    self.addChild(ship)

    actionMoveUp = SKAction.moveByX(0, y: 30, duration: 0.2)
    actionMoveDown = SKAction.moveByX(0, y: -30, duration: 0.2)
}

Edit the didMoveToView method and add addShip after setting the background color to white. It should look like this:

self.backgroundColor = SKColor.whiteColor()
self.addShip()

Let’s go over the addShip method step-by-step.

ship = SKSpriteNode(imageNamed: "spaceship")
ship.setScale(0.5)
ship.zRotation = CGFloat(-M_PI/2)

This creates a new SpriteKit node with the spaceship image and reduces the size of the image by half. After that, we rotate it so it points in the right direction.

ship.physicsBody = SKPhysicsBody(rectangleOfSize: ship.size) //1
ship.physicsBody?.categoryBitMask = UInt32(shipCategory) //2
ship.physicsBody?.dynamic = true //3
ship.physicsBody?.contactTestBitMask = UInt32(obstacleCategory) //4
ship.physicsBody?.collisionBitMask = 0 //5
ship.name = "ship" //6
ship.position = CGPointMake(120, 160) //7

self.addChild(ship) //8

actionMoveUp = SKAction.moveByX(0, y: 30, duration: 0.2) //9
actionMoveDown = SKAction.moveByX(0, y: -30, duration: 0.2) //10
  1. We create a rectangle physics body for the ship, which has the same size as the ship node.

  2. Sets the categoryBitMask to be the shipCategory we defined earlier.

  3. Setting dynamic to true means that the physics engine will not control the movement of the ship.

  4. contactTestBitMask means what categories of objects this object should notify the contact listener when they intersect.

  5. On collision with missiles, we don’t want the ship to bounce off, so we set collisionBitMask to 0.

  6. Naming the ship node.

  7. Setting position of ship on screen.

  8. Adding ship as child node of the scene.

  9. Defining SKAction for moving ship up.

  10. Defining SKAction for moving ship down.

Next we have to open GameViewController.swift and update our viewDidLoad method. Replace the entire method with:

override func viewDidLoad() {
    super.viewDidLoad()
    let skView = self.view as SKView
    let myScene = GameScene(size: skView.frame.size)
    skView.presentScene(myScene)
}

We are now creating the scene from our viewDidLoad method instead of GameScene.sks. When you run the program again, you’ll see the white background and the spaceship on screen.

Sprite Kit Game in Swift Hero

Now let’s add the capability of moving the ship on its y axis based on where you touch the screen. In GameScene.swift, replace the touchesBegan method with:

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
    /* Called when a touch begins */

    for touch: AnyObject in touches {
        let location = touch.locationInNode(self) //1
        if location.y > ship.position.y { //2
            if ship.position.y < 300 { //3
                ship.runAction(actionMoveUp) //4
            }
        } else {
            if ship.position.y > 50 {
                ship.runAction(actionMoveDown) //5
            }
        }
    }
}

Here’s what’s happening in this method:

  1. Tracks the location of touches on the screen.

  2. If the location is higher than the ship’s location, then we want to move the ship up.

  3. Setting offset from the edge, so the ship completely stays in bounds of the scene.

  4. Calling actionMoveUp to move the ship up by 30 points.

  5. Calling actionMoveDown when touch location is below the ship’s current location.

Scrolling Background

Add the background velocity right below the collision detection categories.

let shipCategory = 0x1 << 1
let obstacleCategory = 0x1 << 2

let backgroundVelocity : CGFloat = 3.0

To make an endlessly scrolling background, make two background images instead of one and lay them side-by-side. Then, as you scroll both images from right to left and once one of the images goes off-screen, you simply put it back to the right.

Add this method in GameScene.swift to accomplish this:

func initializingScrollingBackground() {
    for var index = 0; index < 2; ++index {
        let bg = SKSpriteNode(imageNamed: "bg")
        bg.position = CGPoint(x: index * Int(bg.size.width), y: 0)
        bg.anchorPoint = CGPointZero
        bg.name = "background"
        self.addChild(bg)
    }
}

Call this method right after setting background color to white in didMoveToView.

self.backgroundColor = SKColor.whiteColor()
self.initializingScrollingBackground()

Next, we will add the method to move the background in GameScene.swift.

func moveBackground() {
    self.enumerateChildNodesWithName("background", usingBlock: { (node, stop) -> Void in
        if let bg = node as? SKSpriteNode {
            bg.position = CGPoint(x: bg.position.x - self.backgroundVelocity, y: bg.position.y)

            // Checks if bg node is completely scrolled off the screen, if yes, then puts it at the end of the other node.
            if bg.position.x <= -bg.size.width {
                bg.position = CGPointMake(bg.position.x + bg.size.width * 2, bg.position.y)
            }
        }
    })
}

This finds any child with the name “background” and moves it to the left according to the velocity.

Finally to scroll the background with every frame, we are going to replace the current update method in GameScene.swift with this:

override func update(currentTime: CFTimeInterval) {
    /* Called before each frame is rendered */

    self.moveBackground()
}   

Run the project and you should have a scrolling background.

Sprite Kit Game in Swift Final Product

Check out the second part of this tutorial, where we add the oncoming missiles and the end game screen.


Get job-ready in Swift »


This tutorial originally appeared on Kevin’s blog and is shared with permission. It was written in Swift and checked against Xcode 6.1.1 and iOS 8.1. Thanks to Megha Gulati for posting the original tutorial in Objective-C.