Sprite Kit for Kids with Swift

Sprite Kit for Kids with Swift

Hi! My name is Ajay, and I am a 13 year old iOS developer.

I enjoy making iOS games using Sprite Kit, Apple’s 2D game programming framework. I know a lot of kids are interested in learning how to do this too, so I thought I’d make this tutorial!

In this tutorial, I’ll show you how to make a simple game called Space Monkey. It will teach you the basics of making your own iPhone game, and hopefully get you excited ’bout learning more.

So strap on your jet boosters, and let’s get started!

Note: This tutorial assumes you know the basics of programming with Swift. If you are new to Swift, check out our book the iOS Apprentice or check out our video tutorials on Swift.

Getting Started

The first thing you need to do is to install Xcode – Apple’s free tool for developing iOS and Mac apps.

If you don’t have Xcode installed already, download it from the App Store. If you already have Xcode, make sure you have the latest version installed (Xcode 6 at the time of writing this tutorial).

Xcode

Once you have Xcode installed, download this starter project, unzip it, and double click SpaceMonkey.xcodeproj.

This will open the project in Xcode. Click the Play button to see what you’ve got so far:

SpaceMonkeyStarter

You will see a blank black screen:

LandscapeBlank

This starter project is a nice blank slate for you to start working on your game. I’ve already pre-included some art and sounds for you in the project, which you can find in the Sounds and sprites.atlas folders.

Let’s put that art to good use – starting with the space monkey!

Adding your Monkey

In Sprite Kit, to add an image (like the space monkey) to the game, you need to do three things:

  1. Create the sprite. A sprite is a copy of an image that you can move around in your game. Your first step is to create one of these, using the SKSpriteNode class.
  2. Position the sprite. The second thing you need to do is set the position of your sprite somewhere on the screen. In this game, you want the space monkey to start to the left hand side of the screen, toward the middle.
  3. Add the sprite to the scene. Creating the sprite isn’t enough – to display the sprite, you need to add it to the scene.

Let’s try this out step by step. Open GameScene.swift and replace the contents with the following code:

import SpriteKit
 
class GameScene: SKScene {
 
  // 1 - Create the sprite
  let player = SKSpriteNode(imageNamed:"spacemonkey_fly02")
 
  override func didMoveToView(view: SKView) {
 
    // 2 - Position the sprite
    player.position = CGPoint(x:frame.size.width * 0.1, y: frame.size.height * 0.5)
    // 3 - Add the sprite to the scene
    addChild(player)
 
    // 4 - Set scene background color to black
    backgroundColor = SKColor.blackColor()
  }
 
}

Let’s go over this code step by step.

  1. Create your SKSpriteNode with the monkey image given in the Art Folder, using SKSpriteNode(imageNamed:). Note you are storing the sprite in a property on the class, so you can refer to it later.
  2. didMoveToView(_:) is called when your scene is first presented, so it’s a good place to add initial setup code. Here you set your sprite’s position near the left hand side of the screen.
  3. Add the sprite to the scene by calling addChild() with the name of your sprite.
  4. Set the scene’s background color to black, to give a sense that the monkey is flying in space.

Build and run, and see your space monkey flying on the screen:

SpaceMonkeyOnScreen

Time to add some enemies!

Adding your Enemies!

Enemies will work a bit differently because you need more than one and you need them to spawn in different areas of the screen.

First, you’ll need some methods to create some random numbers. Add these new methods to GameScene.swift, right after didMoveToView(_:) (but before the final curly brace):

