Tutorial: How to animate Sprites with SpriteKit and Swift

Learn how to use SpriteSheets with SpriteKit:
- Creating sprite sheets with TexturePacker
- Loading sprite sheets
- Using header files to enable compile time checks
- Play animations with a single line of code
This blog post contains full source code available on GitHub.
In this tutorial you are going to learn how you can improve development of SpriteKit games and apps using TexturePacker.
The main advantages over the pure Xcode solution are:
-
Organizing your sprites in folders.
-
Importing multiple formats such as PNG, PSD, SVG, SWF.
-
Compile time checks for sprite names.
-
Creating animations with a single line of code.
Let's start with how to easily create your atlas.

Create your SpriteKit atlas with TexturePacker
To create a new SpriteKit atlas, simply start TexturePacker and click on the Choose Data Format button, then select the SpriteKit (Swift) framework :

Drag & drop the directories containing your sprite images to the Sprites area of TexturePacker. TexturePacker will automatically load and lay out all image files:

In the Settings panel you can enter a path to which the atlas bundle should be written. To add scaled sprites for non-retina displays automatically, open the Scaling variants dialog by clicking on the cog icon. There you can apply the SpriteKit @2x/@1x preset, which defines two scaling variants: @2x with the original sprites, and a variant without suffix containing the sprites scaled by 0.5.

Additionally TexturePacker can generate a .swift file which contains a class providing useful methods for easy SKTexture creation. The name of this file can be specified on the Advanced settings panel.
With the Publish sprite sheet button in the toolbar the Atlas bundle and the Swift file is written to disk.

To use the published sprite sheet in Xcode, drag and drop the .atlasc bundle and the generated .swift file file to your Xcode project:

Xcode asks how the folder should be added. If you create a folder reference , the Xcode project is automatically updated in the future if the atlasc bundle changes (e.g. additional sprite sheets are added).

Creating a SKSpriteNode from the texture atlas
Creating a textured sprite is quite easy, just load the texture and use the SKTexture
object when creating the sprite node:
let texture = SKTexture(imageNamed: "Background")
let sprite = SKSpriteNode(texture: texture)
The first line loads the sprite—looking for a single sprite in the file system— and if not found searching all sprite sheets available to the application.
The second line creates a sprite object using the specified texture.
If you know on which sprite sheet the texture is stored, it is more efficient to fetch the texture directly from the sheet:
let atlas = SKTextureAtlas(named: "CapGuy")
let texture = atlas.textureNamed("Background")
let sprite = SKSpriteNode(texture: texture)
Adding compile-time checks to your SpriteKit project
As the texture image is referenced by its file name, typos in its name or a mismatch due to a reorganized texture atlas cannot be detected at compile-time.
SpriteKit replaces missing images with a dummy graphic which might look strange. Imagine what this would mean for you if it accidently reaches the AppStore...
TexturePacker helps you avoid this: with compile-time checks!
TexturePacker creates a Swift class file together with your atlas. You can simply add it to your Xcode project and create an object of this class:

let sheet = CapGuy()
The class contains all sprite names used in the atlas as properties. It also defines a method for each texture image to create the corresponding SKTexture
object.
let BACKGROUND = "Background"
let CAPGUY_WALK_0001 = "capguy/walk/0001"
let CAPGUY_WALK_0002 = "capguy/walk/0002"
...
func Background() -> SKTexture { return textureAtlas.textureNamed(BACKGROUND) }
func capguy_walk_0001() -> SKTexture { return textureAtlas.textureNamed(CAPGUY_WALK_0001) }
func capguy_walk_0002() -> SKTexture { return textureAtlas.textureNamed(CAPGUY_WALK_0002) }
Using these methods, creating a sprite is a 1-liner:
let sprite = SKSpriteNode(texture: sheet.Background());
If you now rename the sprite and publish the sprite atlas from TexturePacker, the method definition also changes its name. When compiling in Xcode you get a compiler error about a missing sprite.
Simplifying SpriteKit's animation handling
Sprites are considered as animation if they end with a number—e.g. img_01 , img_02 , etc. For these a method returning an array with all textures of the animation is defined.

func capguy_walk() -> [SKTexture] {
return [
capguy_walk_0001(),
capguy_walk_0002(),
capguy_walk_0003(),
...
This makes it extremely simple to animate sprites:
let walk = SKAction.animate(with: sheet.capguy_walk(), timePerFrame: 0.033)
sprite.run(walk)
No more adding single frames, no more worrying about missing animation phases!
Enhancing the animation with additional frames—or removing frames—doesn't require you change the code at all: TexturePacker always fills in the right frames.
Using SKActions to move the sprite
For our sample application we use two animations:
-
walk (left to right)
-
turn (right to left)


These animations can be created as mentioned above:
let walk = SKAction.animate(with: sheet.capguy_walk(), timePerFrame: 0.033)
let turn = SKAction.animate(with: sheet.capguy_turn(), timePerFrame: 0.033)
To walk over the complete iPad display, we have to repeat the animation
let walkAnim = SKAction.repeat(walk, count: 6)
In the animation CapGuy walks without moving forward. We need a move action to move the sprite from left to right, and back. The action gets the same duration as the animation itself:
let moveRight = SKAction.moveTo(x: 900, duration: walkAnim.duration)
let moveLeft = SKAction.moveTo(x: 100, duration: walkAnim.duration)
We have only an animation with CapGuy walking from left to right, but not in the other direction. So we use a scale action with scaling factor -1 to get a mirrored animation. Another action is needed to set the scaling back to 1:
let mirrorDirection = SKAction.scaleX(to: -1, y:1, duration:0.0)
let resetDirection = SKAction.scaleX(to: 1, y:1, duration:0.0)
All actions which are put into a group are executed in parallel. We are not only adding the walk and move actions to a group, but also the mirror / reset actions. They have a duration of 0 and are executed at the beginning of the group, so their scaling factor has direct impact on the walk / move actions:
let walkAndMoveRight = SKAction.group([resetDirection, walkAnim, moveRight]);
let walkAndMoveLeft = SKAction.group([mirrorDirection, walkAnim, moveLeft]);
Now we combine walk & turn actions into a sequence, and repeat this sequence forever:
sequence = SKAction.repeatForever(
SKAction.sequence([walkAndMoveRight, turn, walkAndMoveLeft, turn])
);
Applying SKAction to multiple SKSpriteNodes
SKAction objects can be used for many sprites in parallel. In our example we want to create a new CapGuy sprite each time the user touches the screen. We have to create a new SKSpriteNode only, and run the action on it which we created in the section above:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
let sprite = SKSpriteNode(texture: sheet.capguy_walk_0001())
sprite.position = CGPoint(x: 100.0, y: CGFloat(arc4random() % 100) + 200.0)
sprite.run(sequence!)
addChild(sprite)
}
The complete SpriteKit sample
