Categories
Uncategorized

Writing iOS games with Swift and Sprite Kit, part 2: Responding to touches

touching ipad screen

In the previous article in this series, we looked using actions to create complex sprite movements with very few lines of code. As this series progresses, we’ll work our way towards building complete games.

In this article, we’ll look at responding to user’s touches on the screen.

Set up a new project

It’s time to do the File → New → Project… dance again! Open up Xcode and create a new Game project:

new project

When it comes time to name the project, give it an appropriate name like RespondingToTouches (that’s the name I gave my project).

Once again, we’ll set the game to that it’s landscape-only. The simplest way to restrict our app’s orientation to landscape is by checking and unchecking the appropriate Device Orientation checkboxes in the app’s properties. Make sure that:

  • Portrait is unchecked
  • Landscape Left and Landscape Right are checked

uncheck portrait

Click the screenshot to see it at full size.

You can confirm that the app is landscape-only by running it. It should look like the video below, with spinning spaceships appearing wherever you tap the screen. It shouldn’t switch to portrait mode when you rotate the phone to a portrait orientation:

Making the spaceship go to where you touch the screen

It’s time to make our own interactivity. Let’s make an app that puts the spaceship sprite in the center of the screen, and then has the spaceship sprite immediately go to wherever the user touches the screen.

Open Gamescene.swift and replace its code with this:

import SpriteKit

class GameScene: SKScene {

  override func didMoveToView(view: SKView) {

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

  }

}

First task: drawing the spaceship sprite in the center of the screen. Before you look at the code, see if you can do it yourself. There’s enough information in the previous article to help you do this, or you can follow these hints:

  • A sprite is an instance of the SKSpriteNode class, and given the name of an image, you can instantiate one by using SKSpriteNode‘s init(imageNamed:) method.
  • A game scene’s didMoveToView method is called when the scene is first presented, and it’s the perfect place to do things like setting sprites in their initial locations.
  • A sprite’s location onscreen can be set using its position property, which is of type CGPoint, a struct that holds 2 Floats: one for the x-coordinate, and another for the y-coordinate.
  • The frame of a scene is the rectangle — a CGRect — that determines its bounds. You can get its width and height through the properties bearing those names.
  • You might want to scale down the spaceship sprite to one-third of its size. The setScale method will do that.
  • To display a sprite in a scene, you need to use the addChild method.

Your code should look something like this…

import SpriteKit

class GameScene: SKScene {
  
  let spaceshipSprite = SKSpriteNode(imageNamed: "Spaceship")
  

  override func didMoveToView(view: SKView) {
    spaceshipSprite.position = CGPoint(x: frame.width / 2,
                                       y: frame.height / 2)
    spaceshipSprite.setScale(0.33)
    addChild(spaceshipSprite)
  }
  
  override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

  }

}

…and when you run it, it should look like this:

spaceship in the middle of the screen

If you were to touch the screen, nothing would happen. That’s because there’s no code to respond to touches on the screen…yet! We’ll start by adding code to the touchesBegan method.

touchesBegan is called when the user puts a finger on the screen. It provides information about that touch action in its touches<c/ode> argument, a Set of touch objects, which are instances of the UITouch class. One of the properties of UITouch is locationInNode, which is a CGPoint. Given that sprites have a position property that is also a CGPoint, you've probably already guessed what we're about to code.

The default setting for multitouch in an app is “off”. We want to keep things simple for now, so we’ll just go with the default, which means that the app will register only one touch at a time, which in turn means that touchesBegan‘s touches argument will only contain a single touch object. We’ll retrieve that object from touches by using Set‘s first property.

Try coding it yourself before looking at the code below:

import SpriteKit

class GameScene: SKScene {
  
  let spaceshipSprite = SKSpriteNode(imageNamed: "Spaceship")
  

  override func didMoveToView(view: SKView) {
    spaceshipSprite.position = CGPoint(x: frame.width / 2,
                                       y: frame.height / 2)
    spaceshipSprite.setScale(0.33)
    addChild(spaceshipSprite)
  }
  
  override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let touch = touches.first!
    spaceshipSprite.position = touch.locationInNode(self)
  }
 
}

If you run the app, you’ll see that the spaceship jumps to wherever you put down your finger:

Making the spaceship go to where you released your finger

You’ll notice that if you hold your finger on the screen and move it about, the ship doesn’t follow your finger. It just stays where you first touched the screen and doesn’t move until you remove your finger and touch some other point.

The name touchesBegan implies that it has a counterpart method for when the user releases his/her finger from the screen. That method is called touchesEnded, and it takes the same arguments as touchesBegan.

Start typing touchesEnded into GameScene, and Xcode’s autocompletion should make it simple for you to set up a touchesEnded method. Cut the code from touchesBegan and paste it into touchesEnded, like so:

import SpriteKit

class GameScene: SKScene {
  
  let spaceshipSprite = SKSpriteNode(imageNamed: "Spaceship")
  

  override func didMoveToView(view: SKView) {
    spaceshipSprite.position = CGPoint(x: frame.width / 2,
                                       y: frame.height / 2)
    spaceshipSprite.setScale(0.33)
    addChild(spaceshipSprite)
  }
  
  override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

  }
  
  override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let touch = touches.first!
    spaceshipSprite.position = touch.locationInNode(self)
  }
 
}

Note what happens: the spaceship doesn’t move when you touch the screen until you remove your finger. Only when you release your finger does it move, and it jumps to the location where you released your finger from the screen. Try touching the screen, moving your finger to another location on the screen, and then releasing your finger.

What if we put the same code into both the touchesBegan and touchesEnded methods, like this?

import SpriteKit

class GameScene: SKScene {
  
  let spaceshipSprite = SKSpriteNode(imageNamed: "Spaceship")
  

  override func didMoveToView(view: SKView) {
    spaceshipSprite.position = CGPoint(x: frame.width / 2,
                                       y: frame.height / 2)
    spaceshipSprite.setScale(0.33)
    addChild(spaceshipSprite)
  }
  
