Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

Sprites, Camera, Actions!

Save for later
  • 20 min read
  • 20 Jul 2015

article-image

In this article by, Stephen Haney, author of the book Game Development with Swift, we will focus on building great gameplay experiences while SpriteKit performs the mechanical work of the game loop. To draw an item to the screen, we create a new instance of a SpriteKit node. These nodes are simple; we attach a child node to our scene, or to existing nodes, for each item we want to draw. Sprites, particle emitters, and text labels are all considered nodes in SpriteKit. The topics in this article include:

  • Drawing your first sprite
  • Animation: movement, scaling, and rotation
  • Working with textures
  • Organizing art into texture atlases

For this article, you need to first install Xcode, and then create a project. The project automatically creates the GameScene.swift file as the default file to store the scene of your new game.

(For more resources related to this topic, see here.)

Drawing your first sprite

It is time to write some game code – fantastic! Open your GameScene.swift file and find the didMoveToView function. Recall that this function fires every time the game switches to this scene. We will use this function to get familiar with the SKSpriteNode class. You will use SKSpriteNode extensively in your game, whenever you want to add a new 2D graphic entity.

The term sprite refers to a 2D graphic or animation that moves around the screen independently from the background. Over time, the term has developed to refer to any game object on the screen in a 2D game. We will create and draw your first sprite in this article: a happy little bee.

Building a SKSpriteNode class

Let's begin by drawing a blue square to the screen. The SKSpriteNode class can draw both texture graphics and solid blocks of color. It is often helpful to prototype your new game ideas with blocks of color before you spend time with artwork. To draw the blue square, add an instance of SKSpriteNode to the game:

override func didMoveToView(view: SKView) {
// Instantiate a constant, mySprite, instance of SKSpriteNode
// The SKSpriteNode constructor can set color and size
// Note: UIColor is a UIKit class with built-in color presets
// Note: CGSize is a type we use to set node sizes
let mySprite = SKSpriteNode(color: UIColor.blueColor(), size:
CGSize(width: 50, height: 50))
// Assign our sprite a position in points, relative to its
// parent node (in this case, the scene)
mySprite.position = CGPoint(x: 300, y: 300)
// Finally, we need to add our sprite node into the node tree.
// Call the SKScene's addChild function to add the node
// Note: In Swift, 'self' is an automatic property
// on any type instance, exactly equal to the instance itself
// So in this instance, it refers to the GameScene instance
self.addChild(mySprite)
}

Go ahead and run the project. You should see a similar small blue square appear in your simulator:

sprites-camera-actions-img-0

Swift allows you to define variables as constants, which can be assigned a value only once. For best performance, use let to declare constants whenever possible. Declare your variables with var when you need to alter the value later in your code.

Adding animation to your Toolkit

Before we dive back in to sprite theory, we should have some fun with our blue square. SpriteKit uses action objects to move sprites around the screen. Consider this example: if our goal is to move the square across the screen, we must first create a new action object to describe the animation. Then, we instruct our sprite node to execute the action. I will illustrate this concept with many examples in the article. For now, add this code in the didMoveToView function, below the self.addChild(mySprite) line:

// Create a new constant for our action instance
// Use the moveTo action to provide a goal position for a node
// SpriteKit will tween to the new position over the course of the
// duration, in this case 5 seconds
let demoAction = SKAction.moveTo(CGPoint(x: 100, y: 100),
duration: 5)
// Tell our square node to execute the action!
mySprite.runAction(demoAction)

Run the project. You will see our blue square slide across the screen towards the (100,100) position. This action is re-usable; any node in your scene can execute this action to move to the (100,100) position. As you can see, SpriteKit does a lot of the heavy lifting for us when we need to animate node properties.

