SpriteKit with Swift: Introducing the Level Editor

About: This project uses SpriteKit’s level editor interface to create a sandbox for experimenting with various SpriteKit features, including physics interactions and responding to touches. It is intended to be a surface-level introduction to the level editor so you can get setup and start playing around quickly and easily.

This project was created in Xcode 6.1.1 using the Swift programming language. The full project can be found here.

Initial Setup

Open Xcode and create a new project (⇧ + ⌘ + N) for an iOS Game. Setup your project with the following settings:

  • Product Name: DemoSpritePlayground (or whatever name you want)
  • Language: Swift
  • Game Technology: SpriteKit
  • Devices: iPad

I also limited my project to Landscape-only orientation for simplicity’s sake. You can change these settings by selecting the project (top-level) in the file browser and checking/unchecking the boxes under Deployment Info.

Project Resources

You can get the images/textures used in this project here, or you can use whatever images you want. All that really matters is that you have a background image and some sprite textures to choose from.

If you do choose to download the resources above, include them in your project by unzipping Images.zip and dragging the Images folder into your project directory in Xcode. When prompted, make sure “Copy items if needed” is selected and click Finish:

Copy Items if Needed

You should now see the Images folder in your project directory.

Getting Started

First you’ll want to clear out much of the existing project code. Start by editing the methods in the GameScene.swift file so that they look like this:

override func didMoveToView(view: SKView) {
    /* Setup your scene here */
}

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

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

You won’t need to alter AppDelegate.swift or GameViewController.swift for this project.

Level Designer Overview

Now that your project is a blank slate, we’ll get started with Xcode’s extremely useful Level Editor interface. This is also referred to as the “Level Designer” or “Scene Editor,” the latter of which seems only to be the case in projects that use the SceneKit library (as opposed to SpriteKit). Fair warning: I may use these terms interchangeably throughout this tutorial — be aware that other resources you use may refer to it as something else.

Anyway, in your project directory, you will see a file called GameScene.sks. Click it and you’ll see the Level Editor interface. Some of its features will look familiar to you right away if you’ve ever used Xcode Storyboards or the interface builder. The following image and its description are courtesy of this excellent tutorial, which is part of Techotopia’s iOS 8 App Development Essentials:

Please visit the Techotopia page linked above to read this great tutorial!
Please visit the Techotopia page linked above to read this great tutorial!

Image key:

  • (A) Scene Canvas – This is the canvas onto which nodes may be placed, positioned and configured.
  • (B) SKNode Inspector Panel – This panel provides a range of configuration options for the currently selected node in the scene canvas.
  • (C) Object Library – The Object Library panel contains a range of node and effect types that can be dragged and dropped onto the scene. Clicking on the far right button in the Object Library toolbar displays the Media Library containing items such as textures for sprites.
  • (D) Simulate/Edit Button – Toggles between the editor’s simulate and edit modes. Simulate mode provides a useful mechanism for previewing the scene behavior without the need to compile and run the application.
  • (E) Zoom – Buttons to zoom in and out of the scene canvas. You’ll notice right away when using the touchpad or a mouse wheel to zoom that for some reason, zooming and scrolling are inverted in this interface. If it really bothers you, just use the zoom buttons.

Adding a Background via Code

For the purpose of illustrating the difference between creating Sprites programmatically and creating them via the Level Editor, we’re going to set the background in code. Add the following two lines to the didMoveToView method your GameScene.swift file:

let bgSprite = SKSpriteNode(imageNamed: "genericBg.jpg")
self.addChild(bgSprite)

Build and run the project, and you should see the background image as expected. That was rather painless, right?

Note: In this case, we don’t have to reposition or scale the image, since we’re using an oversized image to begin with, but if you’re using an image that is exactly the size of the screen, it may not appear where you expect. This is because addChild adds new sprites at position 0, 0 (bottom left corner) by default. If you want to use an image sized specifically for your screen, you’ll have to change its position.

Adding a Background via the Level Editor

First I’m going to make you delete what you just did (sorry!) and show you another way to accomplish the same thing, with a little more control. Go to GameScene.swift and remove the two lines you just added to the didMoveToView method.

Now click on your GameScene.sks file to see the level editor interface. What I’m about to show you is so stupidly easy I’m almost hesitant to recommend it. All you have to do to add your background sprite to the scene is click the genericBg.jpg file in your project directory and drag it onto the scene. Boom. Done.