  override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let touch = touches.first!
    spaceshipSprite.position = touch.locationInNode(self)
  }
  
  override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let touch = touches.first!
    spaceshipSprite.position = touch.locationInNode(self)
  }
 
}

If you run the app, the spaceship now jumps to the location where you made initial contact with the screen, and again to the location where you released your finger.

Making the spaceship follow your finger as you move it

You’re probably wondering if there’s a method that responds to changes that occur between the touchesBegan and touchesEnded events, something that lets you track the movements of the user’s finger as it moves on the screen. There is, and it’s called touchesMoved. It takes the same arguments as touchesBegan and touchesEnded, which means that it should respond to the code that we put into those two methods. Copy and paste the code from either touchesBegan and touchesEnded and paste it into touchesMoved. Your code should look like the following:

import SpriteKit

class GameScene: SKScene {
  
  let spaceshipSprite = SKSpriteNode(imageNamed: "Spaceship")
  

  override func didMoveToView(view: SKView) {
    spaceshipSprite.position = CGPoint(x: frame.width / 2,
                                       y: frame.height / 2)
    spaceshipSprite.setScale(0.33)
    addChild(spaceshipSprite)
  }
  
  override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let touch = touches.first!
    spaceshipSprite.position = touch.locationInNode(self)
  }
  
  override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let touch = touches.first!
    spaceshipSprite.position = touch.locationInNode(self)
  }
  
  override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let touch = touches.first!
    spaceshipSprite.position = touch.locationInNode(self)
  }
 
}

Run the app. It should look like this:

You’ll find that the spaceship jumps to the location where you touched the screen and as long as you keep your finger on the screen, you can drag it around. When you release your finger from the screen, the ship stays at the location where you released your finger.

Let’s refactor the duplicate code in touchesBegan, touchesMoved, and touchesEnded into its own method, handleTouch. The code will now look like this:

import SpriteKit

class GameScene: SKScene {
  
  let spaceshipSprite = SKSpriteNode(imageNamed: "Spaceship")
  

  override func didMoveToView(view: SKView) {
    spaceshipSprite.position = CGPoint(x: frame.width / 2,
                                       y: frame.height / 2)
    spaceshipSprite.setScale(0.33)
    addChild(spaceshipSprite)
  }
  
  override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    handleTouch(touches.first!)
  }
  
  override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
    handleTouch(touches.first!)
  }
  
  override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    handleTouch(touches.first!)
  }

  func handleTouch(touch: UITouch) {
    spaceshipSprite.position = touch.locationInNode(self)
  }

}

Experimenting with actions

Let’s combine this article’s lessons with the ones from the last one. Can you create some actions that make the spaceship pulsate as long as the user presses on the screen, as shown below?

Before you look at my solution below, see if you can’t code it yourself first:

import SpriteKit

class GameScene: SKScene {
  
  let spaceshipSprite = SKSpriteNode(imageNamed: "Spaceship")
  

  override func didMoveToView(view: SKView) {
    spaceshipSprite.position = CGPoint(x: frame.width / 2,
                                       y: frame.height / 2)
    spaceshipSprite.setScale(0.33)
    addChild(spaceshipSprite)
  }
  
  override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let expandAction = SKAction.scaleTo(0.5, duration: 0.33)
    let contractAction = SKAction.scaleTo(0.33, duration: 0.33)
    let pulsateAction = SKAction.repeatActionForever(
      SKAction.sequence([expandAction, contractAction]))
    spaceshipSprite.runAction(pulsateAction)
    handleTouch(touches.first!)
  }
  
  override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
    handleTouch(touches.first!)
  }
  
  override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    handleTouch(touches.first!)
    spaceshipSprite.removeAllActions()
    let restoreScaleAction = SKAction.scaleTo(0.33, duration: 0.1)
    spaceshipSprite.runAction(restoreScaleAction)
  }

  func handleTouch(touch: UITouch) {
    spaceshipSprite.position = touch.locationInNode(self)
  }

}

You may have noticed that in the touchesEnded method, the spaceship sprite is restored to its normal scale with an action rather than by simply setting its scale using the setScale method. I found that simply setting the spaceship back to its normal scale with setScale a little too abrupt; restoreScaleAction makes the transition very smooth.

What we just covered

In this article, we covered:

  • A recap of:
    • Starting a new Game project in Xcode
    • Restricting an app’s orientation to landscape-only
    • Drawing sprites at a specified location on the screen
  • Responding to the event where the user touches the screen with the touchesBegan method
  • Responding to the event where the user stops touching the screen with the touchesEnded method
  • Responding to the event where the user moves his/her finger while touching the screen with the touchesMoved method
  • Combining actions with touch events for interesting visual effects.

In the next article in this series, we’ll look at moving player characters around the screen.

Previous articles in this series

  1. Writing iOS games with Swift and Sprite Kit, part 1: The power of actions
Categories
Uncategorized

The story hidden inside Business Insider’s “More iPad Pros sold in 6 weeks than Surfaces sold in 3 months” story

business insider ipad pro 1

Here’s the story: Business Insider has posted a story with the lengthy title Apple’s gigantic iPad Pro outsold Microsoft’s entire Surface lineup last quarter — and it was only on sale for 6 weeks. They report that with a November 11 release date, Apple had only 6 weeks of the fourth quarter of 2015 in which to sell the iPad Pro, and in that time, they sold over 2 million units. In twice the time, Microsoft managed to sell fewer units of their entire Surface line: 1.6 million of them over the entire fourth quarter.

Here’s the story hidden inside that story: Like most online news outlets, Business Insider tries to squeeze a few more clicks and pageviews out of you by providing links to related stories also on their site. For this story, here are the articles they linked to:

business insider ipad pro 2

…and here are the IDC numbers they cited. Because Business Insider is really in the clicks-for-money business and not the “informing you” business, they made do with a crappy screenshot of IDC’s table of 4Q 2015 tablet sales:

screen shot 2016-02-01 at 1.22.28 pm

