Others Talk, We Listen.

iOS 7 Tutorial Series: Introduction to Sprite Kit

by Mark DiGiovanni on Oct 29, 2013

Native to iOS 7, Sprite Kit empowers the developer to build fully featured, high quality games using Objective-C for both iOS and OS X. Sprite Kit renders the 2D frames at very high frame rates using the device's (iPhone, iPad, Macbook Pro, etc.) graphics processor. You as the developer will have a lot of fun creating games with this framework.

Sprite Kit supports a variety of content including:

  • Images
  • CGPath Shapes
  • Video
  • Text

It is very easy to create a game that builds for both the iOS and OS X platforms. For example, SKView will inherit from UIView (iOS) or NSView (OS X). Most of the heavy lifting will be handled for you when you create two build targets—one for each platform. To see this in action, take a look at the Adventure Game. You will be able to download the source code and run it on your Mac or iOS device.

This article is intended for developers who already have a good understanding of the iOS platform. However, if you have experience developing applications for OS X, you should feel right at home. If you are new to iOS development and Objective-C, I suggest bookmarking http://www.raywenderlich.com, and working through some starter tutorials beginning with: Learn to Code iOS Apps 1: Welcome to Programming.

This introduction to Sprite Kit will take a look at many of the core and most common features that you will use when developing your games by looking at the different parts of the demo application. The graphical elements were created by Vicki Wenderlich and released under the Creative Commons Attribution License.

These assets are included in the demo application and can be downloaded here. This article will reference the source code of the demo game. Please download the code before continuing further.

In this game, you are a monkey flying through space trying to snag as many bananas as possible without getting hit by asteroids! But incase you do collide with an asteroid, make sure to collect hearts as they will restore some of your life.

DemoApp2

The core topics covered are:

  • The Game Loop
  • Layers and Nodes
  • The Parallax effect
  • Emitters and Actions
  • Some Physics and Collisions
  • Texture Atlas and Animation
  • Game Audio

The Game Loop

The game loop is the process used by the system to render frames to the user on screen. The system will attempt to process frames as quickly as possible to produce a smooth visual and interactive experience for the user. This is similar to how movies are viewed. When you watch a movie you are essentially watching a series of still images that are changed at a very high frequency. For the NTSC format (USA and other regions), the frame rate is approximately 30 frames per second (FPS). This rate is constant for movies, but it can vary for games.

Sprite Kit will attempt to render frames at a rate of 60 FPS. As the complexity of the code increases, the FPS may drop below 60 FPS. Once it begins to drop below 30 FPS, the user will begin to notice and feel the lag.

The game loop is made up of six steps, three of which you as the developer can influence as depicted below in the green areas. The other three are handled by Sprite Kit. This entire process occurs for every single frame that is rendered on screen up to 60 times per second—that is pretty fast!

Below is a depiction of the game loop:

SpriteKitGameLoop

  1. The update method -update is called on the scene. This is where the bulk of the game logic will occur such as moving the background elements, updating the health of the monkey, and moving the monkey to his next location. The monkey's space dust trails are also influenced in this step.

  2. SKScene will then evaluate and apply the actions in the scene. There are many actions defined in this game such as spawning asteroids, bananas, and hearts. These spawned elements could be manually processed in the -update method, but SKActions give you some real power to work with.

  3. The did evaluate actions method -didEvaluateActions is called. This gives the developer a chance to respond to the actions applied by the scene. For example, if an object has been moved completely off screen—such as a bolt of lighting or a rolling ball, it can be cleaned up and removed. As the asteroids are moved off screen, they are cleaned up in the action sequence.

  4. SKScene will then simulate the physics that are setup on the nodes in the scene such as tilting the monkey when he collides with an asteroid.

  5. The did simulate physics method -didSimulatePhysics is called. The developer has a chance to respond to the results of the applied physics—such as what happens to the monkey when he collides with an asteroid. We could modify the results of the applied physics. This is also the last opportunity to make changes to the scene before the frame is rendered on screen.

  6. SKView renders the scene

The final view of this frame is rendered for display to the user on screen. This demo displays the node count and frame rate in the lower right side of the screen. You will notice a marked difference when running the game in the simulator vs an actual device such as an iPhone 5 or 5s.

If you notice here that the frame render rate is too slow in your own game, you may want to revisit the complexity of the code in 1, 3, & 5 above, as well as the amount of nodes being rendered. Too many particles being emitted could be the culprit—more on this later.

Sprite Kit Basics

All nodes descend from SKNode—it's the node of nodes much like NSObject is the object of objects—if you will. SKNode ultimately descends from NSObject too: SKNode : UIResponder : NSObject.

The SKNode has no inherent size or content itself, however you can set its position, scale, alpha, and other attributes directly on it. This is useful when using an SKNode for organizational purposes such as using multiple layers to separate the parallax background and gameplay nodes. In a node hierarchy, setting properties such as scale, position, and visibility will also influence child nodes—hiding a parent node will hide all of its children.

Open up the demo project you have downloaded, and then open Main.storyboard. You will notice one View Controller. Select its View and you will see that its custom class is SKView. Click on the class arrow to open up the SKView class:

SKViewClass

SKView will either subclass UIView (iOS) or NSView (OS X):

#if TARGET_OS_IPHONE
SK_EXPORT @interface SKView : UIView
#else
SK_EXPORT @interface SKView : NSView
#endif

It is responsible for displaying content on the screen. When creating the project using the "SpriteKit Game Template", this storyboard with a view controller, and a view set to SKView is created for you. You can create additional view controllers & storyboards and implement transitions between them. Many games that you are already familiar with have at least a main menu, a game play, and an end game screen.

Feel free to look through this header file, then switch over to ViewController.m, and look in the -viewDidLoad selector. This is where the SKView is created and the scene is presented.

The project game template will also create header and class files called MyScene.h and MyScene.m. These are the main files that you will be working with when building your game. You do not need to keep these generated files when you start your project. You can rename them, or delete and replace them with another set of scene files if you like. But in any case, the scene will need to be presented by the SKView, and it must inherit from SKScene.

SKScene

SKScene inherits from SKEffectsNode which inherits from SKNode. Only one scene can be presented at a time. When MyScene is presented by the SKView, the Sprite Kit game loop begins and processes each frame in the order shown in the game loop above.

Open MyScene.h and you will notice several properties. These properties are used to setup the organizational nodes in the game. For example, all the background nodes are children of the layerBackgroundNode while the gameplay nodes are children of the layerGamePlayNode. The methods listed allow the Heads Up Display (HUD) layer to be updated to reflect the increased score and current health.

Open MyScene.m and look at the -initWithSize selector. Here the layers and child nodes are setup. The SKPhysicsWorld is configured for this scene. The physicsWorld property is created for you on SKScene—you do not create SKPhysicsWorld objects directly. Here some basic properties are set such as zero gravity in space, and the contactDelegate is set to self.

The physics world handles gravity, node contacts such as collisions between the monkey and an asteroid, and so on. It will make calls to the delegate (SKPhysicsContactDelegate) methods in MyScene.m -didBeginContact and -didEndContact.

Setting up the HUD - SKSpriteNode & SKLabelNode

The SKSpriteNode and SKLabelNode will be two of the common SKNode subclasses that you will use frequently when building your games. Look at the -setupHUD selector in MyScene.m.

Here the HUD layer is created with a background, the score label, and the life of the monkey in hearts. Each heart is an SKSpriteNode. This type of node draws textures from images (in the case of empty hearts) or colors (as in the case of the hudBackground), or can be a blend of the two (full red or pinkish hearts) and is automatically managed in memory. As you will see when playing the game, the color blend factor is altered on the hearts to show a decreased blend factor when the monkey takes damage. The heart SKSpriteNode is actually a blend of a white heart image and [SKColor redColor]. SpriteKitBase.h defines SKColor as an alias for UIColor (iOS) and NSColor (OS X).

lightheart