func random() -> CGFloat {
  return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
 
func random(#min: CGFloat, max: CGFloat) -> CGFloat {
  return random() * (max - min) + min
}

random() returns a random decimal value between 0 and 1. random(min:max:) returns a random number within a specified range. For this tutorial, you don’t need to understand how these work; you can just use them as helper methods.

Next, add this new method right after random(min:max:):

// 1
func spawnEnemy() {
  // 2
  let enemy = SKSpriteNode(imageNamed: "boss_ship")
  // 3
  enemy.name = "enemy"
  // 4
  enemy.position = CGPoint(x: frame.size.width, y: frame.size.height * random(min: 0, max: 1))
  // 5
  addChild(enemy)
}

Let’s go over this line by line:

  1. Here you create a method named spawnEnemy().
  2. Here you create a sprite like you did before when you added the monkey, by calling SKSpriteNode(imageNamed:) and adding the image name of the image.
  3. Here you add a name to the sprite – this will make it easy to find the sprite by name later on.
  4. Here you set the enemies X position to the right of the screen, but the Y position to a random value using random().
  5. Lastly you just add the sprite to the parent/scene using addChild().

Now all you need to do is call this method a bunch of times! To do this, you will create a sequence of actions to make the enemies spawn periodically over time.

Add these lines to the end of didMoveToView(_:):

runAction(SKAction.repeatActionForever(
  SKAction.sequence([
    SKAction.runBlock(spawnEnemy),
    SKAction.waitForDuration(1.0)])))

Here you create an action with a sequence which repeats itself over and over. The sequence consists of calling spawnEnemy() and a wait duration of 1 second. This makes an enemy spawns every second.

At this point, your file should look like this (with the comments deleted):

import SpriteKit
 
class GameScene: SKScene {
 
  let player = SKSpriteNode(imageNamed:"spacemonkey_fly02")
 
  override func didMoveToView(view: SKView) {
 
    player.position = CGPoint(x:frame.size.width * 0.1, y: frame.size.height * 0.5)
    addChild(player)
    backgroundColor = SKColor.blackColor()
 
    runAction(SKAction.repeatActionForever(
      SKAction.sequence([
        SKAction.runBlock(spawnEnemy),
        SKAction.waitForDuration(1.0)])))
  }
 
  func random() -> CGFloat {
    return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
  }
 
  func random(#min: CGFloat, max: CGFloat) -> CGFloat {
    return random() * (max - min) + min
  }
 
  func spawnEnemy() {
    let enemy = SKSpriteNode(imageNamed: "boss_ship")
    enemy.name = "enemy"
    enemy.position = CGPoint(x: frame.size.width, y: frame.size.height * random(min: 0, max: 1))
    addChild(enemy)
  }
 
}

Build and run your app. You should start seeing enemies appear on the right hand side at random spots:

iOS Simulator Screen Shot Feb 25, 2015, 12.36.33 PM

Now let’s make them move!

Making your Enemies Move

First, let’s tweak the enemies so when they spawn they are fully offscreen (rather than being halfway visible on the screen). This will make the player not realize they’re popping in from nowhere!

To fix this, update the line in spawnEnemy() that sets the enemy sprite’s position to the following:

enemy.position = CGPoint(x: frame.size.width + enemy.size.width/2, 
  y: frame.size.height * random(min: 0, max: 1))

Now, let’s make your game a bit more interesting by making your enemies move from one side of your screen to the other by using more actions.

Add this line to the end of spawnEnemy():

enemy.runAction(
  SKAction.moveByX(-size.width - enemy.size.width, y: 0.0, 
    duration: NSTimeInterval(random(min: 1, max: 2))))

Let me explain this code to you :

  1. It runs an SKAction on the enemy sprite.
  2. This SKAction contains a moveByX() method which tells the enemy to move on the X axis by a certain amount. Here you set it to move the entire length of the screen to the left (-size.width) and also the full length of the sprite (-enemy.size.width).
  3. The SKAction has a duration parameter which indicates how long it should take for the sprite to move that amount. Here, you set it to move a random value between 1-2 seconds, so some enemies will be faster than others.

Build and run your app to see if the enemies are moving to the end of your screen and disappearing completely. It should look something like this:

Screen Shot 2015-03-02 at 10.38.34 pm

Now you need to make your player move with physics!

Moving Sprites with Physics

In this game, you are going to make it so that the monkey falls to the bottom of the screen, unless you tap the screen – in which case the monkey will jump up.

You could do this by moving the monkey with a SKAction, just like you did for the enemies, but it’s easier to let Sprite Kit’s built-in physics engine move the monkey instead.

Let’s try this out. Still in GameScene.swift, and add the following code to the end of didMoveToView(_:):

player.physicsBody = SKPhysicsBody(circleOfRadius:player.frame.size.width * 0.3)
player.physicsBody?.allowsRotation = false

The first line creates a physics body for the monkey. This tells the physics engine to start controlling the monkey’s movement, making him “fall” over time according to gravity and other forces.

Note the shape of the physics body is a circle, that roughly matches up to the monkey. You don’t need physics shapes to match exactly, just whatever works for your game. You also specify that the physics body shouldn’t rotate.

Build and run, and watch as your monkey falls down and off the screen. Cool, huh?

Demo of dragging art directly into Scene

Physics Impulses

It’s no fair if the monkey falls right off the screen, so you’re going to use physics to make the monkey jump.

To do this, add this new method after spawnEnemy():

func jumpPlayer() {
  // 1
  let impulse =  CGVector(dx: 0, dy: 75)
  // 2
  player.physicsBody?.applyImpulse(impulse)
}

Let’s go over this line by line:

  1. First you need to specify the amount to make the monkey jump, by creating a CGVector with an impulse amount. I came up with these values by trial and error.
  2. Then you use applyImpulse(), passing in the impulse you provided earlier. This will convert the impulse into both linear and angular velocity so it will move through space and also rotate, that’s why you locked the rotation earlier.

This code has not been called yet so you cannot make your player jump yet. To actually make your player jump you need to override a method that is called when the player taps the screen. Copy this code under jumpPlayer():

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
  jumpPlayer()
}

