How To Make a Game Like Space Invaders with Sprite Kit and Swift Tutorial: Part 2

How To Make a Game Like Space Invaders with Sprite Kit and Swift Tutorial: Part 2
Learn how to make a game like Space Invaders!

Learn how to make a game like Space Invaders!

Update note: This tutorial was updated for iOS 8 and Swift by Riccardo D’Antoni. Original tutorial by Joel Shapiro.

Welcome back to our 2-part Sprite Kit tutorial that teaches you how to make a game like Space Invaders!

In the first part, you created the foundation of the game. So far, you’ve added the invaders, your ship, and a Heads Up Display (HUD) to your game. You also coded the logic to make the invaders move automatically and to make your ship move as you tilted your device.

In this second and final part, you’ll add the ability for your ship and the aliens to fire on each other and blow each other up! You’ll also polish your game by adding sound effects and realistic images to replace the colored rectangles that currently serve as place holders for the invaders and your ship.

This tutorial picks up where the first part left off. If you don’t have the project already, you can download the example project where we left things off.

All right, it’s time to blow up some invaders!

Making Your Ship Fire its Laser Cannon

First, think about how you want your scene to fire the ship’s laser cannon. You decided to use single-taps to fire the cannon. But how should you detect these single taps?

You have two obvious choices:

  1. UITapGestureRecognizer – Create one and attach it to your scene’s view.
  2. touchesEnded(_:,withEvent:) – Your scene is a subclass of UIResponder; therefore, you could use this to detect touches directly.

The second approach is the best choice in this situation: touchesEnded(). The first approach gets a bit tricky when you need to detect and handle touches differently in the various scene nodes of your game, since you can only specify a single callback selector for the UITapGestureRecognizer on the scene’s view. The extra work to get this working properly just isn’t worth it in this simple case.

Since all SKNode nodes (including SKScene) can handle touches directly via touchesEnded(), the second choice is a much more natural approach to handling node-specific touches — and will pay off when you develop games with more complex tap handling.

Now that you’re going to detect user taps in your scene’s touchesEnded() method, what should you do inside that method?

Taps can happen at any point during the gameplay. Contrast that with the way your scene changes: — at discrete intervals from within the update() method. So how can you save up taps detected at any time in touchesEnded() and process them later in update() when it’s invoked by the Sprite Kit game loop?

The answer is a queue! You’re going to use a simple Array to store your taps in a FIFO (First In First Out) queue.

Add the following property at the top of GameScene.swift in order to initializes the tap queue to an empty array:

var tapQueue: Array<Int> = []

Now add the following lines to didMoveToView() right after the motionManager.startAccelerometerUpdates() line:

userInteractionEnabled = true

The above code ensures that user interactions are enabled for the scene so it can receive tap events.

Now, add the following code right after // User Tap Helpers:

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
  // Intentional no-op
}
 
override func touchesMoved(touches: NSSet, withEvent event: UIEvent)  {
  // Intentional no-op
}
 
override func touchesCancelled(touches: NSSet, withEvent event: UIEvent) {
  // Intentional no-op
}
 
override func touchesEnded(touches: NSSet, withEvent event: UIEvent)  {
 
  if let touch : AnyObject = touches.anyObject() {
 
    if (touch.tapCount == 1) {
 
      // add a tap to the queue
      self.tapQueue.append(1)
    }
  }
}

The first three methods are just empty stubs; they’re added because Apple suggests doing so when you override touchesEnded() without calling super().

The touchesEnded() method itself is fairly simple. It just adds an entry to the queue. You don’t need a custom class to store the tap in the queue since all you need to know is that a tap occurred. Therefore, you use the integer 1 as a mnemonic for single tap.

Since you know that the invaders will also eventually fire bullets at your ship, add the following enum to the top of the class:

enum BulletType {
  case ShipFiredBulletType
  case InvaderFiredBulletType
}

You’re going to use BulletType to share the same bullet code for both invaders and your ship. It appears that you and the invaders shop at the same ammunition stores! :]

Next, add the following properties:

let kShipFiredBulletName = "shipFiredBullet"
let kInvaderFiredBulletName = "invaderFiredBullet"
let kBulletSize = CGSizeMake(4, 8)

Now, add the following method at the end of the “Scene Setup and Content Creation” section:

func makeBulletOfType(bulletType: BulletType) -> SKNode! {
 
  var bullet: SKNode!
 
  switch (bulletType) {
  case .ShipFiredBulletType:
    bullet = SKSpriteNode(color: SKColor.greenColor(), size: kBulletSize)
    bullet.name = kShipFiredBulletName
  case .InvaderFiredBulletType:
    bullet = SKSpriteNode(color: SKColor.magentaColor(), size: kBulletSize)
    bullet.name = kInvaderFiredBulletName
    break;
  default:
    bullet = nil
  }
 
  return bullet
}