When the monkey's health is reduced by the calculated percentage and a heart is drained, it will be displayed as empty. This occurs when the health is updated in the -update selector. SKTextureNodes can also be created from a subregion of another texture. This new node will not create a new object in memory—it will reuse the one it is being created from making the process more efficient.

An SKLabelNode is perfect for displaying the score. It supports all the system fonts, custom fonts, and will display the text as a sprite in a single line. It will also respond to any SKAction just like other nodes that derive from SKNode. The score is configured by setting the properties on the label node, and adding it to the HUD layer.

The Parallax Effect & the SKEmitterNode

Parallax is about perception. When viewing an object at an angle in relation to another object, it may appear closer or farther away. For example, the iOS 7 applications and the background image shift as the device is angled—the app icons move, or is it the background that moves? More on Parallax.

In the demo application, this effect is implemented on the background layer. In MyScene.m, look at the -setupSpaceSceneLayers selector.

This effect is created by using two different size star images that are added to the background layer using 4 different SKEmitterNode objects, and another node for floating space dust. It is important to note that child nodes will be rendered in the order that they are added to the parent node. The first child added will be at the bottom (bottom layer), and the last node added will be at the top (top layer). Lower layers can be obscured by higher layers. More on Building Your Scene.

Backgrounda

In the above screenshot, space appears to have depth—some stars look farther away than others with the space dust appearing to be closer than the stars. Since the stars are managed by the emitters, they will be removed as they fall off the bottom of the screen. However, the space dust must be moved. Rather than creating a new space dust image each time it falls off the screen, two SKSpriteNodes are created and rotated like a tread on a tank—they form one continuous loop. The space dust is setup in -setupSpaceSceneLayers and moved with each rendered frame when the -update selector makes a call to -moveSpaceDust.

The SKEmitterNode automatically creates and renders the stars for the background. The particles are created and owned by Sprite Kit and cannot be accessed directly—you cannot assign physics bodies to each emitted node, for example—more on this later.

Emitters can be coded as shown in -setupSpaceSceneLayers, but they can also be created using the Particle Emitter Editor. The effects settings are stored in .sks files.

Asteroids and the Particle Emitter Editor

The Particle Emitter Editor is built into Xcode as an inspector in the right pane. It renders the emitter live in an SKView so you can make changes to the emitter and see the results live. To add a Sprite Kit Particle Emitter File to your project, Click File > New > File > Resource > "SpriteKit Particle File" and click Next. You will be given an option to create the file from one of 8 templates. A description of these can be found here along with a walkthrough on how to add the file.

Here is more information on manipulating the emitter settings. This will tell you what each of these settings mean, but I've found that the best way to learn is hands on. It will be very easy for you to lose track of time tweaking these settings—but fun.

Open the MonkeySpaceDustTrails.sks file by expanding the Assets > Particles groups. This does make use of the default spark.png file which is automatically added to the project. This is used to create the trails that appear to be emitted from the monkey as he is flying. They also get paused when he dies.

MonkeySpaceDustTrails_sks

The monkey's space dust trails are configured in the -configureEmitters selector of the Monkey.m file. Notice that the same .sks file is used for both left and right trails. The positions are offset to give the dual dust effect.

bodyEmitterLeft.position = CGPointMake(-40, 10);
bodyEmitterLeft.name = @"spaceDustTrails";
bodyEmitterLeft.zPosition = 0;
[self addChild:bodyEmitterLeft];
 
bodyEmitterRight.position = CGPointMake(40, 10);
bodyEmitterRight.name = @"spaceDustTrails";
bodyEmitterRight.xAcceleration = 65;
bodyEmitterRight.zPosition = 0;
[self addChild:bodyEmitterRight];

In the same file, look at the -toggleEmitter selector. The emitters here are paused when the monkey dies.

However, you do not need to use the default image to generate your particle. Look at Asteroid_1.sks. This uses an asteroid that will fall down the screen. A slight tint is added to the image. Play around with this file and see how your changes affect the creation of these nodes. Compare these settings to those of the monkey's space dust trails.