This simply calls the method you wrote whenever the user taps.

Almost done – add this code to the end of didMoveToView(_:):

// 1
let collisionFrame = CGRectInset(frame, 0, -self.size.height * 0.2)
// 2
physicsBody = SKPhysicsBody(edgeLoopFromRect: collisionFrame)

This code creates a special physics body on the edge of the screen so the monkey doesn’t fly or fall of into space. Let’s review this line by line:

  1. First you create a rectangle that specifies the bounds of the monkey’s movement. CGRectInset() allows you to grow or shrink a rectangle; you use it to grow the frame of the screen by 20% above and below the area, to let the monkey go slightly offscreen but not a crazy amount.
  2. You then set the physics body of the scene itself. Last time you created a physics body that was a circle shape; here you make it an edge loop, which is a fancy way of saying “the edges of a rectangle”.

Build and run your project, and you should see the following:

Demo of dragging art directly into Scene

Holy bouncing monkey!

Collision Detection

Now you can jump to avoid enemies… but if you crash into them, nothing happens.

You need to add collision detection into your game. To do this, here’s what you need to do:

  1. Create a physics body for each sprite: Right now, you’ve created a physics body for your space monkey, but you haven’t created one for the enemies – so you’ll need to create physics bodies for them too.
  2. Set category and contacts for each physics body: Next you need to set a category on each physics body, which tells Sprite Kit which group the sprite is in. This way, you can have one category for the player and one category for the enemy. You can also configure your physics body to register “contacts” with certain other groups of physics bodies.
  3. Set Contact Delegate: Using the Contact Delegate you can set a contact delegate on the world to be told when two physics bodies contact with each other. Then you find out which category the physics body is in, and if it is the Enemy and the Monkey. Game Over!

Remember how you added the physics body to your monkey? Well now you need to add it to your enemy sprite so they can actually collide.

Start by adding this enumeration to the very top of GameScene.swift.

enum BodyType: UInt32 {
  case player = 1
  case enemy = 2
  case ground = 4
}

All you do here is create categories for each sprite. The ground number is not for a sprite but is instead for the frame of the app so if the monkey collides with the frame it bounces of the frame so the monkey will not keep falling!

Note: Note how each number you add to this list is the previous number, multiplied by 2. The reasoning for this is beyond the scope of this tutorial, but if you want to learn more, check out this video tutorial on bitmasks.

Next, mark GameScene as implementing the SKPhysicsContactDelegate protocol:

class GameScene: SKScene, SKPhysicsContactDelegate {

You can think of a protocol as a promise that your class will implement certain methods. Here you’re promising that you’ll be implementing a method that handles when two physics bodies contact each other.

After this make the contactDelegate of the physics world equal to self. Add this code to the end of didMoveToView(_:):

physicsWorld.contactDelegate = self

This tells the physics world to call a method on your class when two physics bodies contact each other (the one you will write soon).

Now add this code to the end of spawnEnemy():

// 1
enemy.physicsBody = SKPhysicsBody(circleOfRadius: enemy.size.width/4)
// 2
enemy.physicsBody?.dynamic = false
// 3
enemy.physicsBody?.affectedByGravity = false
// 4
enemy.physicsBody?.allowsRotation = false
// 5
enemy.physicsBody?.categoryBitMask = BodyType.enemy.rawValue
// 6
enemy.physicsBody?.contactTestBitMask = BodyType.player.rawValue
// 7
enemy.physicsBody?.collisionBitMask = 0

Let me explain this code a little.

  1. Creates a physics body for the enemy. Physics bodies don’t have to be an exact match to the shape of the sprite; just an estimate. Here you use a circle shape, setting the radius to 1/4’s of the sprite’s width to make the collision a bit forgiving.
  2. Turns dynamic off. This allows the physics to control the sprite.
  3. Stops sprite from being affected by gravity. This is self explanatory, basically the enemy sprite will not be affected by gravity produced by the physics.
  4. Stops sprite from rotating. This code will stop the object from rotating when collided by other physics bodies.
  5. Sets the category bit mask to be the enemy category you defined earlier.
  6. This instructs Sprite Kit to notify you when an enemy contacts a player.
  7. The collisionBitMask means, if you for example set the collisionBitMask to the player, the player and the enemy will bounce of each other. You don’t want anything to bounce of anything so you set it to 0.

Now add this to the end of didMoveToView(_:):

physicsBody?.categoryBitMask = BodyType.ground.rawValue
 
player.physicsBody?.categoryBitMask = BodyType.player.rawValue
player.physicsBody?.contactTestBitMask = BodyType.enemy.rawValue
player.physicsBody?.collisionBitMask = BodyType.ground.rawValue

This sets up the categories and collision bit masks for the player and ground so they collide with each other, and sets the player to register a “contact” with enemies.

Now for the most important part. Actually doing something about the collision. For this, you need to implement the method you “promised” to implement earlier to handle contacts:

func didBeginContact(contact: SKPhysicsContact) {
  let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
  switch(contactMask) {
  case BodyType.player.rawValue | BodyType.enemy.rawValue:
    let secondNode = contact.bodyB.node
    secondNode?.removeFromParent()
    let firstNode = contact.bodyA.node
    firstNode?.removeFromParent()
  default:
    return
  }
}

Since you set the scene as the contactDelegate of the physics world earlier, this method will be called whenever two physics bodies collide.

This method combines the two bitmasks into a single contact mask, and checks to see if they are the combination of the player and the enemy. If so, it removes the player and the enemy from the scene.

Build and run, and you’ll see the following:

Demo of dragging art directly into Scene

That is AWESOME!!!

In the next section you have to quite a lot of work, you have to change basically everything. Well thats going to be fun. You will mainly add a game over screen.

Game Over!

In this section you will display a game over Screen to the player when your player either collides with an enemy or falls out of the screen. You will also create a start screen where the user is instructed to Touch the screen to begin! :]