This method is relatively straightforward: it simply creates a rectangular colored sprite to represent a bullet and sets the name of the bullet so you can find it later in your scene.

Now, add the following methods to the // Bullet Helpers section:

func fireBullet(bullet: SKNode, toDestination destination:CGPoint, withDuration duration:CFTimeInterval, andSoundFileName soundName: String) {
 
  // 1
  let bulletAction = SKAction.sequence([SKAction.moveTo(destination, duration: duration), SKAction.waitForDuration(3.0/60.0), SKAction.removeFromParent()])
 
  // 2
  let soundAction = SKAction.playSoundFileNamed(soundName, waitForCompletion: true)
 
  // 3
  bullet.runAction(SKAction.group([bulletAction, soundAction]))
 
  // 4
  self.addChild(bullet)
}
 
func fireShipBullets() {
 
  let existingBullet = self.childNodeWithName(kShipFiredBulletName)
 
  // 1
  if existingBullet == nil {
 
    if let ship = self.childNodeWithName(kShipName) {
 
      if let bullet = self.makeBulletOfType(.ShipFiredBulletType) {
 
        // 2
        bullet.position =
          CGPointMake(ship.position.x, ship.position.y + ship.frame.size.height - bullet.frame.size.height / 2)
 
        // 3
        let bulletDestination = CGPointMake(ship.position.x, self.frame.size.height + bullet.frame.size.height / 2)
 
        // 4
        self.fireBullet(bullet, toDestination: bulletDestination, withDuration: 1.0, andSoundFileName: "ShipBullet.wav")
 
      }
    }
  }
}

Going through the code in fireBullet() step-by-step, you do the following:

  1. Create an SKAction that moves the bullet to the desired destination and then removes it from the scene. This sequence executes the individual actions consecutively — the next action only takes place after the previous action has completed. Hence the bullet is removed from the scene only after it has been moved.
  2. Play the desired sound to signal that the bullet was fired. All sounds are included in the starter project and iOS knows how to find and load them.
  3. Move the bullet and play the sound at the same time by putting them in the same group. A group runs its actions in parallel, not sequentially.
  4. Fire the bullet by adding it to the scene. This makes it appear onscreen and starts the actions.

Here’s what you do in fireShipBullets():

  1. Only fire a bullet if there isn’t one currently on-screen. It’s a laser cannon, not a laser machine gun — it takes time to reload!
  2. Set the bullet’s position so that it comes out of the top of the ship.
  3. Set the bullet’s destination to be just off the top of the screen. Since the x coordinate is the same as that of the bullet’s position, the bullet will fly straight up.
  4. Fire the bullet!

The decision in //1 to only allow one ship bullet on-screen at the same time is a gameplay decision, not a technical necessity. If your ship can fire thousands of bullets per minute, Space Invaders would be too easy. Part of the fun of your game is choosing your shots wisely and timing them to collide with invaders.

Your laser cannon is almost ready to fire!

Add the following to the Scene Update Helpers section:

func processUserTapsForUpdate(currentTime: CFTimeInterval) {
  // 1
  for tapCount in self.tapQueue {
    if tapCount == 1 {
      // 2
      self.fireShipBullets()
    }
    // 3
    self.tapQueue.removeAtIndex(0)
  }
}

Let’s review the above code:

  1. Loop over your tapQueue.
  2. If the queue entry is a single-tap, handle it. As the developer, you clearly know that you only handle single taps for now, but it’s best to be defensive against the possibility of double-taps (or other actions) later.
  3. Remove the tap from the queue.

Note: processUserTapsForUpdate() completely consumes the queue of taps at each invocation. Combined with the fact that fireShipBullets() will not fire another bullet if one is already onscreen, this emptying of the queue means that extra or rapid-fire taps will be ignored. Only the first tap needed to fire a bullet will matter.

Finally, add the following code as the first line in update():

processUserTapsForUpdate(currentTime)

This invokes processUserTapsForUpdate() during the update loop and processes any user taps.

Build your game, run, and fire away!

laser_test

Making Invaders Attack

Awesome, your ship can finally fire on those evil invaders! You’ll have them on the run soon enough.

But you’ve probably noticed that your bullets pass straight through invaders instead of blowing them up. That’s because your bullets aren’t yet smart enough to detect when they’ve hit an invader. You’re going to fix that in a moment.

First, you’ll make the invaders return fire by adding the code below to the // Scene Update Helpers section:

func fireInvaderBulletsForUpdate(currentTime: CFTimeInterval) {
 
  let existingBullet = self.childNodeWithName(kInvaderFiredBulletName)
 
  // 1
  if existingBullet == nil {
 
    var allInvaders = Array<SKNode>()
 
    // 2
    self.enumerateChildNodesWithName(kInvaderName) {
      node, stop in
 
      allInvaders.append(node)
    }
 
    if allInvaders.count > 0 {
 
      // 3
      let allInvadersIndex = Int(arc4random_uniform(UInt32(allInvaders.count)))
 
      let invader = allInvaders[allInvadersIndex]
 
      // 4
      let bullet = self.makeBulletOfType(.InvaderFiredBulletType)
      bullet.position = CGPointMake(invader.position.x, invader.position.y - invader.frame.size.height / 2 + bullet.frame.size.height / 2)
 
      // 5
      let bulletDestination = CGPointMake(invader.position.x, -(bullet.frame.size.height / 2))
 
      // 6
      self.fireBullet(bullet, toDestination: bulletDestination, withDuration: 2.0, andSoundFileName: "InvaderBullet.wav")
    }
  }
}

The central logic for the above method is as follows:

  1. Only fire a bullet if one’s not already on-screen.
  2. Collect all the invaders currently on-screen.
  3. Select an invader at random.
  4. Create a bullet and fire it from just below the selected invader.
  5. The bullet should travel straight down and move just off the bottom of the screen.
  6. Fire off the invader’s bullet.

Add the following line to the end of update():

fireInvaderBulletsForUpdate(currentTime)

This invocation of fireInvaderBulletsForUpdate() starts the invaders firing back at you.

Build, run, and you should see the invaders firing their purple bullets at your ship, as shown in the screenshot below:

laser_foe_test

As a matter of game design, notice that the invaders’ bullets are purple while your ship’s bullets are green. This strong color contrast makes it easy to see the difference between bullets in the heat of battle.

Also, you should hear a different sound when your ship fires versus when an invader fires. The use of different sounds is partly stylistic, to give your game rich audio and make it more immersive. But it’s also partly an accessibility issue since 7 – 10% of men and 0.5% – 1% women are color blind. The differentiation in sound effects will make your game more playable by those who are color blind.

Detecting When Bullets Hit Their Target

With all those bullets flying around on the screen it’s amazing that nothing blows up! That’s because your game has no hit detection. It needs to detect when your ship’s bullets hit an invader — and when an invader’s bullet hits your ship.

You could do this manually, comparing bullet/invader/ship positions at each update() invocation and checking for hits. But why not let Sprite Kit do the work for you?

Since you’re already using physics bodies, Sprite Kit’s physics engine can detect when one body hits another. For this, you’ll use contact detection — not collision detection. You’re not using physics to move bullets or invaders, so you’re not interested in the physical collisions between them. Contact detection merely detects when one physics body overlaps another in space; it doesn’t otherwise move or affect the bodies in contact.

Some games have many distinct types of physics bodies and are not interested in contact between all types of physics bodies. Sprite Kit will only check for contact between those categories of physics bodies that you tell it to check.

This is both a speed optimization and a correctness constraint, as some types of contact may not be desired. Controlling which physics bodies are checked for contact begins by defining category bitmasks.

Add the following new properties to GameScene:

let kInvaderCategory: UInt32 = 0x1 << 0
let kShipFiredBulletCategory: UInt32 = 0x1 << 1
let kShipCategory: UInt32 = 0x1 << 2
let kSceneEdgeCategory: UInt32 = 0x1 << 3
let kInvaderFiredBulletCategory: UInt32 = 0x1 << 4

These strange-looking constants are bitmasks. A bitmask is basically a way of stuffing multiple on/off variables into a single 32-bit unsigned integer. A bitmask can have 32 distinct values when stored as a UInt32. Each of these five categories defines a type of physics body. Notice how the number to the right of the << operator is different in each case; that guarantees each bitmask is unique and distinguishable from the others.

Add the following code to createContent() right after the line that creates the physics body:

physicsBody!.categoryBitMask = kSceneEdgeCategory

This new code sets the category for the physics body of your scene.

Add the following code to makeShip() right before the return ship line to set up the categories for your ship:

// 1
ship.physicsBody!.categoryBitMask = kShipCategory
// 2
ship.physicsBody!.contactTestBitMask = 0x0
// 3
ship.physicsBody!.collisionBitMask = kSceneEdgeCategory

Here’s the breakdown of the above code:

  1. Set the ship’s category.
  2. Don’t detect contact between the ship and other physics bodies.
  3. Do detect collisions between the ship and the scene’s outer edges.