Inbetweening, or tweening, uses the engine to animate smoothly between a start frame and an end frame. Our moveTo animation is a tween; we provide the start frame (the sprite's original position) and the end frame (the new destination position). SpriteKit generates the smooth transition between our values.

Let's try some other actions. The SKAction.moveTo function is only one of many options. Try replacing the demoAction line with this code:

let demoAction = SKAction.scaleTo(4, duration: 5)

Run the project. You will see our blue square grow to four times its original size.

Sequencing multiple animations

We can execute actions together simultaneously or one after the each other with action groups and sequences. For instance, we can easily scale our sprite larger and spin it at the same time. Delete all of our action code so far and replace it with this code:

// Scale up to 4x initial scale
let demoAction1 = SKAction.scaleTo(4, duration: 5)
// Rotate 5 radians
let demoAction2 = SKAction.rotateByAngle(5, duration: 5)
// Group the actions
let actionGroup = SKAction.group([demoAction1, demoAction2])
// Execute the group!
mySprite.runAction(actionGroup)

When you run the project, you will see a spinning, growing square. Terrific! If you want to run these actions in sequence (rather than at the same time) change SKAction.group to SKAction.sequence:

// Group the actions into a sequence
let actionSequence = SKAction.sequence([demoAction1, demoAction2])
// Execute the sequence!
mySprite.runAction(actionSequence)

Run the code and watch as your square first grows and then spins. Good. You are not limited to two actions; we can group or sequence as many actions together as we need.

We have only used a few actions so far; feel free to explore the SKAction class and try out different action combinations before moving on.

Recapping your first sprite

Congratulations, you have learned to draw a non-textured sprite and animate it with SpriteKit actions. Next, we will explore some important positioning concepts, and then add game art to our sprites. Before you move on, make sure your didMoveToView function matches with mine, and your sequenced animation is firing properly. Here is my code up to this point:

override func didMoveToView(view: SKView) {
// Instantiate a constant, mySprite, instance of SKSpriteNode
let mySprite = SKSpriteNode(color: UIColor.blueColor(), size:
CGSize(width: 50, height: 50))
// Assign our sprite a position
mySprite.position = CGPoint(x: 300, y: 300)
// Add our sprite node into the node tree
self.addChild(mySprite)
// Scale up to 4x initial scale
let demoAction1 = SKAction.scaleTo(CGFloat(4), duration: 2)
// Rotate 5 radians
let demoAction2 = SKAction.rotateByAngle(5, duration: 2)
// Group the actions into a sequence
let actionSequence = SKAction.sequence([demoAction1,
demoAction2])
// Execute the sequence!
mySprite.runAction(actionSequence)
}

The story on positioning

SpriteKit uses a grid of points to position nodes. In this grid, the bottom left corner of the scene is (0,0), with a positive X-axis to the right and a positive Y-axis to the top.

Similarly, on the individual sprite level, (0,0) refers to the bottom left corner of the sprite, while (1,1) refers to the top right corner.

Alignment with anchor points

Each sprite has an anchorPoint property, or an origin. The anchorPoint property allows you to choose which part of the sprite aligns to the sprite's overall position.

The default anchor point is (0.5,0.5), so a new SKSpriteNode centers perfectly on its position.

To illustrate this, let us examine the blue square sprite we just drew on the screen. Our sprite is 50 pixels wide and 50 pixels tall, and its position is (300,300). Since we have not modified the anchorPoint property, its anchor point is (0.5,0.5). This means the sprite will be perfectly centered over the (300,300) position on the scene's grid. Our sprite's left edge begins at 275 and the right edge terminates at 325. Likewise, the bottom starts at 275 and the top ends at 325. The following diagram illustrates our block's position on the grid:

sprites-camera-actions-img-1

Why do we prefer centered sprites by default? You may think it simpler to position elements by their bottom left corner with an anchorPoint property setting of (0,0). However, the centered behavior benefits us when we scale or rotate sprites:

  • When we scale a sprite with an anchorPoint property of (0,0) it will only expand up the y-axis and out the x-axis. Rotation actions will swing the sprite in wide circles around its bottom left corner.
  • A centered sprite, with the default anchorPoint property of (0.5, 0.5), will expand or contract equally in all directions when scaled and will spin in place when rotated, which is usually the desired effect.

There are some cases when you will want to change an anchor point. For instance, if you are drawing a rocket ship, you may want the ship to rotate around the front nose of its cone, rather than its center.

Adding textures and game art

You may want to take a screenshot of your blue box for your own enjoyment later. I absolutely love reminiscing over old screenshots of my finished games when they were nothing more than simple colored blocks sliding around the screen. Now it is time to move past that stage and attach some fun artwork to our sprite.

Downloading the free assets

I am providing a downloadable pack for all of the art assets. I recommend you use these assets so you will have everything you need for our demo game. Alternatively, you are certainly free to create your own art for your game if you prefer.

These assets come from an outstanding public domain asset pack from Kenney Game Studio. I am providing a small subset of the asset pack that we will use in our game. Download the game art from this URL:

http://www.thinkingswiftly.com/game-development-with-swift/assets

More exceptional art

If you like the art, you can download over 16,000 game assets in the same style for a small donation at http://kenney.itch.io/kenney-donation. I do not have an affiliation with Kenney; I just find it admirable that he has released so much public domain artwork for indie game developers.

As CC0 assets, you can copy, modify, and distribute the art, even for commercial purposes, all without asking permission. You can read the full license here:

https://creativecommons.org/publicdomain/zero/1.0/

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at $19.99/month. Cancel anytime

Drawing your first textured sprite

Let us use some of the graphics you just downloaded. We will start by creating a bee sprite. We will add the bee texture to our project, load the image onto a SKSpriteNode class, and then size the node for optimum sharpness on retina screens.

Add the bee image to your project

We need to add the image files to our Xcode project before we can use them in the game. Once we add the images, we can reference them by name in our code; SpriteKit is smart enough to find and implement the graphics. Follow these steps to add the bee image to the project:

  1. Right-click on your project in the project navigator and click on Add Files to "Pierre Penguin Escapes the Antarctic" (or the name of your game). Refer to this screenshot to find the correct menu item:
    sprites-camera-actions-img-2
  2. Browse to the asset pack you downloaded and locate the bee.png image inside the Enemies folder.
  3. Check Copy items if needed, then click Add.

You should now see bee.png in your project navigator.

Loading images with SKSpriteNode

It is quite easy to draw images to the screen with SKSpriteNode. Start by clearing out all of the code we wrote for the blue square inside the didMoveToView function in GameScene.swift. Replace didMoveToView with this code:

override func didMoveToView(view: SKView) {
// set the scene's background to a nice sky blue
// Note: UIColor uses a scale from 0 to 1 for its colors
self.backgroundColor = UIColor(red: 0.4, green: 0.6, blue:
0.95, alpha: 1.0);
// create our bee sprite node
let bee = SKSpriteNode(imageNamed: "bee.png")
// size our bee node
bee.size = CGSize(width: 100, height: 100)
// position our bee node
bee.position = CGPoint(x: 250, y: 250)
// attach our bee to the scene's node tree
self.addChild(bee)
}

Run the project and witness our glorious bee – great work!

sprites-camera-actions-img-3

Designing for retina

You may notice that our bee image is quite blurry. To take advantage of retina screens, assets need to be twice the pixel dimensions of their node's size property (for most retina screens), or three times the node size for the iPhone 6 Plus. Ignore the height for a moment; our bee node is 100 points wide but the PNG file is only 56 pixels wide. The PNG file needs to be 300 pixels wide to look sharp on the iPhone 6 Plus, or 200 pixels wide to look sharp on 2x retina devices.

SpriteKit will automatically resize textures to fit their nodes, so one approach is to create a giant texture at the highest retina resolution (three times the node size) and let SpriteKit resize the texture down for lower density screens. However, there is a considerable performance penalty, and older devices can even run out of memory and crash from the huge textures.

The ideal asset approach

These double- and triple-sized retina assets can be confusing to new iOS developers. To solve this issue, Xcode normally lets you provide three image files for each texture. For example, our bee node is currently 100 points wide and 100 points tall. In a perfect world, you would provide the following images to Xcode:

  • Bee.png (100 pixels by 100 pixels)
  • Bee@2x.png (200 pixels by 200 pixels)
  • Bee@3x.png (300 pixels by 300 pixels)

However, there is currently an issue that prevents 3x textures from working correctly with texture atlases. Texture atlases group textures together and increase rendering performance dramatically (we will implement our first texture atlas in the next section). I hope that Apple will upgrade texture atlases to support 3x textures in Swift 2. For now, we need to choose between texture atlases and 3x assets for the iPhone 6 Plus.

My solution for now

In my opinion, texture atlases and their performance benefits are key features of SpriteKit. I will continue using texture atlases, thus serving 2x images to the iPhone 6 Plus (which still looks fairly sharp). This means that we will not be using any 3x assets.

Further simplifying matters, Swift only runs on iOS7 and higher. The only non-retina devices that run iOS7 are the aging iPad 2 and iPad mini 1st generation. If these older devices are important for your finished games, you should create both standard and 2x images for your games. Otherwise, you can safely ignore non-retina assets with Swift.

This means that we will only use double-sized images. The images in the downloadable asset bundle forgo the 2x suffix, since we are only using this size. Once Apple updates texture atlases to use 3x assets, I recommend that you switch to the methodology outlined in The ideal asset approach section for your games.

Hands-on with retina in SpriteKit

Our bee image illustrates how this all works:

  • Because we set an explicit node size, SpriteKit automatically resizes the bee texture to fit our 100-point wide, 100-point tall sized node. This automatic size-to-fit is very handy, but notice that we have actually slightly distorted the aspect ratio of the image.
  • If we do not set an explicit size, SpriteKit sizes the node (in points) to the match texture's dimensions (in pixels). Go ahead and delete the line that sets the size for our bee node and re-run the project. SpriteKit maintains the aspect ratio automatically, but the smaller bee is still fuzzy. That is because our new node is 56 points by 48 points, matching our PNG file's pixel dimensions of 56 pixels by 48 pixels . . . yet our PNG file needs to be 112 pixels by 96 pixels for a sharp image at this node size on 2x retina screens.
  • We want a smaller bee anyway, so we will resize the node rather than generate larger artwork in this case. Set the size property of your bee node, in points, to half the size of the texture's pixel resolution:
    // size our bee in points:
    bee.size = CGSize(width: 28, height: 24)

Run the project and you will see a smaller, crystal sharp bee, as in this screenshot:

sprites-camera-actions-img-4

Great! The important concept here is to design your art files at twice the pixel resolution of your node point sizes to take advantage of 2x retina screens, or three times the point sizes to take full advantage of the iPhone 6 Plus. Now we will look at organizing and animating multiple sprite frames.

Organizing your assets

We will quickly overrun our project navigator with image files if we add all our textures as we did with our bee. Luckily, Xcode provides several solutions.

Exploring Images.xcassets

We can store images in an .xcassets file and refer to them easily from our code. This is a good place for our background images:

  1. Open Images.xcassets from your project navigator.
  2. We do not need to add any images here now but, in the future, you can drag image files directly into the image list, or right-click, then Import.
  3. Notice that the SpriteKit demo's spaceship image is stored here. We do not need it anymore, so we can right-click on it and choose Removed Selected Items to delete it.

Collecting art into texture atlases

We will use texture atlases for most of our in-game art. Texture atlases organize assets by collecting related artwork together. They also increase performance by optimizing all of the images inside each atlas as if they were one texture. SpriteKit only needs one draw call to render multiple images out of the same texture atlas. Plus, they are very easy to use! Follow these steps to build your bee texture atlas:

  1. We need to remove our old bee texture. Right-click on bee.png in the project navigator and choose Delete, then Move to Trash.
  2. Using Finder, browse to the asset pack you downloaded and locate the Enemies folder.
  3. Create a new folder inside Enemies and name it bee.atlas.
  4. Locate the bee.png and bee_fly.png images inside Enemies and copy them into your new bee.atlas folder. You should now have a folder named bee.atlas containing the two bee PNG files. This is all you need to do to create a new texture atlas – simply place your related images into a new folder with the .atlas suffix.
  5. Add the atlas to your project. In Xcode, right-click on the project folder in the project navigator and click Add Files…, as we did earlier for our single bee texture.
  6. Find the bee.atlas folder and select the folder itself.
  7. Check Copy items if needed, then click Add.

The texture atlas will appear in the project navigator. Good work; we organized our bee assets into one collection and Xcode will automatically create the performance optimizations mentioned earlier.

Updating our bee node to use the texture atlas

We can actually run our project right now and see the same bee as before. Our old bee texture was bee.png, and a new bee.png exists in the texture atlas. Though we deleted the standalone bee.png, SpriteKit is smart enough to find the new bee.png in the texture atlas.

We should make sure our texture atlas is working, and that we successfully deleted the old individual bee.png. In GameScene.swift, change our SKSpriteNode instantiation line to use the new bee_fly.png graphic in the texture atlas:

// create our bee sprite
// notice the new image name: bee_fly.png
let bee = SKSpriteNode(imageNamed: "bee_fly.png")

Run the project again. You should see a different bee image, its wings held lower than before. This is the second frame of the bee animation. Next, we will learn to animate between the two frames to create an animated sprite.

Iterating through texture atlas frames

We need to study one more texture atlas technique: we can quickly flip through multiple sprite frames to make our bee come alive with motion. We now have two frames of our bee in flight; it should appear to hover in place if we switch back and forth between these frames.

Our node will run a new SKAction to animate between the two frames. Update your didMoveToView function to match mine (I removed some older comments to save space):

override func didMoveToView(view: SKView) {
self.backgroundColor = UIColor(red: 0.4, green: 0.6, blue:
0.95, alpha: 1.0)
// create our bee sprite
// Note: Remove all prior arguments from this line:
let bee = SKSpriteNode()
bee.position = CGPoint(x: 250, y: 250)
bee.size = CGSize(width: 28, height: 24)
self.addChild(bee)
// Find our new bee texture atlas
let beeAtlas = SKTextureAtlas(named:"bee.atlas")
// Grab the two bee frames from the texture atlas in an array
// Note: Check out the syntax explicitly declaring beeFrames
// as an array of SKTextures. This is not strictly necessary,
// but it makes the intent of the code more readable, so I
// chose to include the explicit type declaration here:
let beeFrames:[SKTexture] = [
beeAtlas.textureNamed("bee.png"),
beeAtlas.textureNamed("bee_fly.png")]
// Create a new SKAction to animate between the frames once
let flyAction = SKAction.animateWithTextures(beeFrames,
timePerFrame: 0.14)
// Create an SKAction to run the flyAction repeatedly
let beeAction = SKAction.repeatActionForever(flyAction)
// Instruct our bee to run the final repeat action:
bee.runAction(beeAction)
}

Run the project. You will see our bee flap its wings back and forth – cool! You have learned the basics of sprite animation with texture atlases. We will create increasingly complicated animations using this same technique later also. For now, pat yourself on the back. The result may seem simple, but you have unlocked a major building block towards your first SpriteKit game!

Putting it all together

First, we learned how to use actions to move, scale, and rotate our sprites. Then, we explored animating through multiple frames, bringing our sprite to life. Let us now combine these techniques to fly our bee back and forth across the screen, flipping the texture at each turn.

Add this code at the bottom of the didMoveToView function, beneath the bee.runAction(beeAction) line:

// Set up new actions to move our bee back and forth:
let pathLeft = SKAction.moveByX(-200, y: -10, duration: 2)
let pathRight = SKAction.moveByX(200, y: 10, duration: 2)
// These two scaleXTo actions flip the texture back and forth
// We will use these to turn the bee to face left and right
let flipTextureNegative = SKAction.scaleXTo(-1, duration: 0)
let flipTexturePositive = SKAction.scaleXTo(1, duration: 0)
// Combine actions into a cohesive flight sequence for our bee
let flightOfTheBee = SKAction.sequence([pathLeft,
flipTextureNegative, pathRight, flipTexturePositive])
// Last, create a looping action that will repeat forever
let neverEndingFlight =
SKAction.repeatActionForever(flightOfTheBee)
// Tell our bee to run the flight path, and away it goes!
bee.runAction(neverEndingFlight)

Run the project. You will see the bee flying back and forth, flapping its wings. You have officially learned the fundamentals of animation in SpriteKit! We will build on this knowledge to create a rich, animated game world for our players.

Summary

You have gained foundational knowledge of sprites, nodes, and actions in SpriteKit and already taken huge strides towards your first game with Swift.

You configured your project for landscape orientation, drew your first sprite, and then made it move, spin, and scale. You added a bee texture to your sprite, created an image atlas, and animated through the frames of flight. Terrific work!

Resources for Article:


Further resources on this subject: