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.
Getting started
Start Xcode and select File\New\Project from the main menu. Select the iOS\Application\Game template and click Next.
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.
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.
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.
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 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
-
We create a rectangle physics body for the ship, which has the same size as the ship node.
-
Sets the
categoryBitMask
to be theshipCategory
we defined earlier. -
Setting dynamic to true means that the physics engine will not control the movement of the ship.
-
contactTestBitMask
means what categories of objects this object should notify the contact listener when they intersect. -
On collision with missiles, we don’t want the ship to bounce off, so we set
collisionBitMask
to0
. -
Naming the ship node.
-
Setting position of ship on screen.
-
Adding ship as child node of the scene.
-
Defining
SKAction
for moving ship up. -
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.
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:
-
Tracks the location of touches on the screen.
-
If the location is higher than the ship’s location, then we want to move the ship up.
-
Setting offset from the edge, so the ship completely stays in bounds of the scene.
-
Calling actionMoveUp to move the ship up by 30 points.
-
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.
Check out the second part of this tutorial, where we add the oncoming missiles and the end game screen.
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.