Note: You didn’t need to set the ship’s collisionBitMask before because only your ship and the scene had physics bodies. The default collisionBitMask of “all” was sufficient in that case.

Since you’ll be adding physics bodies to invaders next, setting your ship’s collisionBitMask precisely ensures that your ship will only collide with the sides of the scene and won’t also collide with invaders.

While you’re at it, you should set the category for the invaders since this will help detect collisions between your ship’s bullets and the invaders.

Add the following to the end of makeInvaderOfType() right before the return invader line:

invader.physicsBody = SKPhysicsBody(rectangleOfSize: invader.frame.size)
invader.physicsBody!.dynamic = false
invader.physicsBody!.categoryBitMask = kInvaderCategory
invader.physicsBody!.contactTestBitMask = 0x0
invader.physicsBody!.collisionBitMask = 0x0

This code gives your invader a physics body and identifies it as an invader using kInvaderCategory. It also indicates that you don’t want invaders to contact or collide with other entities.

Your next step is to categorize bullets and set their contact and collision masks.

Add the following inside the case .ShipFiredBulletType clause of makeBulletOfType():

bullet.physicsBody = SKPhysicsBody(rectangleOfSize: bullet.frame.size)
bullet.physicsBody!.dynamic = true
bullet.physicsBody!.affectedByGravity = false
bullet.physicsBody!.categoryBitMask = kShipFiredBulletCategory
bullet.physicsBody!.contactTestBitMask = kInvaderCategory
bullet.physicsBody!.collisionBitMask = 0x0

The above code identifies ship-fired bullets as such and tells Sprite Kit to check for contact between ship-fired bullets and invaders, but that collisions should be ignored.

That takes care of the ship’s bullets — now on to the invaders’ bullets!

Add the following inside the case .InvaderFiredBulletType clause of makeBulletOfType():

bullet.physicsBody = SKPhysicsBody(rectangleOfSize: bullet.frame.size)
bullet.physicsBody!.dynamic = true
bullet.physicsBody!.affectedByGravity = false
bullet.physicsBody!.categoryBitMask = kInvaderFiredBulletCategory
bullet.physicsBody!.contactTestBitMask = kShipCategory
bullet.physicsBody!.collisionBitMask = 0x0

This code is similar to the previous block: it identifies invader-fired bullets as such and tells Sprite Kit to check for contact between invader-fired bullets and your ship, and again, ignores the collision aspect.

Note: In order for contact detection to work, the ship-fired bullets must be defined as dynamic by setting bullet.physicsBody.dynamic = true. If not, Sprite Kit won’t check for contact between these bullets and the static invaders as their definition is invader.physicsBody.dynamic = false.

Invaders are static because they aren’t moved by the physics engine. Sprite Kit won’t check for contact between two static bodies, so if you need to check for contact between two categories of physics bodies, at least one of the categories must have a dynamic physics body.

You may be wondering why the contactTestBitMask values are not symmetrical. For example, why are you setting an invader’s contactTestBitMastk = 0x0 but a ship-fired bullet’s contactTestBitMask = kInvaderCategory?

The reason is that when Sprite Kit checks for contact between any two physics bodies A and B, only one of the bodies needs to declare that it should test for contact with the other, not both. As long as either A declares that it can contact with B, or B declares that it can contact with A, contact will be detected. It’s not necessary for both bodies to declare that they should test for contact with the other.

Setting the contactTestBitMask on only one type of body like you’ve done seems more manageable. You might prefer to set contactTestBitMask values on both types of bodies, and that’s fine, as long as you’re consistent in choosing one approach or the other.

With these changes, your game’s physics engine will detect contact between ship-fired bullets and the invaders, and between invader-fired bullets and your ship. But how does the physics engine inform your game of these contacts?

The answer is to use SKPhysicsContactDelegate.

Implementing the Physics Contact Delegate Methods

Still in GameScene.swift, modify the class line to look like the following:

class GameScene: SKScene, SKPhysicsContactDelegate {

This declares your scene as a delegate for the physics engine. The didBeginContact() method of SKPhysicsContactDelegate executes each time two physics bodies make contact, based on how you set your physics bodies’ categoryBitMask and contactTestBitMask. You’ll implement didBeginContact in just a moment.

Much like taps, contact can happen at any time. Consequently, didBeginContact can execute at any time. But in keeping with your discrete time ticks, you should only process contact during those ticks when update is called. So, just like taps, you’ll create a queue to store contacts until they can be processed via update.

Add the following new property at the top of the class:

var contactQueue = Array<SKPhysicsContact>()

Now add the following code to the end of didMoveToView(), right after the userInteractionEnabled = true line:

physicsWorld.contactDelegate = self

This just initializes an empty contact queue and sets the scene as the contact delegate of the physics engine.

Next, add this method to the // Physics Contact Helpers section:

func didBeginContact(contact: SKPhysicsContact!) {
  if contact != nil {
    self.contactQueue.append(contact)
  }
}

This method simply records the contact in your contact queue to handle later when update() executes.

Still in the same section, add the following method:

func handleContact(contact: SKPhysicsContact) {
  //1
  // Ensure you haven't already handled this contact and removed its nodes
  if (contact.bodyA.node?.parent == nil || contact.bodyB.node?.parent == nil) {
    return
  }
 
  var nodeNames = [contact.bodyA.node!.name!, contact.bodyB.node!.name!]
 
  // 2
  if (nodeNames as NSArray).containsObject(kShipName) && (nodeNames as NSArray).containsObject(kInvaderFiredBulletName) {
 
    // 3
    // Invader bullet hit a ship
    self.runAction(SKAction.playSoundFileNamed("ShipHit.wav", waitForCompletion: false))
 
    contact.bodyA.node!.removeFromParent()
    contact.bodyB.node!.removeFromParent()
 
 
  } else if ((nodeNames as NSArray).containsObject(kInvaderName) && (nodeNames as NSArray).containsObject(kShipFiredBulletName)) {
 
    // 4
    // Ship bullet hit an invader
    self.runAction(SKAction.playSoundFileNamed("InvaderHit.wav", waitForCompletion: false))
    contact.bodyA.node!.removeFromParent()
    contact.bodyB.node!.removeFromParent()
 
  }
}

This code is relatively straightforward, and explained below:

  1. Don’t allow the same contact twice.
  2. containsObject is not yet implemented in Swift’s Array and you should cast the Array to NSArray like so (instanceOfSwiftArray as NSArray) in order to get access to NSArray’s methods.
  3. If an invader bullet hits your ship, remove your ship and the bullet from the scene and play a sound.
  4. If a ship bullet hits an invader, remove the invader and the bullet from the scene and play a different sound.

Add the following method to the // Scene Update Helpers section:

func processContactsForUpdate(currentTime: CFTimeInterval) {
 
  for contact in self.contactQueue {
    self.handleContact(contact)
 
    if let index = (self.contactQueue as NSArray).indexOfObject(contact) as Int? {
      self.contactQueue.removeAtIndex(index)
    }
  }
}

The above code just drains the contact queue, calling handleContact for each contact in the queue and then remove the contact with the newly added method in the Array extension.

Add the following line to the very top of update() to call your queue handler:

processContactsForUpdate(currentTime)

Build and run you app, and start firing at those invaders!

destroy_foes

Now, when your ship’s bullet hits an invader, the invader disappears from the scene and an explosion sound plays. In contrast, when an invader’s bullet hits your ship, the code removes your ship from the scene and a different explosion sound plays.

Depending on your playing skill (or lack thereof!), you may have to run a few times to see both invaders and your ship get destroyed.

You will notice that when the ship is hit the app crashes, this is due to let ship = self.childNodeWithName(kShipName) as SKSpriteNode, in fact you should do a conditional downcast with as?.

Now you can substitute let ship = self.childNodeWithName(kShipName) as SKSpriteNode with:

if let ship = self.childNodeWithName(kShipName) as? SKSpriteNode { ... }

Next, you should encase the whole processUserMotionForUpdate‘s code within the if statement:

func processUserMotionForUpdate(currentTime: CFTimeInterval) {
 
  // 1
  if let ship = self.childNodeWithName(kShipName) as? SKSpriteNode {
 
    // 2
    if let data = motionManager.accelerometerData {
 
      // 3
      if (fabs(data.acceleration.x) > 0.2) {
 
        // 4 How do you move the ship?
        ship.physicsBody!.applyForce(CGVectorMake(40.0 * CGFloat(data.acceleration.x), 0))
 
      }
    }
  }
}

Just hit Command R to run the game, you will see that the bug is finally squashed! :]

Updating Your Heads Up Display (HUD)

Your game looks good, but it’s lacking a certain something. There’s not much dramatic tension to your game. What’s the advantage of hitting an invader with your bullet if you don’t get credit? What’s the downside to being hit by an invader’s bullet if there’s no penalty?

You’ll rectify this by awarding score points for hitting invaders with your ship’s bullets, and by reducing your ship’s health when it gets hit by an invader’s bullet.

Add the following properties to the top of the class:

var score: Int = 0
var shipHealth: Float = 1.0