Start by adding all these variables to the top of GameScene, right after the line let player = SKSpriteNode(imageNamed:"spacemonkey_fly02"):

// 1
var gameOver = false
// 2
let endLabel = SKLabelNode(text: "Game Over")
let endLabel2 = SKLabelNode(text: "Tap to restart!")
let touchToBeginLabel = SKLabelNode(text: "Touch to begin!")
let points = SKLabelNode(text: "0")
// 3
var numPoints = 0
// 4
let explosionSound = SKAction.playSoundFileNamed("explosion.mp3", waitForCompletion: true)
let coinSound = SKAction.playSoundFileNamed("coin.wav", waitForCompletion: false)

Let’s review this section by section:

  1. First you create a bool called gameOver to keep track whether the game is over or not.
  2. Next you create a few label nodes, which is how you display text to the screen in Sprite Kit.
  3. Next you create an integer to store the amount of points you have. Note you mark this with var, not let, because you want to be able to change the value after creating it.
  4. Last you create some actions you will use later to play some sound effects.

Next create a new method called setupLabels() :

func setupLabels() {
  // 1
  touchToBeginLabel.position = CGPoint(x: frame.size.width/2, y: frame.size.height/2)
  touchToBeginLabel.fontColor = UIColor.whiteColor()
  touchToBeginLabel.fontSize = 50
  addChild(touchToBeginLabel)
 
  // 2  
  points.position = CGPoint(x: frame.size.width/2, y: frame.size.height * 0.1)
  points.fontColor = UIColor.whiteColor()
  points.fontSize = 100
  addChild(points)
}

Let’s review this section by section:

  1. You position the “touch to begin” label to the center of the screen, with a white color, and 50pt font size.
  2. You set the position label toward the bottom of the screen, with a white color and a font size of 100.

Now call the setupLabels() in didMoveToView(_:):

setupLabels()

Now delete touchesBegan(_:withEvent:) and replace it with this :

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
  // 1
  if (!gameOver) {
    if player.physicsBody?.dynamic == false {
      player.physicsBody?.dynamic = true
      touchToBeginLabel.hidden = true
      backgroundColor = SKColor.blackColor()
 
      runAction(SKAction.repeatActionForever(
        SKAction.sequence([
          SKAction.runBlock(spawnEnemy),
          SKAction.waitForDuration(1.0)])))
    }
    // 2
    jumpPlayer()
 
  }
  // 3
  else if (gameOver) {
    let newScene = GameScene(size: size)
    newScene.scaleMode = scaleMode
    let reveal = SKTransition.flipHorizontalWithDuration(0.5)
    view?.presentScene(newScene, transition: reveal)
  }
}

Let’s review this section by section:

  1. If the game isn’t over, and the player isn’t dynamic (i.e. moved by the physics engine) yet, then that means the game hasn’t started yet. So you set dynamic to true, hide the label, and begin spawning enemies.
  2. You call the jumpPlayer anyway because it will only work if dynamic is set to true.
  3. If the game is over, you start the game over by creating a new instance of your GameScene and presenting that new scene..

Next, add this new method to your class:

override func update(currentTime: CFTimeInterval) {
  //1
  if !gameOver {
    //2
    if player.position.y <= 0 {
      endGame()
    }
    //3
    enumerateChildNodesWithName("enemy") {
      enemy, _ in
      //4
      if enemy.position.x <= 0 {
        //5
        self.updateEnemy(enemy)
      }
    }
  }
}

Let’s review this section by section:

  1. You check if gameOver is false and if it is then you check if the player is out of the screen, if the player is out of the screen it calls endGame which you will add later.
  2. Your tell enumerateChildNodesWithName to find every single child in the scene with the name of “enemy”. For each enemy, you check if any of the enemies position is out of the screen and if it is, it calls updateEnemy() which you will also add later.

