SpriteKit Animations and TextureAtlases

This tutorial uses Objective-C. A new version is available here: SpriteKit TextureAtlases with Swift V3
Learn how you can improve development of SpriteKit games and apps using TexturePacker.
- Create your SpriteKit atlas with TexturePacker
- Creating a SKSpriteNode from the texture atlas
- Using SKActions to move the sprite
- Applying SKAction to multiple SKSpriteNodes
Full source code is 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 drag & drop the directories containing your sprite images to the Sprites area of TexturePacker. TexturePacker will automatically load and lay out all image files:

Select SpriteKit in the Data Format field, and enter a path to which the atlas bundle should be written.
Parallel to this .atlasc file TexturePacker generates a .h header file which contains useful macros for easy SKTexture
creation. If you choose the Xcode project directory as output here, the generated header file is automatically found in the include path.

To use the published sprite sheet in Xcode, drag and drop the .atlasc bundle and the generated .h header 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:
texture = [SKTexture textureWithImageNamed:@"Background"];
sprite = [SKSpriteNode spriteNodeWithTexture: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.
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 header file together with your atlas. You can simply import it using:
#import "sprites.h"
The file contains all sprite names used in the atlas as a #define
.
It also defines a macro for each texture image which creates the corresponding SKTexture
object.
#define SPRITES_SPR_BACKGROUND @"Background"
#define SPRITES_SPR_CAPGUY_TURN_0001 @"capguy/turn/0001"
#define SPRITES_SPR_CAPGUY_TURN_0002 @"capguy/turn/0002"
...
#define SPRITES_TEX_BACKGROUND [SKTexture textureWithImageNamed:@"Background"]
#define SPRITES_TEX_CAPGUY_TURN_0001 [SKTexture textureWithImageNamed:@"capguy/turn/0001"]
#define SPRITES_TEX_CAPGUY_TURN_0002 [SKTexture textureWithImageNamed:@"capguy/turn/0002"]
Using these defines, creating a sprite is a one-liner:
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithTexture:SPRITES_TEX_BACKGROUND];
If you now rename the sprite and publish the sprite atlas from TexturePacker, the 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 an NSArray object with all textures of the animation is defined.

#define SPRITES_ANIM_CAPGUY_TURN @[ \
[SKTexture textureWithImageNamed:@"capguy/turn/0001"], \
[SKTexture textureWithImageNamed:@"capguy/turn/0002"], \
[SKTexture textureWithImageNamed:@"capguy/turn/0003"], \
...
This makes it extremely simple to animate sprites:
SKAction *walk = [SKAction animateWithTextures:SPRITES_ANIM_CAPGUY_WALK timePerFrame:0.033];
[sprite runAction: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. Note: SpriteKit will load the texture images as soon as the corresponding animation is started.
That might cause a reduced frame rate the first time an animation is played.
To avoid this the SKTextureAtlas
can be loaded explicitly:
self.atlas = [SKTextureAtlas atlasNamed:SPRITES_ATLAS_NAME];
In contrast to this solution, preloading textures with [SKTexture preloadTextures:withCompletionHandler:]
or [SKTextureAtlas preloadTextureAtlases:withCompletionHandler:]
does not fix the problem with the frame rate drop.
BTW: The manually loaded SKTextureAtlas
also fixes the deficiency that [SKTexture textureWithImageNamed:@"mysprite"]]
is not able to find the retina version mysprite@2x within a texture atlas.
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:
SKAction *walk = [SKAction animateWithTextures:SPRITES_ANIM_CAPGUY_WALK timePerFrame:0.033];
SKAction *turn = [SKAction animateWithTextures:SPRITES_ANIM_CAPGUY_TURN timePerFrame:0.033];
Due to the enormous width of the iPad display we have to repeat the animation a few times:
SKAction *walkAnim = [SKAction sequence:@[walk, walk, walk, walk, walk, walk]];
Note: As SpriteKit does not allow repeat
actions to be nested, we cannot use [SKAction repeatAction:count:]
here,
this would conflict with [SKAction repeatActionForever:]
, see below.
This is why we implement the action as a sequence of walk
actions.
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:
SKAction *moveRight = [SKAction moveToX:900 duration:walkAnim.duration];
SKAction *moveLeft = [SKAction moveToX: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:
SKAction *mirrorDirection = [SKAction scaleXTo:-1 y:1 duration:0.0];
SKAction *resetDirection = [SKAction scaleXTo: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:
SKAction *walkAndMoveRight = [SKAction group:@[resetDirection, walkAnim, moveRight]];
SKAction *walkAndMoveLeft = [SKAction group:@[mirrorDirection, walkAnim, moveLeft]];
Now we combine walk
& turn
actions into a sequence, and repeat this sequence forever:
self.sequence =
[SKAction repeatActionForever:[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:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithTexture:SPRITES_TEX_CAPGUY_WALK_0001];
sprite.position = CGPointMake(100, rand() % 100 + 200);
[sprite runAction:sequence];
[self addChild:sprite];
}
The complete SpriteKit sample