Your ship’s health starts at 100% but you will store it as a number ranging from 0 to 1. The above sets your ship’s initial health.

Now, replace the following line in setupHud():

healthLabel.text = String(format: "Health: %.1f%%", 100.0)

With this:

healthLabel.text = String(format: "Health: %.1f%%", self.shipHealth * 100.0)

The new line sets the initial HUD text based on your ship’s actual health value instead of a static value of 100.

Next, add the following two methods to the // HUD Helpers section:

func adjustScoreBy(points: Int) {
 
  self.score += points
 
  let score = self.childNodeWithName(kScoreHudName) as SKLabelNode
 
  score.text = String(format: "Score: %04u", self.score)
}
 
func adjustShipHealthBy(healthAdjustment: Float) {
 
  // 1
  self.shipHealth = max(self.shipHealth + healthAdjustment, 0)
 
  let health = self.childNodeWithName(kHealthHudName) as SKLabelNode
 
  health.text = String(format: "Health: %.1f%%", self.shipHealth * 100)
 
}

These methods are fairly straightforward: update the score and the score label, and update the ship’s health and the health label. //1 merely ensures that the ship’s health doesn’t go negative.

The final step is to call these methods at the right time during gameplay. Replace handleContact() with the following updated version:

func handleContact(contact: SKPhysicsContact) {
 
  // Ensure you haven't already handled this contact and removed its nodes
  if (contact.bodyA.node!.parent == nil || contact.bodyB.node!.parent == nil) {
    return
  }
 
  var nodeNames = [contact.bodyA.node!.name!, contact.bodyB.node!.name!]
 
  if (nodeNames as NSArray).containsObject(kShipName) && (nodeNames as NSArray).containsObject(kInvaderFiredBulletName) {
 
    // Invader bullet hit a ship
    self.runAction(SKAction.playSoundFileNamed("ShipHit.wav", waitForCompletion: false))
 
    // 1
    self.adjustShipHealthBy(-0.334)
 
    if self.shipHealth <= 0.0 {
 
      // 2
      contact.bodyA.node!.removeFromParent()
      contact.bodyB.node!.removeFromParent()
 
    } else {
 
      // 3
      let ship = self.childNodeWithName(kShipName)!
 
      ship.alpha = CGFloat(self.shipHealth)
 
      if contact.bodyA.node == ship {
 
        contact.bodyB.node!.removeFromParent()
 
      } else {
 
        contact.bodyA.node!.removeFromParent()
      }
 
    }
 
  } else if ((nodeNames as NSArray).containsObject(kInvaderName) && (nodeNames as NSArray).containsObject(kShipFiredBulletName)) {
 
    // Ship bullet hit an invader
    self.runAction(SKAction.playSoundFileNamed("InvaderHit.wav", waitForCompletion: false))
    contact.bodyA.node!.removeFromParent()
    contact.bodyB.node!.removeFromParent()
 
    // 4
    self.adjustScoreBy(100)
  }
}

Here’s what’s changed in the method:

  1. Adjust the ship’s health when it gets hit by an invader’s bullet.
  2. If the ship’s health is zero, remove the ship and the invader’s bullet from the scene.
  3. If the ship’s health is greater than zero, only remove the invader’s bullet from the scene. Dim the ship’s sprite slightly to indicate damage.
  4. When an invader is hit, add 100 points to the score.

The above also explains why you store the ship’s health as a value between 0 and 1, even though your health starts at 100. Since alpha values range from 0 to 1, you can use the ship’s health value as the alpha value for for your ship to indicate progressive damage. That’s pretty handy!

Build and run your game again; you should see the score change when your bullets hit an invader; as well, you should see your ship’s health change when your ship is hit, as below:

ship-hud

Polishing Your Invader and Ship Images

You’ve been incredibly patient working with these less-than-menacing red, green, blue and magenta rectangles. Keeping the visuals simple has worked well because it allowed you to focus ruthlessly on getting your game logic correct.

Now you’ll add some actual image sprites to make your game much more realistic — and more fun to play!

Replace makeInvaderOfType() with the following two methods:

func loadInvaderTexturesOfType(invaderType: InvaderType) -> Array<SKTexture> {
 
  var prefix: String
 
  switch(invaderType) {
  case .A:
    prefix = "InvaderA"
  case .B:
    prefix = "InvaderB"
  case .C:
    prefix = "InvaderC"
  default:
    prefix = "InvaderC"
  }
 
  // 1
  return [SKTexture(imageNamed: String(format: "%@_00.png", prefix)),
    SKTexture(imageNamed: String(format: "%@_01.png", prefix))]
}
 