Doing More with Less

Why did I make you add the background both ways? Well, if you’ve ever added sprites programmatically before, you know that doing any additional setup (positioning it, giving it a physics body, etc.) requires many more lines of code. With the level editor, you can accomplish most of this simple setup without writing a single line of code.

Select the background sprite you just added and explore the node inspector for a bit. You’ll notice you can set several of the sprite’s properties here:

Xcode Node Inspector

Change the following properties of your background sprite:

  • Name: background
  • Size: 1024 x 770 — or whatever size roughly fits the scene’s boundary.
  • Body Type (under Physics Definition): None

Position the background so that it covers your whole scene.

Add More Sprites

Use the same method outlined above to add some other sprites to your scene. In my example, I’m using the genericBigCircle.png and genericBigSquare.png images. Position them wherever you want on screen, then click the Simulate button. You should see your sprites fall right off the screen! The level editor’s Simulate feature is an excellent way to test physics interactions without having to build and run the app.

Names

Give each sprite you added to the scene a unique name. This will be important when we want to make them respond to touches. I named my sprites “square1” and “circle1” for their respective shapes.

Physics Bodies

For now, we don’t want our sprites moving around unless we tell them to. For each sprite you added, uncheck its Affected By Gravity property (under Physics Definition). You can also change the Body Type if you want. Changing this setting to Alpha mask will result in the sprite having a physical body exactly the size and shape of the sprite itself, which allows for the most precise interactions.

There are many other settings here we could get into, but we’ll leave those for a more in-depth guide in the future.

Note: Another way to stop your sprites from falling off the screen is to add the following line to GameScene.swift in the didMoveToView method:

physicsWorld.gravity = CGVectorMake(0, 0)

Choose whichever method best suits your project, or do it both ways.

Responding to Touches

We’re going to make our sprites into selectable objects so we can drag them around the screen and make them run into one another, for science! All of the code in this section should be inside the GameScene.swift file.

Sprite Types

Something you may want to do is create a Struct for types of sprites. This can be useful if you placed multiple squares and circles on your scene and you want each type of shape to respond to touches or other events in different ways. We’ll need it for sure for this project, so add the following lines just below import SpriteKit:

struct SpriteType {
    static let Circle : String = "circle"
    static let Square : String = "square"
    static let Background : String = "background"
}

Subclass Methods

In addition to the three methods already in this class, you’ll need one you can call when you want to select a sprite by touching it. Add this method to GameScene.swift:

/*
    This method gets the sprite node at the touched location, if one exists.

    Note: In this project, a node will always exist, since the background is a sprite itself.
    This means we'll have to do some special handling to determine the type of sprite selected.
*/
func selectSpriteForTouch(location: CGPoint) {

}

Handling Touch Events

Before we can do anything with selectSpriteForTouch, we need to handle touch events. Add the following code to touchesBegan:

// Record initial location of touch.
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self)

// Select the sprite where the touch occurred.
selectSpriteForTouch(location)

We’re doing three things here:

  1. Since touchesBegan takes an NSSet of generic objects, we need to get one object and cast it as a UITouch so we can access information about it.
  2. locationInNode(self) gives us a CGPoint where the touch occurred. We’re telling it to look for a point relative to the whole scene (a.k.a. “self”).
  3. Finally we’re calling selectSpriteForTouch with the location of the touch.

Selecting a Sprite

How do we go about actually telling the game which sprite to select? Does GameScene.swift even know about the sprites in our .sks file? We have no references to the sprites in code, so how do we “find” them?

Fortunately, this is also ridiculously easy. Add the following code to selectSpriteForTouch:

// Get the node at the touched location.
let touchedNode = self.nodeAtPoint(location) as SKSpriteNode
println("Selected sprite with name: (touchedNode.name)")

To ensure this works, run the project and touch some sprites, then check the console output. Make sure you also click the background!

My console output looked like this:

Selected sprite with name: Optional(“background”)
Selected sprite with name: Optional(“circle1”)
Selected sprite with name: Optional(“background”)
Selected sprite with name: Optional(“square1”)
Selected sprite with name: Optional(“background”)
Selected sprite with name: Optional(“background”)
Selected sprite with name: Optional(“square1”)