Asteroid_1_sks

It's pretty cool that falling asteroids can be generated by the particle system, but we cannot use this method of monkey destruction in this game. It cannot be used because the asteroid needs to collide with the monkey. Collisions between a generated asteroid and the monkey cannot be detected because physics bodies cannot be set on the generated particle nodes. Instead we need to use SKActions.

Emitters and Actions

In this game, SKActions are used to perform the monkey's fly animation, play the death animation, spawn bananas, hearts, asteroids, and play sound effects. They make it incredibly easy to do things that would normally require you to write a lot of code if you were to roll these actions yourself. Actions are automatically removed upon completion—this is a really nice feature as it keeps you focused on writing your game and not writing plumbing.

SKActions are actions that are set on and performed by a node in the scene. They can be used to change the position, rotation, and scale of the node—to name just a few. They can be grouped (run in parallel), sequenced (run serially), and groups can be sequenced and sequences can be grouped—some can even be reversed. When grouped, the group's duration will be equal to the longest running action in the group. They can also be repeated for a certain number of times or forever.

Open MyScene.m and look at the -setupGameObjects selector. The asteroid actions are configured to repeat forever. After waiting for the specified time, the -spawnAsteroidAction will make a selector call to -spawnAsteroid. In -spawnAsteroid, the asteroid's position is randomly set across the x-axis and will move the asteroid down the screen over a period of 8 seconds.

SKAction spawnAsteroidAction = [SKAction performSelector:@selector(spawnAsteroid) onTarget:self];
SKAction waitAction1 = [SKAction waitForDuration:5 withRange:10];
[self runAction:[SKAction repeatActionForever:[SKAction sequence:@[spawnAsteroidAction, waitAction1]]]];

This is great—asteroids will fall from a random x-axis position through the bottom of the screen. They will not stop even when hitting the monkey, however the monkey will move and lose life when he is hit by the asteroid. This is accomplished by setting the physicsBody properties.

Physics Bodies and Collisions

Physics simulations are applied by adding an SKPhysicsBody to a node. When the frame is processed by the scene, the calculations are performed on the physics bodies attached to the nodes in the scene. Physics friction, gravity, and collisions with other physics bodies are some things that can be set for the node. Custom forces can also be applied to a body. This is what gives the game some realistic reactions to applied forces such as in games like Angry Birds.

There are various shapes that can be used to apply physics to a scene. These shapes define the node's "personal space". When one node's shape intersects with another node's shape, -didBeginContact is called and physics may be applied. These shape types are:

  • Circles
  • Rectangles
  • EdgeLoopFromRect
  • Edge
  • Polygon
  • EdgeChain
  • EdgeLoopFromPath

To configure the collisions for this game, a mask is first defined that will be used for contacts and collisions. This is defined in GameObject.h and is used for detecting contacts or collisions for the monkey with asteroids, bananas, and health hearts:

typedef NS_OPTIONS(uint32_t, CollisionType) {
    CollisionTypeMonkey      =1 << 0,
    CollisionTypeAsteroid    =1 << 1,
    CollisionTypeBanana      =1 << 2,
    CollisionTypeHealth      =1 << 3,
};

Now open Monkey.h and look in -configureCollisionBody:

self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.frame.size];
self.physicsBody.affectedByGravity = NO;
self.physicsBody.categoryBitMask = CollisionTypeMonkey;
self.physicsBody.collisionBitMask = CollisionTypeAsteroid;
self.physicsBody.contactTestBitMask = CollisionTypeAsteroid;

Here the physicsBody is created using one of the several convenience methods. The body is assigned the CollisionTypeMonkey category, and it will react when it collides with an asteroid as the collisionBitMask is set to CollisionTypeAsteroid. When the monkey collides with an asteroid, the -didBeginContact selector will be called in MyScene.m. In this selector, both bodies involved in the contact will be notified:

- (void)didBeginContact:(SKPhysicsContact *)contact {
 
	if(_gameRunning) {
 
		SKNode *contactNode = contact.bodyA.node;
		if([contactNode isKindOfClass:[GameObject class]]) {
			[(GameObject *)contactNode collidedWith:contact.bodyB contact:contact];
		}
		contactNode = contact.bodyB.node;
		if([contactNode isKindOfClass:[GameObject class]]) {
			[(GameObject *)contactNode collidedWith:contact.bodyA contact:contact];
		}
	}
}

In the -collidedWith:contact: selector of Monkey.m, the health of the monkey is reduced. However, in Asteroid.m, this selector is not implemented. This is because the asteroid will not react to the impact. It's collisionBitMask property in the -configureCollisionBody selector has been set to 0. It will just continue to plow through the screen—moving the monkey out of the way. However, it will still notify that it did have contact with the monkey: self.physicsBody.contactTestBitMask = CollisionTypeMonkey;. The main difference between contactTestBitMask and collisionBitMask is that contactTestBitMask will only notify of the contact, but when setting the collisionBitMask appropriately, physics will be applied. If the collisionBitMask is set to 0 for both colliding nodes, they will just pass through each other.

Physics bodies can also be joined together using one of the following objects:

  • SKPhysicsJointFixed
  • SKPhysicsJointSliding
  • SKPhysicsJointSpring
  • SKPhysicsJointLimit
  • SKPhysicsJointPin

Texture Atlas and Animation

A texture atlas is a single image file made up of many smaller image files. Image locations within the atlas are mapped in an atlas plist. This helps to minimize disk IO, the memory footprint, and helps to reduce overall draw calls.

This capability is built into Xcode 5. A texture atlas can be created by doing the following:

  • Turn on atlas generation. When using the SpriteKit Project Template, the Enable Texture Atlas Generation build setting is automatically set to YES. In this case, it is not necessary to manually set this yourself.
  • Place all your related image files into a folder on disk called .atlas
  • Drag the folder into your Xcode project

The files in the .atlas will be processed and available for use right away. When compiled in the bundle, the folder will have a .atlasc extension.

When the project is built, the following occurs:

  • Textures are automatically combined
  • Hardware specific atlases are generated
  • Maximum atlas size of 2048 x 2048 is generated. Additional atlases will be automatically generated if necessary. A single plist file will be created.
    • Automatic image rotation for maximum packing
    • Transparent edges are trimmed
    • Extrude opaque images by 1 pixel - this prevents flashing images when rendering

Open up the bundle (show package contents) for this game (I like to use the Simulator Folders App to get there). You will see the images packed nice and tight along with a .plist file that has descriptive information for each of the packed images including the location of each image within the single image file:

atlasc

spacemonkey.1

All this helps to improve developer iteration time. Hundreds of megabytes can be saved just by utilizing texture atlases. Texture atlas references are tracked in memory; when the developer decides to load the same texture, the in-memory texture is used. Loading an image from a texture atlas is the nearly the same as loading a standalone image. The developer only needs to reference the image by name from the atlas object.

Take a look at -configureMonkeyAnimations in Monkey.m:

- (void)configureMonkeyAnimations {
 
	SKTextureAtlas *monkeyAtlas = [SKTextureAtlas atlasNamed:@"spacemonkey"];
 
	NSArray *flyTextures = @[[monkeyAtlas textureNamed:@"spacemonkey_fly_01"],
                          [monkeyAtlas textureNamed:@"spacemonkey_fly_02"]];
 
	NSArray *deathTextures = @[[monkeyAtlas textureNamed:@"spacemonkey_dead_01"],
                           [monkeyAtlas textureNamed:@"spacemonkey_dead_02"]];
 
	_monkeyFlyAnimation = [SKAction animateWithTextures:flyTextures timePerFrame:0.1];
	_monkeyDeathAnimation = [SKAction animateWithTextures:deathTextures timePerFrame:0.1];
}

The monkey animations are run by SKActions in -startFlyAnimation, and -startDeathAnimation.