func makeInvaderOfType(invaderType: InvaderType) -> SKNode {
 
  let invaderTextures = self.loadInvaderTexturesOfType(invaderType)
 
  // 2
  let invader = SKSpriteNode(texture: invaderTextures[0])
  invader.name = kInvaderName
 
  // 3
  invader.runAction(SKAction.repeatActionForever(SKAction.animateWithTextures(invaderTextures, timePerFrame: self.timePerMove)))
 
  // invaders' bitmasks setup
  invader.physicsBody = SKPhysicsBody(rectangleOfSize: invader.frame.size)
  invader.physicsBody!.dynamic = false
  invader.physicsBody!.categoryBitMask = kInvaderCategory
  invader.physicsBody!.contactTestBitMask = 0x0
  invader.physicsBody!.collisionBitMask = 0x0
 
  return invader
}

Here’s what the new code does:

  1. Loads a pair of sprite images — InvaderA_00.png and InvaderA_01.png — for each invader type and creates SKTexture objects from them.
  2. Uses the first such texture as the sprite’s base image.
  3. Animates these two images in a continuous animation loop.

All of the images were included in the starter project and iOS knows how to find and load them, so there’s nothing left to do here.

Build and run your app; you should see something similar to the screenshot below:

textures

Looks pretty cool doesn’t it? Next, you’ll replace your blocky green ship with a much more retro and stylish looking version.

Replace this piece of code inside makeShip():

let ship = SKSpriteNode(color: SKColor.greenColor(), size: kShipSize)

With the following:

let ship = SKSpriteNode(imageNamed: "Ship.png")

Your ship sprite is now constructed from an image.

Build and run your game; you should see your official-looking ship appear as below:

spaceship

Play your game for a while — what do you notice? Although you can blast happily away at the invaders, there’s no clear victory or defeat. It’s not much of a space war, is it?

Implementing the End Game

Think about how your game should end. What are the conditions that will lead to a game being over?

  • Your ship’s health drops to zero.
  • You destroy all the invaders.
  • The invaders get too close to Earth.

You’ll now add checks for each of the above conditions.

First, add the following new properties to the top of the class:

let kMinInvaderBottomHeight: Float = 32.0
var gameEnding: Bool = false

The above defines the height at which the invaders are considered to have invaded Earth, and a flag that indicates whether the game is over or not.

Now, add the following two methods to the // Game End Helpers section:

func isGameOver() -> Bool {
 
  // 1
  let invader = self.childNodeWithName(kInvaderName)
 
  // 2
  var invaderTooLow = false
 
  self.enumerateChildNodesWithName(kInvaderName) {
    node, stop in
 
    if (Float(CGRectGetMinY(node.frame)) <= self.kMinInvaderBottomHeight)   {
 
      invaderTooLow = true
      stop.memory = true
    }
  }
 
  // 3
  let ship = self.childNodeWithName(kShipName)
 
  // 4
  return invader == nil || invaderTooLow || ship == nil
}
 
func endGame() {
  // 1
  if !self.gameEnding {
 
    self.gameEnding = true
 
    // 2
    self.motionManager.stopAccelerometerUpdates()
 
    // 3
    let gameOverScene: GameOverScene = GameOverScene(size: self.size)
 
    view!.presentScene(gameOverScene, transition: SKTransition.doorsOpenHorizontalWithDuration(1.0))
  }
}

Here’s what’s happening in the first method, which checks to see if the game is over:

  1. Get all invaders that remain in the scene.
  2. Iterate through the invaders to check if any invaders are too low.
  3. Get a pointer to your ship: if the ship’s health drops to zero, then the player is considered dead and the player ship will be removed from the scene. In this case, you’d get a nil value indicating that there is no player ship.
  4. Return whether your game is over or not. If there are no more invaders, or an invader is too low, or your ship is destroyed, then the game is over.

The second method actually ends the game and displays the game over scene. Here’s what the code does:

  1. End your game only once. Otherwise, you’ll try to display the game over scene multiple times and this would be a definite bug.
  2. Stop accelerometer updates.
  3. Show the GameOverScene. You can inspect GameOverScene.swift for the details, but it’s a basic scene with a simple “Game Over” message. The scene will start another game if you tap on it.

Add the following line as the first line of code in update():

if self.isGameOver() {
  self.endGame()
}

The above checks to see if the game is over every time the scene updates. If the game is over, then it displays the game over scene.

Build and run; blast away at the invaders until your game ends. Hopefully, you’ll destroy all of the invaders before they destroy you! Once your game ends, you should see a screen similar to the following:

gameover

Tap the game over scene and you should be able to play again!