There are two important things to note here:

  1. The sprites we added with the level editor are Optionals, which will need to be unwrapped if we want to access or change some of their properties.
  2. We are able to select the background, which means our sprite selection just got a little more complicated.

Responding to Different Sprite Types

There are many ways/places to do this, and I can’t promise my way is the best way or even a good way, but since our end goal here is really to give you a sandbox to play in, it will have to suffice.

Storing the Selected Node

First, add a variable to the top of the GameScene class that we’ll use to store the selected node so we can reference it in other methods:

var selectedNode: SKSpriteNode?

Action Depending on SpriteType

Now we’re going to tell the game to act only if the selected node is something other than the background. Add the following code to selectSpriteForLocation:

if touchedNode.name!.hasPrefix(SpriteType.Background) {
    // Reset selectedNode (prevents selectedNode from "snapping" to touched location).
    // Thanks to Robin from the comments section for pointing this out!
    selectedNode = nil
} else {
    // Store the selected node for future use.
    if selectedNode != touchedNode {
        selectedNode = touchedNode
    }
}

It’s important to note that touchedNode.name will require unwrapping in order to use hasPrefix(). In this case it’s safe to explicitly unwrap since we know every touch must be inside the boundaries of some node. Also note that this won’t work if you forgot to name your sprites when you added them to the level editor or if you forgot to add the SpriteType struct to top of the GameScene.swift file.

Moving the Selected Node

Finally, we’ll tell the selected sprite to move. Override touchesMoved:

override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
    // Record location of touch.
    let touch = touches.anyObject() as UITouch
    let touchLocation = touch.locationInNode(self)

    // Define a movement action for the sprite.
    let moveAction = SKAction.moveTo(CGPointMake(touchLocation.x, touchLocation.y), duration: 0.0)

    // Move the sprite (if one was selected).
    if let node = selectedNode {
        node.runAction(moveAction)
    }
}

Overview of this method:

  1. It captures the location of the touch, just like touchesBegan. The difference between the two methods is that this one will fire constantly, as long as the touch keeps moving.
  2. moveAction is a variable that stores an SKAction, which is something that can be enacted upon a sprite using the SKNode class’s runAction method. SKAction.moveTo takes a CGPoint for the location to move to, as well as a duration (how long should moving there take?).
  3. Finally, it executes the action on the selected node, as long as selectedNode is not nil.

Now if you build and run your project, you should be able to touch and drag your sprites around the screen. Try running one into another to see what happens (notice the abysmal FPS — thanks Quicktime!).

All Done! (technically)

If you’ve gotten this far, you technically have all you need to create a sandbox environment for experimenting with sprite creation, selection, and interaction, but check back in the next couple weeks for a forthcoming post involving gravity fields, particle emitters, and other awesome things from the level editor’s object library.

In the mean time, I encourage you to delve deeper into the level editor’s features on your own, especially setting sprite properties in the node inspector panel and adding new nodes from the object library. Also please share anything interesting you discover!

Where to go from here?

Check out some of these resources to learn about more features you can add to your SpriteKit sandbox:

SpriteKit with Swift: Orienting Sprites to a Location

About this project: This project uses Xcode’s SpriteKit Game template to practice moving sprites in response to touches. It was created in Xcode v6.1.1 using the Swift programming language.

Getting Started

Open Xcode and create a new project (⇧ + ⌘ + N) for an iOS Game.

New iOS Game
New iOS Game

In this example, we’re calling the project DemoSpriteOrientation and we’re making it for iPad using Swift. Ensure SpriteKit is selected for the Game Technology option.

SpriteKit Project for iPad
SpriteKit Project for iPad

The first thing we’re going to do is click on GameScene.swift and remove everything inside the touchesBegan method so that it looks like this:

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

Build and run the project, and you should see a gray screen with the “Hello, World” label in the middle.

Setting the Scene

Let’s add a sprite to the scene. This was previously handled inside touchesBegan, but we want the scene to contain a sprite on load. First, add a sprite property to the GameScene class:

//
class GameScene: SKScene {
    var sprite: SKSpriteNode?
//

Now replace the contents of didMoveToView:

//
override func didMoveToView(view: SKView) {
    // Create space ship sprite.
    sprite = SKSpriteNode(imageNamed: "Spaceship")
    sprite!.xScale = 0.5
    sprite!.yScale = 0.5

    // Give the sprite an initial location in the center of the screen.
    sprite!.position = CGPointMake(frame.width / 2, frame.height / 2)

    // Add the sprite to the scene.
    self.addChild(sprite!)
}
//

Build and run the app. You should now see your space ship in the middle of the screen upon loading the scene.

iPad Simulator View
iPad Simulator View

That’s it for the initial setup.

Responding to Touches

Upon touching the screen, we want the space ship to perform two actions. First, it should rotate so that it faces its direction of travel. Second, it should move to the location on the screen that was touched. Since rotation is trickier, we’re going to start by telling the ship how to move.

Inside the touchesBegan method, add the following code:

//
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
    // Capture the touch event.
    if let touch:UITouch = touches.first as? UITouch {

        // Get the position that was touched.
        let touchPosition = touch.locationInNode(self)

        // Define actions for the ship to take.
        let moveAction = SKAction.moveTo(touchPosition, duration: 0.5)

        // Tell the ship to execute actions.
        sprite!.runAction(moveAction) 
    }
}
//

Note: The code snippet above has changed as of 6/22/2015 (due to a reader pointing out conflicts in a new version of Swift. In the original version of this code, line 4 was a single line of code (not a block), which read: let touch = touches.anyObject() as UITouch. I have not tested whether the new version works yet. Thanks Paul for pointing out the conflict since the most recent Swift update.

This is all you need to tell the sprite to move to a designated point. To elaborate on what’s happening here:

  • touches.anyObject() as UITouch is capturing the touch and casting it as a UITouch object.
  • touch.locationInNode(self) is fetching a CGPoint containing the x and y coordinates of the touch event within the SKScene (a.k.a. “self”).
  • SKAction.moveTo is a method that takes two parameters:
    1. A CGPoint containing the x and y coordinates of a location. We’re passing the location of the touch event.
    2. An NSTimeInterval Double within which the action should take place. We’re passing a brisk move-speed of 0.5.
  • runAction(moveAction) is telling the sprite to execute whatever action is stored in moveAction.

Build and run the app. Your ship should now move around to wherever you touch on screen, but there is a problem. It’s always facing up! That’s not very natural-looking travel for a space ship, so let’s fix it.

Orienting the Sprite

This is the challenging part. Where do we even start? Well, we’re going to have to rotate the ship somehow. Let’s take a look back at a couple lines that used to be in the touchesBegan method.

//
let action = SKAction.rotateByAngle(CGFloat(M_PI), duration:1)

sprite.runAction(SKAction.repeatActionForever(action))
//

Ok, so from looking at this we know how to rotate the ship. SKAction.rotateByAngle is very similar to SKAction.moveTo, in that it takes two parameters and stores the resulting action to be used later. The difference is that in the case of rotateByAngle, the first parameter is a CGFloat for the radians by which to rotate. Now the challenge is finding the angle in radians.

Where to begin? Start with what we know.

  1. We know the sprite is positioned on a CGPoint, which has x and y coordinates.
  2. We know the touch occurs at a CGPoint, which has x and y coordinates.
  3. With those two points, we can imagine a right triangle.

triangle

The next step is to have a passing knowledge of Geometry, or — if you’re like me and haven’t looked at a Math textbook since high school — the next step is to hit Google. My myriad searches included such inspired Google-fu as “xcode swift rotate sprite to face point” and “xcode swift orienting sprites,” which led to the perusal of numerous Stack Overflow posts, which led to me having a vague certainty that, somehow, the tangent was important. Finally, I stumbled upon this wenda.io post.

The magic all happens in a little function called atan2. You can find a description of this function here. In short, it takes two parameters for the x and y coordinates and spits out the arc (or inverse) tangent measured in radians. Remember rotateByAngle‘s method definition?

//
class func rotateByAngle(_ radians: CGFloat,
    duration sec: NSTimeInterval) -> SKAction
//

Radians is what we’ve been looking for.

Following the example in the wenda.io post, let’s pass the values we need into atan2 by finding the difference between our ship’s starting and ending points. Change touchesBegan to include the following:

//
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
    /* Called when a touch begins */
    // Capture the touch event.
    let touch = touches.anyObject() as UITouch

    // Get the position that was touched (a.k.a. ending point).
    let touchPosition = touch.locationInNode(self)

    // Get sprite's current position (a.k.a. starting point).
    let currentPosition = sprite!.position

    // Calculate the angle using the relative positions of the sprite and touch.
    let angle = atan2(currentPosition.y - touchPosition.y, currentPosition.x - touchPosition.x)

    // Define actions for the ship to take.
    let rotateAction = SKAction.rotateByAngle(angle, duration: 0.0)
    let moveAction = SKAction.moveTo(touchPosition, duration: 0.5)

    // Tell the ship to execute actions.
    sprite!.runAction(SKAction.sequence([rotateAction, moveAction]))
}
//

What you’ve just done:

  • sprite!.position gets the CGPoint location of the ship. We’re only doing this so the syntax inside atan2 is a little simpler.
  • atan2 is taking a y value that is the difference between the starting and ending y values, and an x value that is the difference between the starting and ending x values.
    Note: Pay close attention to the order in which the parameters must be passed into atan2(y, x).
  • SKAction.rotateByAngle is going to rotate our ship by the angle returned by atan2, and should do so instantly.
  • Finally, we’ve modified our runAction statement to include an SKAction.sequence, which takes an array of SKActions and executes them in order.

Build and run the app!

Oops

This isn’t what we want at all. Taking another look at the oft-referenced wenda.io post post, I noticed the angle from atan2 was being passed into a different SKAction. The one we want to use is SKAction.rotateToAngle.

To my shame, at the time of this writing I still haven’t investigated exactly why these two methods behave so differently with the same input. I will update this post when I know more. For now, change your rotateAction to use the proper action:

//
let rotateAction = SKAction.rotateToAngle(angle, duration: 0.0)
//

There, that should do it. Build and run again.

Hmm…

Something still isn’t right. Our ship is flying sideways. Why? It turns out the answer is so stupidly simple you’ll be glad you didn’t have to spend any real time wracking your brain about it like I did. It has to do with the base orientation of the ship. Since it seems to point its side in the direction we want its nose to be pointing, all we really have to do is slightly increase the angle to which it’s rotating.

Modify your rotateAction variable to add 90 degrees in radians to the rotation (a.k.a. π/2):

//
let rotateAction = SKAction.rotateToAngle(angle + CGFloat(M_PI*0.5), duration: 0.0)
//

It Works!

Congratulations! Now you know how to rotate a sprite so that it faces any point on the screen. Here are just a few possible applications for this very simple code:

  • Use it in touchesMoved to make a sprite change its direction in real time as you drag your finger on the screen.
  • Use it to make your hero face an enemy who has appeared on screen.
  • Use it to make projectiles like missiles or arrows face the direction they’re fired in.
  • Use it to create a “heat-seeking” projectile that always aims at the position of another sprite.

If you’re still having difficulty…

Here is the full code for the GameScene.swift file. This is all you need for the project to run as intended:

//
import SpriteKit

class GameScene: SKScene {

    var sprite: SKSpriteNode?

    override func didMoveToView(view: SKView) {
        // Create space ship sprite.
        sprite = SKSpriteNode(imageNamed: "Spaceship")
        sprite!.xScale = 0.5
        sprite!.yScale = 0.5

        // Give the sprite and initial location in the center of the screen.
        sprite!.position = CGPointMake(frame.width / 2, frame.height / 2)

        // Add the sprite and label to the scene.
        self.addChild(sprite!)
    }

    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        /* Called when a touch begins */
        // Capture the touch event.
        let touch = touches.anyObject() as UITouch

        // Get the position that was touched (a.k.a. ending point).
        let touchPosition = touch.locationInNode(self)

        // Get sprite's current position (a.k.a. starting point).
        let currentPosition = sprite!.position

        // Calculate the angle using the relative positions of the sprite and touch.
        let angle = atan2(currentPosition.y - touchPosition.y, currentPosition.x - touchPosition.x)

        // Define actions for the ship to take.
        let rotateAction = SKAction.rotateToAngle(angle + CGFloat(M_PI*0.5), duration: 0.0)
        let moveAction = SKAction.moveTo(touchPosition, duration: 0.5)

        // Tell the ship to execute actions.
        sprite!.runAction(SKAction.sequence([rotateAction, moveAction]))

    }

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