Standalone images are also supported. It is easy to switch between using the standalone and texture atlas image files. Standalone files will have precedence over the texture atlas version if they both exist in the same bundle. This is beneficial if the artist who is creating the images wants to see how an image looks in the application—without having to regenerate the texture atlas every time.

Game Audio

No movie is complete without sound and neither is a game. In this game there are sound effects to alert the player when:

  • The monkey's health is reduced to 25% or below
  • When the monkey dies
  • An asteroid collides with the monkey
  • The monkey collects a banana or a heart

The Background music uses AVAudioPlayer and is configured in the -playMusic:filename selector of MyScene.m. This will loop the music continuously while this scene is being presented.

- (void)playMusic:(NSString *)filename
{
	NSError *error;
	NSURL *musicURL = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];
	_musicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:musicURL error:&error];
	_musicPlayer.numberOfLoops = -1;
	_musicPlayer.volume = 0.40f;
	[_musicPlayer prepareToPlay];
	[_musicPlayer play];
}

All other sounds are played by SKActions during the specific contact events or when the monkey's health drops too low. In -AnimateLowHealthWarning, alarm.wav is played:

SKAction *alertAction = [SKAction playSoundFileNamed:@"alarm.wav" waitForCompletion:YES];
[self runAction:alertAction];

In Monkey.m, the two sounds used are configured:

- (void)configureSounds {
	_deathSoundAction = [SKAction playSoundFileNamed:@"ghost.wav" waitForCompletion:NO];
	_hitSoundAction = [SKAction playSoundFileNamed:@"Error or failed.mp3" waitForCompletion:NO];
}

Sounds are also configured in Banana.m and PowerUpHeart.m. These actions can be played simply by running the action on the node.

A Template for your own game

To start your own game, it is recommended that you use Xcode 5's new project template called the SpriteKit Game Template and it is available for both iOS and OS X applications. It is a starting point for game development and includes the following:

  • A view of the type SKView
  • A Default SKScene displaying "Hello World"
  • Sprite Kit build setting for atlas generation set to YES
  • SpriteKit.framework is linked

iOS Template:

SpriteKitGameTemplateiOS

OS X Template:

SpriteKitGameTemplateOSX

Although it is not necessary to create a game using this template, it does make it easier to get started. An additional build target can be added to the project so that both iOS and OS X applications can be based on the same core code.

Creating a project with this template is the same basic process that you would follow for other iOS projects. After you create the new project, Build and Run, and tap on the screen and see what you get!

Conclusion

If you've made it this far and were able to follow along in the code of the demo game, you've noticed how easy it really is to get a game going with very little code—when compared to hand-rolling your own plumbing. There are many more node types, as well as other features of Sprite Kit that were not addressed in this article. I encourage you to read through Apple's documentation on Sprite Kit. Here are a few resources to help you further:

iOS7 and iOS Games by Tutorials Bundle (http://www.raywenderlich.com/store/ios-7-and-ios-games-by-tutorials-bundle) - This bundle has helped me get up to speed quickly on iOS 7 as well as Sprite Kit. In addition to these great books, Ray has some great tutorials as well.

WWDC 2013 Sessions 502, 503 (https://developer.apple.com/wwdc/videos/) - If you have access to these video sessions, you should watch them!

Sprite Kit Adventure Game (https://developer.apple...AdventureArchitecture/AdventureArchitecture.html) - Walk through this in conjunction with the WWDC videos referenced above.

Sprite Kit Programming Guide (https://developer.apple...Introduction/Introduction.html) - A great resource straight from Apple.

Xcode 5 Overview (https://developer.apple.com/technologies/tools/whats-new.html) - Well worth the time. You can also view the Xcode 5 WWDC 2013 session videos: 400, 401, 403, and 407. There are a few other Xcode 5 related videos as well.

Audio files referenced in this game are all licensed under the Creative Commons Attribution license and can be found here:

Background Music:

http://opengameart.org/content/the-return-of-martha-stewart

Sound Effects:

Game Art:

  • The monkey game art: http://www.vickiwenderlich.com/2013/05/free-game-art-space-monkey/
  • Health hearts: Created in Photoshop CC by me