But because Global Nerdy is just Yours Truly sharing information because I can’t live any other way, I searched for the IDC news release that Business Insider refused to provide a link to (which I just did), and I took both their tables and turned them into these graphs:

top tablet vendors 1

Click the graph to see the information source.

top tablet vendors 2

Click the graph to see the information source.

Business Insider‘s reporting on the shrinking tablet market is correct. From 2014 to 2015, tablet sales shrank by 10.1%, with Huawei as the only top 5 vendor seeing growth. As for their prediction of the iPad’s Pro’s impending doom: if this is doom, I’d like some of that action, please!

Categories
Uncategorized

T-shirt of the day

internet went skawee reweert

For those of you who miss the SKAWEE REWEERT sound, here it is, with annotations! It works best when viewed in full-screen mode on a large screen:

And for those of you who always thought that the sound of dial-up could be turned into music, here’s 56K by RAC:

Categories
Uncategorized

Your guide to software developer job ads

software developer advertisements

Courtesy of George Takei’s Facebook page — an ever-reliable source nerd humor — here’s a handy guide that should prove useful to people looking at software job site’s descriptions of available positions. As with many things, it’s funny because it’s true.

Categories
Uncategorized

Privacy joke of the day

difference between usa and usb

It’s Hump Day, and what better way to start it than with a privacy joke, courtesy of @alfiedotwtf?

Categories
Uncategorized

Better to be roughly right than precisely wrong: Rounding numbers with Swift

ludicrous trek accuracy

Update (December 19, 2016): The code in this article was updated for Swift 3.0.

The 1960s Star Trek TV series wasn’t written by engineers, scientists, or mathematicians, which is why one of their tricks for showing you that Mr. Spock was an intellectual badass was having make calculations to a ridiculous degree of preciseness, a dramatic device that TV Tropes calls ludicrous precision.

You don’t want your apps to be as annoying as Spock could sometimes be, which is why rounding is an important operation.

Rounding is useful for things like:

  • The limits of your input precision. Suppose you’re calculating the average of the daily high temperatures over the past week, each of which is accurate to the nearest degree. Because of the way an average (or more technically, the mean) is calculated, the result may contain a decimal point. However, the result you report shouldn’t be more precise than the data it’s based on, so you’ll want to round it to the nearest degree.
  • The limits of usefulness. If you’re displaying directions for a short trip, you probably wouldn’t want to report the total distance with more than a 1/4 mile or 1/2 kilometer precision, and you’d probably want to state the trip time rounded to the nearest minute. On the other hand, for a trip covering the width of the United States or Canada, anything more precise than rounding the total distance to the nearest 10 miles and the trip time to the nearest half-hour isn’t generally helpful.
  • Practical considerations. When performing financial calculations, especially when dealing with cash, you’ll want to round your result to the nearest penny. In countries like Canada, where they’ve phased out the penny, you’ll want to round cash calculations to the nearest nickel.

Swift’s built-in rounding functions

Swift comes built-in with most of the rounding functions in C’s math library. The ones you’re most likely to use are round, floor, and ceil:

round(_:) Given a number n, this returns n rounded to the nearest whole number. For example:

  • round(2.0) returns 2.0
  • round(2.2) returns 2.0
  • round(2.5) returns 3.0
  • round(2.9) returns 3.0

With negative numbers, a fractional value of 0.5 or higher rounds the number down, and lower fractional values round the number up:

  • round(-2.0) returns –2.0
  • round(-2.2) returns –2.0
  • round(-2.5) returns –3.0
  • round(-2.9) returns –3.0
floor(_:) Given a number n, this returns n rounded down to the nearest whole number. For example:

  • floor(2.0) returns 2.0
  • floor(2.2) returns 2.0
  • floor(2.5) returns 2.0
  • floor(2.9) returns 2.0
  • floor(-2.0) returns -2.0
  • floor(-2.2) returns -3.0
  • floor(-2.5) returns -3.0
  • floor(-2.9) returns -3.0
ceil(_:) Given a number n, this returns n rounded up to the nearest whole number (you’ve probably guessed that ceil is short for ceiling). For example:

  • ceil(2.0) returns 2.0
  • ceil(2.2) returns 3.0
  • ceil(2.5) returns 3.0
  • ceil(2.9) returns 3.0
  • ceil(-2.0) returns -2.0
  • ceil(-2.2) returns -2.0
  • ceil(-2.5) returns -2.0
  • ceil(-2.9) returns -2.0

round, floor, and ceil each come in two versions:

  • A version that accepts a Double argument and returns a Double result (Doubles are 64-bit numbers and have a precision of at least 15 decimal digits), and
  • a version that accepts a Float argument and returns a Float result (Floats are 32-bit numbers and have a precision of as little as 6 decimal digits).

Rounding n to the nearest multiple of x

rounding coaster

round, floor, and ceil work well when you’re trying to round to nearest whole number, but they’re not as useful if you’re facing one of these rounding scenarios:

  • Rounding a number to the nearest 10 (as demonstrated in the “Rounding Coaster” shown above), 100, 1000, etc.
  • Rounding a number to the nearest .1, .01, .001, etc.
  • Rounding a dollar value to the nearest penny
  • In Canada, which has eliminated the penny (because it costs more than a penny to manufacture one), you calculate change by rounding to the nearest nickel
  • Rounding a number to the nearest multiple of x

For situations like the ones listed above, you’ll find these Swift functions handy:

// Given a value to round and a factor to round to,
// round the value to the nearest multiple of that factor.
func round(_ value: Double, toNearest: Double) -> Double {
  return round(value / toNearest) * toNearest
}

// Given a value to round and a factor to round to,
// round the value DOWN to the largest previous multiple
// of that factor.
func roundDown(_ value: Double, toNearest: Double) -> Double {
  return floor(value / toNearest) * toNearest
}

// Given a value to round and a factor to round to,
// round the value DOWN to the largest previous multiple
// of that factor.
func roundUp(_ value: Double, toNearest: Double) -> Double {
  return ceil(value / toNearest) * toNearest
}

Here’s the round(_:toNearest:) function in action:

round(52.376, toNearest: 0.01) // 52.38
round(52.376, toNearest: 0.1)  // 52.4
round(52.376, toNearest: 0.25) // 52.5
round(52.376, toNearest: 0.5)  // 52.5
round(52.376, toNearest: 1)    // 52
round(52.376, toNearest: 10)   // 50
round(52.376, toNearest: 100)  // 100
round(52.376, toNearest: 1000) // 0

Here’s the roundDown(_:toNearest:) function in action:

roundDown(52.376, toNearest: 0.01) // 52.37
roundDown(52.376, toNearest: 0.1)  // 52.3
roundDown(52.376, toNearest: 0.25) // 52.25
roundDown(52.376, toNearest: 0.5)  // 52
roundDown(52.376, toNearest: 1)    // 52
roundDown(52.376, toNearest: 10)   // 50
roundDown(52.376, toNearest: 100)  // 0
roundDown(52.376, toNearest: 1000) // 0

Here’s the roundUp(_:toNearest:) function in action:

roundUp(52.376, toNearest: 0.01) // 52.38
roundUp(52.376, toNearest: 0.1)  // 52.4
roundUp(52.376, toNearest: 0.25) // 52.5
roundUp(52.376, toNearest: 0.5)  // 52.5
roundUp(52.376, toNearest: 1)    // 53
roundUp(52.376, toNearest: 5)    // 55
roundUp(52.376, toNearest: 10)   // 60
roundUp(52.376, toNearest: 100)  // 100
roundUp(52.376, toNearest: 1000) // 1000

Rounding to a specified number of decimal places

price break

Photo from Consumerist. Click to see the source.

If you want to round a value to a specific number of decimal places, try this function:

// Round the given value to a specified number
// of decimal places
func round(_ value: Double, toDecimalPlaces places: Int) -> Double {
  let divisor = pow(10.0, Double(places))
  return round(value * divisor) / divisor
}

Here’s roundToPlaces(_:decimalPlaces:) in action:

round(52.3761, toDecimalPlaces: 3) // 52.376
round(52.3761, toDecimalPlaces: 2) // 52.38
round(52.3761, toDecimalPlaces: 1) // 52.4
round(52.3761, toDecimalPlaces: 0) // 52.0

// If we put a negative number n into decimalPlaces,
// roundToPlaces rounds to the nearest multiple of 
// 10^abs(n):
round(52.3761, toDecimalPlaces: -1) // 50.0 (rounding to nearest multiple of 10)
round(52.3761, toDecimalPlaces: -2) // 100.0 (rounding to nearest multiple of 100)
round(52.3761, toDecimalPlaces: -3) // 0 (rounding to nearest multiple of 1000))

Removing the fractional part of a number

seamus levine from family guy

“Seamus” from Family Guy is rather truncated.

Sometimes, you just want to lop off anything after the decimal point. That’s what trunc (short for truncate) is for:

trunc(52.376)  // 52.0
trunc(52.576)  // 52.0
trunc(-52.376) // -52.0
trunc(-52.576) // -52.0

Displaying rounded values

There are times when you want to store a precise value but display a rounded one. Use the String class’ init(format:arguments:) method and C’s format strings for printf to display numbers as strings representing rounded values. Here it is in action:

// Remember, these are *string* values!
String(format: "%.3f", 52.3761) // "52.376"
String(format: "%.2f", 52.3761) // "52.38"
String(format: "%.1f", 52.3761) // "52.4"
String(format: "%.0f", 52.3761) // "52"
String(format: "%.0f", 52.5761) // "53"

Happy rounding!

Thanks to John Maynard Keynes for the title of this article!

Categories
Uncategorized

Writing iOS games with Swift and Sprite Kit, part 1: The power of actions

get your game onI’m the co-organizer of the Tampa iOS meetup, a monthly gathering of iOS developers based in the Tampa Bay area, and we recently had a session on game development with Sprite Kit that garnered a fair bit of attention. This series of articles takes some of the ideas from that session and uses them as a jumping-off point for exploring game development techniques using Apple’s Sprite Kit framework.

In this first article in the series, we’ll cover enough Sprite Kit to create an app that displays the animated scene below. Best of all, we’ll do it in 26 lines of code:

If you’re comfortable with building basic iOS apps, you shouldn’t have any problem following along.

Setting up the project

Create a new Game project in Xcode by selecting File → New → Project… In the “Choose a template for your new project” window that appears:

  • Select Application under iOS from the list on the left-hand side, and
  • select Game from icon list of template types on the right-hand side

new project

In the “Choose options for your new project” window, give your app a name in the Product Name: text field (I named mine “SpriteKitActions01”, since this project is an introduction to Sprite Kit actions), and went with all the other default options:

project options

After you choose a place to save your project, you should see the various properties for your new project, which should look something like the screenshot below.

In this project, we’re going to show spaceships flying across the full width of the screen, so we want our app to be landscape-only. The simplest way to restrict our app’s orientation to landscape is by checking and unchecking the appropriate Device Orientation checkboxes in the app’s properties. Make sure that:

  • Portrait is unchecked
  • Landscape Left and Landscape Right are checked

uncheck portrait

Click the screenshot to see it at full size.

You can confirm that the app is landscape-only by running it. It should look like the video below, with spinning spaceships appearing wherever you tap the screen. It shouldn’t switch to portrait mode when you rotate the phone to a portrait orientation:

Your first scene, your first sprite

Let’s dive right in and draw our first sprite on the screen. Open GameScene.swift and replace all its code with the following:

import SpriteKit

class GameScene: SKScene {

  let spaceshipSprite = SKSpriteNode(imageNamed: "Spaceship")

  override func didMoveToView(view: SKView) {
    spaceshipSprite.position = CGPoint(x: 300, y: 300)
    addChild(spaceshipSprite)
  }
  
}

