SpriteKit Animations and TextureAtlasses

With this guide you will learn how to use TexturePacker to improve the development of your SpriteKit apps and games. Pure Xcode can be used for this, but using TexturePacker allows you to:

  • Use folders to organize your sprites
  • Import multiple formats such as PNG, PSD, SVG, SWF
  • Run compile-time checks for sprite names
  • Use a single line of code to create animations

Let’s start by creating your atlas.

spritekit-texturepacker

Using TexturePacker to create your SpriteKit atlas

First of all, we need to create a SpriteKit atlas. To do so, open Texture Packer and highlight the directories that contain your sprite images. Next, drag and drop those directories into TexturePacker’s Sprites area. The image files will then be automatically loaded and laid out:

texturepacker-spritekit-1

Under Data Format click SpriteKit and enter the path to which you would like to save your atlas bundle. This will generate two files: .atlasc and .h. The .h is a header file containing macros to facilitate SKTexture creation. If you decide to output this project as an Xcode project directory, the header file will be placed in the include path automatically.

texturepacker-spritekit-2

If you wish to use your sprite sheet in Xcode, simply drag and drop your .atlasc and .h files into your Xcode project:

screenshot-xcode1

A dialog box in Xcode will offer a few options for adding the files. If you choose Create folder references for any added folders, your folders in Xcode will be synchronized with TexturePacker. Therefore, if you make changes in TexturePacker, such as adding sprite sheets, Xcode will automatically be updated with those changes.

screenshot-xcode2

Creating a SKSpriteNode from the texture atlas

A textured sprite can be created by loading the texture and using the SKTexture object to create the sprite node:

texture = [SKTexture textureWithImageNamed:@"Background"];
sprite = [SKSpriteNode spriteNodeWithTexture:texture];

The first line searches for a specific sprite (first in its specified location, and if not found there, searching all other available sheets) and then loads it.

The second line uses the texture specified to create the sprite object.

Adding compile-time checks to your SpriteKit project

In SpriteKit, if a graphic cannot be found, it is replaced with a simple placeholder graphic. This can occur for numerous reasons, including a typo in the filename or a mismatch caused by a reorganized texture atlas.

Just think how foolish you would look if this went unnoticed and your program ended up in the AppStore with placeholder graphics! To combat this, TexturePacker offers compile-time checks.

When you create your atlas, TexturePacker also creates a header file. You can import this file with:

#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.

spritekit-sprite-missing

#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"]

You only need one line to create a sprite when you use these defines:

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

The filenames of animation sprites always end in sequential numbers (img_01, img_02, etc.). An NSArray object collects these textures to define the animation.

spritekit-animationphases

#define SPRITES_ANIM_CAPGUY_TURN @[ \
  [SKTexture textureWithImageNamed:@"capguy/turn/0001"], \
  [SKTexture textureWithImageNamed:@"capguy/turn/0002"], \
  [SKTexture textureWithImageNamed:@"capguy/turn/0003"], \
  ...

Therefore, animating sprites is simple:

SKAction *walk = \
  [SKAction animateWithTextures:SPRITES_ANIM_CAPGUY_WALK \
  timePerFrame:0.033];
[sprite runAction:walk];

This completely eliminates the need to add single frames or worry about animation phases!

If you add or remove frames, you don’t have to worry about changing the code — TexturePacker will update it automatically!

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.

Moving your sprite using SKActions

We will use two animations for this demonstration:

  • walk (left to right)
  • turn (right to left)

capguy-walk capguy-turn

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];

Because the iPad is so much wider than other platforms, the animation will need to be repeated:

SKAction *walkAnim = \
  [SKAction sequence:@[walk, walk, walk, walk, walk, walk]];

Note: Repeat actions cannot be nested in SpriteKit. Using [SKAction repeatAction:count:] would conflict with [SKAction repeatActionForever:]. Therefore, we must simply use a sequence of walk actions instead.

Currently, CapGuy goes through the motions of walking, but does not actually move forward. To fix this, we must use a move action to allow him to move from side to side. The same duration is used for both the animation and the action:

SKAction *moveRight  = \
  [SKAction moveToX:900 duration:walkAnim.duration];
SKAction *moveLeft   = \
  [SKAction moveToX:100 duration:walkAnim.duration];

Now CapGuy walks from left to right, but not right to left. To change this, we can mirror our original animation by using a scale action and setting the scaling factor to -1; then using another action to return the scaling factor to 1:

SKAction *mirrorDirection  = \
  [SKAction scaleXTo:-1 y:1 duration:0.0];
SKAction *resetDirection   = \
  [SKAction scaleXTo:1  y:1 duration:0.0];

All actions within a group are executed simultaneously. 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 indefinitely:

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

iphone-screenshot

Source code available for download

The source code is available on GitHub.
Either clone it using git:

git clone https://github.com/AndreasLoew/TexturePacker-SpriteKit.git

or download one of the archives:

TexturePacker-SpriteKit.zip

TexturePacker-SpriteKit.tar.gz

SpriteKit Tutorials

What is Sprite Kit?

Sprite Kit is a new game development engine created by Apple. It’s part of iOS7 and Mac OS X 10.9 Mavericks.

Apple describes it with the following words:

Develop high-performance 2D games with the powerful new Sprite Kit framework, which combines everything you need to animate sprites, simulate physics and create beautiful particle systems all in one easy-to-use set of APIs.

— Apple: iOS7 beta for Developers

This gives you powerful tools:

  • An easy to use game engine
  • Specialized APIs for iOS
  • Included Physics Engine
  • Included Particle Engine
  • Animations

Why is this site interesting for me?

Here’s the information you need to do a good start into game development.

  • How to start your first game
  • What is a PhysicsEngine and what can I do with it?
  • What is a ParticleSystem?
  • Tools
  • Components you can use in your tutorial
  • Already using cocos2d or some other framework? Learn how to switch to Sprite Kit.

What about Apple’s NDA?

Sprite Kit is currently is under NDA – Apple does not allow public discussion about it until it’s officially released. This is why we can not yet release specific information about Sprite Kit.

Don’t worry – we’ll provide you with useful information right now – presenting tools, and general information about 2d game development.

We also work on tutorials right now – so you can get started with Sprite Kit as soon as the NDA is lifted.