One Last Thing: Polish and Fidelity

It’s a truism of game development that the last 20% of game development takes as long as the first 80%. When you’re working on your next game, it’s a good idea to start out iterating quickly with low-fidelity art assets (e.g. your colored squares) so you can quickly figure out if your game is fun to play.

If it’s not fun to play with colored squares, it’s not going to be fun to play with fancy art work, either! Nail down your gameplay and game logic first, then build out with fancy art assets and cool sound effects.

That being said, it’s essential that you polish your game before releasing it to the App Store. The App Store is a crowded market and spit and polish will distinguish your app from the competition. Try to add little animations, storylines and a dash of cute factor that will delight your users. Also, consider being true to the game if you’re remaking a classic.

If you’re a fan of Space Invaders, you’ll know that your remake is missing one important element. In the original game, the invaders march faster the closer they get to the bottom of the screen.

This was an artifact of the early CPU used to run the first Space Invaders game – the game loop ran faster and faster with fewer invaders because there was less work to do with each loop cycle. The game’s programmer, Tomohiro Nishikado, decided to leave this behavior in the game as a challenging game mechanic.

You’ll update your game to incorporate this game mechanic as well to please the retro gaming purists out there.

Convert the instance constant let timePerMove: CFTimeInterval = 1.0 to variable var:

var timePerMove: CFTimeInterval = 1.0

Then add the following method to the // Invader Movement Helpers section:

func adjustInvaderMovementToTimePerMove(newTimerPerMove: CFTimeInterval) {
 
  // 1
  if newTimerPerMove <= 0 {
      return
  }
 
  // 2
  let ratio: CGFloat = CGFloat(self.timePerMove / newTimerPerMove)
  self.timePerMove = newTimerPerMove
 
  self.enumerateChildNodesWithName(kInvaderName) {
    node, stop in
    node.speed = node.speed * ratio
  }
 
}

Let’s examine this code:

  1. Ignore bogus values — a value less than or equal to zero would mean infinitely fast or reverse movement, which doesn’t make sense.
  2. Set the scene’s timePerMove to the given value. This will speed up the movement of invaders within moveInvadersForUpdate. Record the ratio of the change so you can adjust the node’s speed accordingly.
  3. Speed up the animation of invaders so that the animation cycles through its two frames more quickly. The ratio ensures that if the new time per move is 1/3 the old time per move, the new animation speed is 3 times the old animation speed. Setting the node’s speed ensures that all of the node’s actions run more quickly, including the action that animates between sprite frames.

Now, you need something to invoke this new method.

Modify determineInvaderMovementDirection() as indicated by comments below:

case .Right:
  //3
  if (CGRectGetMaxX(node.frame) >= node.scene!.size.width - 1.0) {
    proposedMovementDirection = .DownThenLeft
 
    // Add the following line
    self.adjustInvaderMovementToTimePerMove(self.timePerMove * 0.8)
 
    stop.memory = true
  }
case .Left:
  //4
  if (CGRectGetMinX(node.frame) <= 1.0) {
    proposedMovementDirection = .DownThenRight
 
    // Add the following line
    self.adjustInvaderMovementToTimePerMove(self.timePerMove * 0.8)
 
    stop.memory = true
  }

The new code simply reduces the time per move by 20% each time the invaders move down. This increases their speed by 25% (4/5 the move time means 5/4 the move speed).

Build and run your game, and watch the movement of the invaders; you should notice that those invaders move faster and faster as they get closer to the bottom of the screen:

Final Screen

This was a quick and easy code change that made your game that much more challenging and fun to play. If you’re going to save the Earth from invading hordes, you might as well do it right! Spending time on seemingly minor tweaks like this is what makes the difference between a good game and a GREAT game.

Where to Go From Here?

Here is the final project from this Sprite Kit tutorial.

I encourage you to experiment with your SKInvaders game. Play with it, tweak it and see what you can do! Breaking your code, fixing it, then finally seeing an awesome new feature come to life is one of the many thrills of game development.

Through this tutorial series you learned some new tricks about Sprite Kit by building a very cool classic game along the way. If you want to learn how to build more fun games like this, check out our brand new book iOS Games by Tutorials, where you’ll learn how to make five complete games from scratch!

Enjoy playing your new game; I look forward to hearing from you with comments below or questions in the forums!

How To Make a Game Like Space Invaders with Sprite Kit and Swift Tutorial: Part 2 is a post from: Ray Wenderlich

The post How To Make a Game Like Space Invaders with Sprite Kit and Swift Tutorial: Part 2 appeared first on Ray Wenderlich.

7
Like
Save

Comments

Write a comment

*