In this example app, we’ll be doing all our coding in GameScene.swift, which contains the GameScene class. GameScene is a subclass of SKScene, the superclass for all scenes. Sprite Kit class names usually begin with the letters SK.

In Sprite Kit, a scene is both:

  • A “canvas” where we draw objects to the screen, such as sprites, and
  • a place for the logic that draws onscreen objects, responds to user input, and deals with the interactions of onscreen objects.

A scene is usually used to correspond to a “screen” in a game. For example, you might use one scene for a game’s title screen, another for gameplay, and a third scene for the “game over” screen. This example app will have just one screen, so we just need one scene: GameScene.

Run the app. You should see something like this:

run 01 - landscape

The spaceship that you see is the onscreen rendering of the spaceshipSprite object, the one property in our GameScene class. It’s an instance of SKSpriteNode, Sprite Kit’s sprite class. It’s declared and instantiated in a single line:

let spaceshipSprite = SKSpriteNode(imageNamed: "Spaceship")

The method that you’ll probably use to instantiate sprite objects most of the time is the init(imageNamed:) method, which takes the name of an image file in the app bundle (in the project’s filesystem) as its argument. The Spaceship image is included in the template that Xcode creates for new game projects, and can be found in the default asset catalog, Assets.xcassets.

Our GameScene class has a single method: didMoveToView. This method is called when the scene is first presented, and at the moment, it contains two lines. Here’s the first one:

spaceshipSprite.position = CGPoint(x: 300, y: 300)

The location of a sprite is determined by its position property, which is a CGPoint, a struct representing a point in 2D space defined by an x-coordinate and a y-coordinate (the CG prefix indicates that it’s from the Core Graphics library).

You’ve probably figured out that this line of code puts the spaceship at the point (300, 300). What you might not know is that (300, 300) means:

  • x-coordinate: 300 points from the left edge of the view
  • y-coordinate: 300 points from the bottom edge of the view

Most programming frameworks treat increasing y-coordinates as going downward, but Sprite Kit is based on OpenGL, which uses the coordinate system used in math, where increasing x goes right, and increasing y goes up:

When setting a sprite’s coordinates, you should keep a sprite’s anchor point — represented by its anchorPoint property — in mind. It’s a CGPoint value that corresponds to the point in a sprite that corresponds to the position in the scene that you assign to it, whose x- and y-coordinates range from 0 to 1 as shown below:

anchor point

The default anchorPoint value for a sprite is (0.5, 0.5), which corresponds to a point in the dead center of the sprite. This means that if we set the sprite’s position to (300, 300), the sprite’s center will be located at (300, 300). If we change the sprite’s anchorPoint to (0, 0), its lower left-hand corner, its lower left-hand corner will be at the coordinates (300, 300), and the rest of the sprite will be above and to the right of that point. On the other hand, if we change the sprite’s anchorPoint to (1, 1), its upper right-hand corner will be at the coordinates (300, 300) and the rest of the sprite will be below and to the left of that point.

The sprite doesn’t actually appear until the second line of the didMoveToView method gets executed:

addChild(spaceshipSprite)

Only sprites that are added as children of a scene appear in the scene.

Scenes, sprites, and nodes

In this tutorial, the only Sprite Kit objects that we’ll be drawing onscreen are scenes and sprites, but it’s worth talking about the larger Sprite Kit framework and how scenes and sprites fit into it.

Most objects in a Sprite Kit-based game that are responsible for putting images on the screen or playing sound are nodes — subclasses of the SKNode class. You’ll probably never have to instantiate an SKNode instance; its purpose is to provide some baseline of behavior and capabilities for its subclasses.

sprite kit nodes

The term “node” implies that there’s a tree structure of some kind, and that’s the case. The scene in a game — represented by the SKScene class — acts as the root node of this tree, and the scene’s child nodes are the ones that appear in it and interact in it. If you want a node like a sprite to appear in a scene, you must add it as a child of that scene.

sprite kit nodes form a tree

Sprites — represented by the SKSpriteNode class — can have child sprites. We’ll cover child sprites in another article, but we’ll say that they’re useful for creating onscreen objects made up of two or more sprites, such in the example above where the mad scientist sprite character has a force field as a child sprite. Just as child sprites of a scene are attached to the parent scene and have a location in their parent scene’s coordinate system, child sprites of a sprite are attached to the parent sprite and have a location in their parent sprite’s coordinate system.

That’s enough big picture discussion for now — let’s get back to coding!

Scaling and rotation

Let’s make a couple of changes to our spaceship sprite. We want to:

  • Scale it down to a quarter of its current size, and
  • Rotate it 90 degrees so that it’s pointing to the right.

This is easily done — just add a couple of lines to didMoveToView so that it looks like this:

override func didMoveToView(view: SKView) {
  spaceshipSprite.position = CGPoint(x: 300, y: 300)
  spaceshipSprite.setScale(0.25)
  spaceshipSprite.zRotation = CGFloat(-M_PI_2)
  addChild(spaceshipSprite)
}

Run the app. You should now see this:

run 02

The setScale method is pretty straightforward. It scales the sprite in both the x- and y-directions by the factor specified in the argument you provide it with, which in this case is 0.25.

The zRotation property specifies rotation around the z-axis. As with most programming languages and frameworks, Sprite Kit requires that you specify angles in radians instead of degrees. It also follows the convention in mathematics where angles increase in the counterclockwise direction:

radians

The standard math library included with Swift comes with a number of π-related constants that you’ll find useful when working with angles:

  • M_PI: π, or 180°
  • M_PI_2: π / 2, or 90°
  • M_PI_4: π / 4, or 45°

The spaceship graphic points upwards, so in order to point it to the right, we need to rotate it 90° clockwise, hence spaceshipSprite.zRotation = CGFloat(-M_PI_2).

The event loop and moving sprites the hard way

Most game frameworks operate in an event loop, which is a repeating cycle based on this general sequence of events:

event loop

The specifics of the event loop vary from game framework to game framework. Here’s what Sprite Kit’s event loop looks like:

sprite kit event loop

Each cycle of the event loop is called a frame. That’s because at the end of each cycle, the currently displayed scene is rendered. By making small changes during each frame and rendering frames several times a second, we create motion, in pretty much the same way that’s it’s done with film and television, which is where the term “frame” comes from — it refers to frames of film:

film frames

As the scene executes the event loop, it calls a number of methods, each of which corresponds to a specific part of the loop. By overriding these methods, we can make our own code execute at specific points of the event loop.

In this application, we’ll override the update method, which gets executed at the start of each frame, immediately after Sprite Kit renders the screen. It’s a good place to define changes to objects in the scene. As a method automatically called by the system every time it goes around the event loop, it takes the argument currentTime, a sub-millisecond-accurate number in seconds that makes it possible to determine the amount of time that has elapsed since the last call to update.

Let’s use it to move the spaceship in a rightward direction by adding the following code to GameScene:

override func update(currentTime: CFTimeInterval) {
  spaceshipSprite.position.x += 10
}

If you run the app, you’ll see the ship start at (300, 300) and move to the right, eventually going off the screen. This method of moving the spaceship around works, but would require you to write a fair bit of code to get this result:

In the app shown above, each spaceship sprite:

  1. Appears on the left edge of the scene at a random y-coordinate and flies across the screen to disappear off the right edge in 5 seconds’ time,
  2. Backs up from the right edge a distance of about a third of the scene over 2 seconds,
  3. Executes a 180-degree counterclockwise turn in 1.5 seconds,
  4. waits 2 seconds, and
  5. Zips off the left edge of the scene in half a second.

Luckily for us, Sprite Kit provides a set of powerful tools that lets us get around the kind of micromanagement that game development once required. They’re called actions, and they’ll let us code the activity shown above in just over two dozen lines of code.

Your first action

Change the code in GameScene so it looks like this:

import SpriteKit

class GameScene: SKScene {

  let spaceshipSprite = SKSpriteNode(imageNamed: "Spaceship")
  
  override func didMoveToView(view: SKView) {
    spaceshipSprite.position = CGPoint(x: -spaceshipSprite.size.width / 2,
                                       y: 300)
    spaceshipSprite.setScale(0.25)
    spaceshipSprite.zRotation = CGFloat(-M_PI_2)
    addChild(spaceshipSprite)
    
    let flyAcrossScreen = SKAction.moveTo(CGPoint(x: size.width +
                                                     spaceshipSprite.size.width / 2,
                                                  y: 300),
                                          duration: 5)
    spaceshipSprite.runAction(flyAcrossScreen)
  }
  
}

Note the changes:

  • We’ve eliminated the update method,
  • the spaceship sprite’s starting x-coordinate is half its width to the left of the left edge of the scene, and
  • we’ve added a couple of lines of code that concern themselves with something named flyAcrossScreen.

Before we discuss the changes, run the app. It should look like this:

Let’s look at why we set the spaceship sprite’s x-coordinate to -spaceshipSprite.size.width / 2. That’s the minimum x-coordinate that ensures that the sprite is far enough to the left so that it’s no longer visible onscreen (remember what we discussed about anchor points earlier):

half spaceship width

Now let’s discuss the more interesting thing: that thing named flyAcrossScreenIt’s an action — represented by the class SKAction — an object that changes something about the structure or properties of a node. There are many types of SKActions, including:

  • Actions that move nodes, either to specific coordinates or in a direction relative to the current position, in a straight line or along a specified path,
  • actions that rotate nodes, either to a specific angle or by a certain angle relative to the current orientation,
  • actions that scale nodes, either to a specific size or by a specific factor relative to the current size, in the x-, y-, or both directions,
  • actions that change the content of a node, either by changing its current appearance, producing an animation effect by rapidly cycling through a set of “frames”, changing its opacity, or colorizing it,
  • actions that play sounds,
  • actions that combine actions, either by executing them in sequence or concurrently,
  • actions that act as pauses or delays,
  • actions that repeat or reverse actions, and
  • actions that execute custom code.

flyAcrossScreen is an instance of an SKAction.moveTo function. This type of function moves a node to a specific point in the scene (in this case, just off the right edge of the screen) in an amount of time specified in seconds (in this case, 5). The function returns an action, which is performed by a node when it is provided as an argument for one of the node’s runAction methods.

Note that in a single line of code:

  • We’ve defined something that moves a sprite to a given coordinate in a given amount of time without having to write any code to manage that movement, and
  • we’ve also stored it in a constant for later use.

Adding another action and introducing action sequences

Let’s try adding another action to the mix, so that after the spaceship has flown off the right edge of the scene, it reappears on the screen by backing up 400 points.

We’ll do this by defining a new action called backUpALittle, using SKAction‘s moveByX(_:y:duration:) method. Unlike moveTo(_:duration:), which moves a node to a specific point in the scene, moveByX(_:y:duration:) moves a node by a number of x- and -y points relative to its current position. Change the code in your didMoveToView method to the code shown below:

override func didMoveToView(view: SKView) {
  spaceshipSprite.position = CGPoint(x: -spaceshipSprite.size.width,
                                     y: 300)
  spaceshipSprite.setScale(0.25)
  spaceshipSprite.zRotation = CGFloat(-M_PI_2)
  
  addChild(spaceshipSprite)
  
  let flyAcrossScreen = SKAction.moveTo(CGPoint(x: size.width +
                                                   spaceshipSprite.size.width,
                                                y: 300),
                                        duration: 5)
  spaceshipSprite.runAction(flyAcrossScreen)
  
  let backUpALittle = SKAction.moveByX(-400,
                                       y: 0,
                                       duration: 2)
  spaceshipSprite.runAction(backUpALittle)
}

If you were to run the app now, you’d see the spaceship fly across the screen, but you won’t see it back up back onto the screen afterwards. If you put some print statements around the runAction method that runs the backUpALittle action, as shown below…

let backUpALittle = SKAction.moveByX(-400,
                                     y: 0,
                                     duration: 2)
print("Commence backing up")
spaceshipSprite.runAction(backUpALittle)
print("Completed backing up")

…you’ll see the lines “Commence backing up” and “Completed backing up” appear in Xcode’s console while the spaceship is still executing the flyAcrossScreen method. Obviously, there’s more to running two or more actions in a sequence than simply running them one after another.

That’s what the SKAction.sequence method is for. Given an array of SKActions, it executes them in the order in which they appear in that array. Change the code in didMoveToView to this:

override func didMoveToView(view: SKView) {
  spaceshipSprite.position = CGPoint(x: -spaceshipSprite.size.width,
                                     y: 300)
  spaceshipSprite.setScale(0.25)
  spaceshipSprite.zRotation = CGFloat(-M_PI_2)
  
  addChild(spaceshipSprite)
  
  let flyAcrossScreen = SKAction.moveTo(CGPoint(x: size.width +
                                                   spaceshipSprite.size.width,
                                                y: 300),
                                        duration: 5)
  let backUpALittle = SKAction.moveByX(-400,
                                       y: 0,
                                       duration: 2)
  let spaceshipActions = SKAction.sequence([flyAcrossScreen, backUpALittle])
  
  spaceshipSprite.runAction(spaceshipActions)
}

When you run the app, you should see this:

Let’s make the sequence a little fancier. We’ll define these actions and then add them to the sequence:

  • doA180: Using SKAction.rotateByAngle, we’ll rotate the spaceship 180 degrees clockwise over a span of 1.5 seconds,
  • wait2Seconds: Using SKAction.waitForDuration, introduce a 2-second pause, and
  • blastOff: Using SKAction.moveTo, blast the spaceship off the left edge of the screen in half a second.

Here’s the code for running all the actions…

let flyAcrossScreen = SKAction.moveTo(CGPoint(x: size.width +
                                                 spaceshipSprite.size.width,
                                              y: 300),
                                      duration: 5)
let backUpALittle = SKAction.moveByX(-400,
                                     y: 0,
                                     duration: 2)
let doA180 = SKAction.rotateByAngle(CGFloat(M_PI), duration: 1.5)
let wait2seconds = SKAction.waitForDuration(2)
let blastOff = SKAction.moveTo(CGPoint(x: -spaceshipSprite.size.width / 2,
                                       y: 300),
                                       duration: 0.5)
let spaceshipActions = SKAction.sequence([flyAcrossScreen,
                                          backUpALittle,
                                          doA180,
                                          wait2seconds,
                                          blastOff])

spaceshipSprite.runAction(spaceshipActions)

…and here’s what the app looks like when you run it:

Spawning multiple spaceships, part one

Let’s harness the true power of actions by changing the code so that we spawn a new spaceship that performs all the actions we defined every time the user taps the screen. Here’s the code:

import SpriteKit

class GameScene: SKScene {
  
  override func didMoveToView(view: SKView) {
    spawnSpaceship(y: 300)
  }
  
  func spawnSpaceship(y yposition: CGFloat) {
    let spaceshipSprite = SKSpriteNode(imageNamed: "Spaceship")
    
    spaceshipSprite.position = CGPoint(x: -spaceshipSprite.size.width / 2,
      y: yposition)
    spaceshipSprite.zRotation = CGFloat(-M_PI_2)
    spaceshipSprite.setScale(0.25)
    addChild(spaceshipSprite)
    
    let flyAcrossScreen = SKAction.moveTo(CGPoint(x: size.width + spaceshipSprite.size.width / 2,
      y: yposition),
      duration: 5)
    let backUpALittle = SKAction.moveByX(-400,
      y: 0,
      duration: 2)
    let doA180 = SKAction.rotateByAngle(CGFloat(M_PI), duration: 1.5)
    let wait2seconds = SKAction.waitForDuration(2)
    let blastOff = SKAction.moveTo(CGPoint(x: -spaceshipSprite.size.width / 2,
      y: yposition),
      duration: 0.5)
    let spaceshipActions = SKAction.sequence([flyAcrossScreen,
      backUpALittle,
      doA180,
      wait2seconds,
      blastOff])
    spaceshipSprite.runAction(spaceshipActions)
  }
  
  override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    for touch in touches {
      spawnSpaceship(y: touch.locationInNode(self).y)
    }
  }

}

Note the changes that we’ve made:

  • We’ve taken our spaceship setup and action code and put it into its own method, spawnSpaceship, which takes a y-coordinate as an argument,
  • we call spawnSpaceship in the didMoveToView method in such a way that the first spaceship shown onscreen behaves like the one in the previous version of the app, and
  • we respond to touches onscreen with the touchesBegan method. It’s called whenever a user touches down on the screen, and one of the arguments it provides is a Set of UITouch objects. From each of these objects, we can get the y-coordinate of each spot where the user touched the screen, and  we spawn a new spaceship and performing its actions at that y-coordinate. (We’ll cover touch events in greater detail in a later article.)

When you run this app, it looks like this:

Not bad for a couple dozen lines of code!

Spawning multiple spaceships, part two: using the update method as a timer

Let’s change the app so that it in addition to spawning a new spaceship when the user touches the screen, it also automatically spawns a new spaceship at a random y-coordinate every half second. Here’s the code:

import SpriteKit

class GameScene: SKScene {
  
  var lastSpaceshipSpawnTime: CFTimeInterval = 0
  
  override func didMoveToView(view: SKView) {
    spawnSpaceship(y: 300)
  }
  
  override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    for touch in touches {
      spawnSpaceship(y: touch.locationInNode(self).y)
    }
  }
  
  override func update(currentTime: CFTimeInterval) {
    if currentTime - lastSpaceshipSpawnTime > 0.5 {
      spawnSpaceship(y: CGFloat(arc4random_uniform(UInt32(size.height))))
      lastSpaceshipSpawnTime = currentTime
    }
  }
  
  func spawnSpaceship(y yposition: CGFloat) {
    let spaceshipSprite = SKSpriteNode(imageNamed: "Spaceship")
    
    spaceshipSprite.position = CGPoint(x: -spaceshipSprite.size.width / 2,
      y: yposition)
    spaceshipSprite.zRotation = CGFloat(-M_PI_2)
    spaceshipSprite.setScale(0.25)
    addChild(spaceshipSprite)
    
    let flyAcrossScreen = SKAction.moveTo(CGPoint(x: size.width + spaceshipSprite.size.width / 2,
      y: yposition),
      duration: 5)
    let backUpALittle = SKAction.moveByX(-400,
      y: 0,
      duration: 2)
    let doA180 = SKAction.rotateByAngle(CGFloat(M_PI), duration: 1.5)
    let wait2seconds = SKAction.waitForDuration(2)
    let blastOff = SKAction.moveTo(CGPoint(x: -spaceshipSprite.size.width / 2,
      y: yposition),
      duration: 0.5)
    let spaceshipActions = SKAction.sequence([flyAcrossScreen,
      backUpALittle,
      doA180,
      wait2seconds,
      blastOff])
    spaceshipSprite.runAction(spaceshipActions)
  }
  
}

Note the changes:

  • We’ve introduced a new property to the GameScene class: lastSpaceshipSpawnTime, a CFTimeInterval variable to keep track of when the spawnSpaceship method was last executed.
  • We’ve brought back the update method. It gets called often enough that we can use it to hold some code to call spawnSpaceship at intervals very, very close to every half second.

Here’s what the app looks like when you run it:

The spaceships spawn every half second, but doing so requires us to write some code and declare a property in order to manage the spawning process. Isn’t there an action that do the management for us?

Spawning multiple spaceships, part three: Using actions to manage the entire process

In this version of our spaceship-spawning app, we’ll make use of two new actions:

Change the code so that it looks like this:

import SpriteKit

class GameScene: SKScene {
  
  override func didMoveToView(view: SKView) {
    spawnSpaceship(y: 300)
    
    let addAnotherShip = SKAction.runBlock {
      self.spawnSpaceship(y: CGFloat(arc4random_uniform(UInt32(self.size.height))))
    }
    let waitAHalfSecond = SKAction.waitForDuration(0.5)
    let addShipThenWait = SKAction.sequence([addAnotherShip, waitAHalfSecond])
    let addShipsAdInfinitum = SKAction.repeatActionForever(addShipThenWait)
    runAction(addShipsAdInfinitum)
  }
  
  override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    for touch in touches {
      spawnSpaceship(y: touch.locationInNode(self).y)
    }
  }
  
  func spawnSpaceship(y yposition: CGFloat) {
    let spaceshipSprite = SKSpriteNode(imageNamed: "Spaceship")
    
    spaceshipSprite.position = CGPoint(x: -spaceshipSprite.size.width / 2,
                                       y: yposition)
    spaceshipSprite.zRotation = CGFloat(-M_PI_2)
    spaceshipSprite.setScale(0.25)
    addChild(spaceshipSprite)
    
    let flyAcrossScreen = SKAction.moveTo(CGPoint(x: size.width + spaceshipSprite.size.width / 2,
                                                  y: yposition),
                                          duration: 5)
    let backUpALittle = SKAction.moveByX(-400,
                                         y: 0,
                                         duration: 2)
    let doA180 = SKAction.rotateByAngle(CGFloat(M_PI), duration: 1.5)
    let wait2seconds = SKAction.waitForDuration(2)
    let blastOff = SKAction.moveTo(CGPoint(x: -spaceshipSprite.size.width / 2,
                                           y: yposition),
                                   duration: 0.5)
    let spaceshipActions = SKAction.sequence([flyAcrossScreen,
                                              backUpALittle,
                                              doA180,
                                              wait2seconds,
                                              blastOff])
    spaceshipSprite.runAction(spaceshipActions)
  }
  
}

Let’s look at the changes:

  • We got rid of the update method. We don’t need it to function as a timekeeper, as we’re using actions to manage the spaceship spawning process.
  • In didMoveToView, we define a set of actions that will end up managing the task of spawning spaceships:
    • First, we define addAnotherShip as a runBlock action that calls the spawnSpaceship method. Remember that in closures, you have to be explicit when referring to methods and properties of the containing class, which is why there’s an explicit use of self.
    • We define waitAHalfSecond as a waitForDuration action, which we’ll use to create the half-second delay between spaceship spawnings.
    • We create addShipThenWait as a sequence of addAnotherShip followed by waitAHalfSecond. By now, you can probably see where we’re going with this.
    • Then we define addShipsAdInfinitum as a repeatActionForever action that performs addShipThenWait over and over and over again.
    • And finally, we have the scene (remember, it’s a node, so it too can run actions) run the addShipsAdInfinitum action. From this point on, we no longer have to worry about spawning those spaceship sprites — we’ve got actions to do that!

Here’s what it looks like when you run it:

Not bade for 26 lines of code, and that includes the line import SpriteKit!

What we just covered

In this article, we covered:

  • Starting a new Game project in Xcode
  • Restricting an app’s orientation to landscape-only
  • Drawing sprites at a specified location on the screen
  • Screen coordinates in Sprite Kit
  • Anchor points in sprites
  • Nodes, scenes, sprites, and how they’re related
  • Scaling and rotating sprites
  • The event loop and the update method
  • Moving sprites by writing code to update their position
  • Moving sprites by using the moveTo and MoveBy actions
  • Performing multiple actions in sequence using sequence actions
  • Rotating sprites using the rotateByAngle action
  • Introducing a delay for action sequences with the waitForDuration action
  • Responding to touches on the screen with the touchesBegan method
  • Using the update method as a timer to perform tasks at specific intervals
  • Running code as an action using the runBlock action
  • Repeating actions indefinitely using the repeatActionForever action
  • Using actions to reduce the amount of code we have to write to manage multiple onscreen objects

We’ll expand on this material and work towards building an actual game in the following articles in this series.