Now you will add the method called updateEnemy(). This which will be called before every frame is rendered so if any enemy leave the screen you can give a point to the user:

func updateEnemy(enemy: SKNode) {
  //1
  if enemy.position.x < 0 {
    //2
    enemy.removeFromParent()  
    //3
    runAction(coinSound)
    //4
    numPoints++
    //5
    points.text = "\(numPoints)"  
  }
}

Let’s review this line by line:

  1. Checks if the enemy position is out of the screen in x axis.
  2. If it is, it removes the enemy from the parent (removes the enemy from the game).
  3. Adds a point to the numPoints which holds the amount of points you have.
  4. Converts points into a string and inserts that string into the points label.

Now you have to change didBeginContact(_:) a little bit. Add this line of code after you remove the first node from parent:

endGame()

Now for the part you have all been waiting for – the endGame method:

func endGame() {
  // 1
  gameOver = true
  // 2
  removeAllActions()
  // 3
  runAction(explosionSound)
 
  // 4
  endLabel.position = CGPoint(x: frame.size.width/2, y: frame.size.height/2)
  endLabel.fontColor = UIColor.whiteColor()
  endLabel.fontSize = 50
  endLabel2.position = CGPoint(x: frame.size.width/2, y: frame.size.height/2 + endLabel.fontSize)
  endLabel2.fontColor = UIColor.whiteColor()
  endLabel2.fontSize = 20
  points.fontColor = UIColor.whiteColor()
  addChild(endLabel)
  addChild(endLabel2)
}

Let’s review this section by section:

  1. You set gameOver to true.
  2. You remove all actions in the scene, to stop any further animation.
  3. Runs the explosion sound.
  4. Position and adds the endLabels to the scene.

Now go to didMoveToView(_:) and remove this block of code :

backgroundColor = SKColor.blackColor()
 
runAction(SKAction.repeatActionForever(
  SKAction.sequence([
    SKAction.runBlock(spawnEnemy),
    SKAction.waitForDuration(1.0)])))

Finally add this line in its place:

player.physicsBody?.dynamic = false

This sets the player so he doesn’t move at the start of the game until you tap.

Build and run, and you have a completely playable game!

OMG – that’s your game done, good job! 😀

Gratuitous Background Music

But wait, there’s one more thing!

Open ViewController.swift and add this new property:

var backgroundMusicPlayer: AVAudioPlayer!

Also add this new method:

func playBackgroundMusic(filename: String) {
  let url = NSBundle.mainBundle().URLForResource(
    filename, withExtension: nil)
  if (url == nil) {
    println("Could not find file: \(filename)")
    return
  }
 
  var error: NSError? = nil
  backgroundMusicPlayer =
    AVAudioPlayer(contentsOfURL: url, error: &error)
  if backgroundMusicPlayer == nil {
    println("Could not create audio player: \(error!)")
    return
  }
 
  backgroundMusicPlayer.numberOfLoops = -1
  backgroundMusicPlayer.prepareToPlay()
  backgroundMusicPlayer.play()
}

This is a handy helper method to play some background music – you don’t need ot know how it works for this tutorial.

Using it is simple. Add this line the viewWillLayoutSubviews(), right after the line skView.presentScene(scene):

playBackgroundMusic("BackgroundMusic.mp3")

Build and run, and enjoy your awesome music made by yours truly… aww yeah!

Where To Go From Here?

Here is the final example project that you created in this tutorial.

Congratulations, you have made your own game! From here, feel free to modify it however you’d like.

If you want to learn more about Sprite Kit, check out some of our other Sprite Kit Tutorials, or check out our book iOS Games by Tutorials.

Happy gaming! :]

Sprite Kit for Kids with Swift is a post from: Ray Wenderlich

The post Sprite Kit for Kids with Swift appeared first on Ray Wenderlich.

4
Like
Save

Comments

Write a comment

*