Getting Started with SpriteKit

By Jorge Jordán
    What do you get with a Packt Subscription?

  • Instant access to this title and 7,500+ eBooks & Videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
About this book

SpriteKit is Apple’s game engine to develop native iOS games. Strongly boosted by the Apple Inc., Cupertino, it has increased in popularity since its first release. This book shows you the solutions provided by SpriteKit to help you create any 2D game you can imagine and apply them to create animations that will highlight your existing apps.

This book will give you the knowledge you need to apply SpriteKit to your existing apps or create your own games from scratch.

Throughout the book, you will develop a complete game. The beautiful designs implemented in the game in this book will easily lead you to learn the basis of 2D game development, including creating and moving sprites, and adding them to a game scene. You will also discover how to apply advanced techniques such as collision detection, action execution, playing music, or running animations to give a more professional aspect to the game. You will finish your first game by learning how to add a main menu and a tutorial, as well as saving and loading data from and to the player’s device.

Finally, you will find out how to apply some mobile games techniques such as accelerometer use or touch detection.

Publication date:
January 2016
Publisher
Packt
Pages
226
ISBN
9781785887338

 

Chapter 1. The First Step toward SpriteKit

In 2013, Apple released SpriteKit, its 2D game engine, in order to compete with all the two-dimensional frameworks that were existing in the market and retain their developers in its own technological ecosystem. Since then, SpriteKit has become one of the most powerful tools that are used to develop 2D games for iOS. In this chapter, we will have a look at the elements that are a part of game development, and we'll study how to use them with SpriteKit.

In this chapter, we will explore the following topics:

  • Understanding game engines

  • Creating and understanding a new SpriteKit project

  • Understanding the SKNode class

  • Studying the SKScene class

  • How to add a sprite and background to a scene

 

Game engines


I remember the time when I developed my first game using the BASIC programming language on my old Amstrad CPC. In those times, every game was hardware-specific, which means that you had to take into account every machine's low-level characteristics.

A game engine is a collection of software instructions that eases the process of game development by providing abstraction between the hardware and software layers. This way, you don't need to waste your efforts when performing important tasks, such as handling user inputs, playing sound and video, rendering images, or simulating physics.

As mentioned previously, SpriteKit is an engine developed by Apple to create games, and it's one of the most powerful tools that are used to build native 2D games for both iOS and Max OS X.

 

Creating a new SpriteKit project


The tool needed to develop SpriteKit games is Apple's Integrated Development Environment (IDE) Xcode, which can be found free of charge on Apple's App Store at https://itunes.apple.com/en/app/xcode/id497799835?l=en&mt=12. We are going to work with version 7.0, which is the latest at the time of writing the book, and iOS 9.

Creating a new project with Xcode is a straightforward task, but I would like to take advantage of it to help you understand how a default Xcode project looks like.

To create a new SpriteKit project, we need to open Xcode and then navigate to File | New | Project…. On the left-hand side, you will need to click on the iOS | Application Template; you will see what's shown in the following screenshot:

At this point, you will need to perform the following steps:

  1. Select the Game template and click on Next.

  2. Set InsideTheHat as the template name. Ensure that the Swift option is chosen in the Language menu. Select SpriteKit as the Game Technology, and Universal is chosen on the Device Family menu. Leave the default configuration (checked) for Include Unit Tests and Include UI Tests before clicking on the Next button.

  3. Choose a place to save your project in and click on Create.

The first thing that you should look at is the left section, which is called Project Navigator and contains the folders, and files that will be a part of our game; this section is shown in the following screenshot:

The Project Navigator shows a tree of files and folders that represents a hierarchy that doesn't correspond with how these files are located on your hard drive. It means that, if you move some file, on the Project Navigator, it won't affect their position in Finder. However, if you move some file in Finder, the reference kept by Xcode will be broken and it won't be able to make use of it.

The yellow containers on the project navigator are called Groups in Xcode, and they are equivalent to folders in a filesystem; as folders, the groups' responsibility is to organize all the files (images, classes, and so on) of an Xcode project.

One of the most important groups is the one called Project. It contains classes and resource files. As you can see in the preceding screenshot, a default project contains three Swift classes, namely AppDelegate, GameScene, and GameViewController, that will contain the core of the game.

Tip

Swift is Apple's programming language that was created by Apple for iOS, Mac OS X, watchOS, and Linux development. It was first released in June 2014.

There are a couple of storyboard files, namely Main and LaunchScreen, that are responsible for showing the game screen and launch image respectively. You will also see an image asset file, which will contain the images used on the game, and a plist file with the project configuration.

In addition to this, there is a file called GameScene.sks that should look pretty new to you. This file is used to build the screen in a static way, which is similar to a storyboard, that is created with Interface Builder.

Getting back to Xcode, there is another important section in the Project; it is the window at the center, which shows the configuration of the Project, as shown in the following screenshot:

In this panel, you will see three different sections, namely Identity, Deployment Info, and App Icons and Launch Images. Let's take a look at the second one first, where you can configure the following:

  • Deployment Target: This is the iOS version that is used to run the game. By default, 9.0 is chosen.

  • Devices: This is the family of devices (iPhone, iPad, or both), on which we will be able to run the game. In our case, it shows Universal, which is the property that we specified when creating the project.

  • Main Interface: This is the main storyboard file that is used to run the project.

  • Device Orientation: This determines the different orientations that our game will be able to support. As we are going to develop a vertical game, unselect the Landscape Left and Landscape Right checkboxes, leaving just Portrait checked off.

  • Status Bar Style: This helps us determine how we want the status bar to be shown.

The third section contains the following configuration:

  • App Icons Source: This comprises the asset catalog for app icons.

  • Launch Images Source: This comprises the asset catalog for the launch image.

  • Launch Screen File: This determines the screen shown while loading the game. If you want to avoid the launch screen that shows the copyright (such as the one that you can use to show your company's logo), choose Main.Storyboard in the drop-down menu.

After performing the aforementioned modifications, the project's properties will meet our requirements. So, let's run the project.

 

Running the project for first time


To execute the project, you just need to click on the Run icon at the top left of the Xcode screen, and it will run the project on an iPhone 6 on the iOS Simulator; the result is shown in the following screenshot:

Well, with a little effort, we have created our first Hello, World project using SpriteKit. Now, it's time to look at the code and understand why the preceding screenshot is the result of these files.

Note

From now on, we are going to assume that we are running the game on the iOS Simulator.

How the default project looks like

In this section, you are not supposed to understand everything. The aim of this section is to understand the responsibility of each class in a default SpriteKit project.

The entry point of our game is the AppDelegate class, which is the same as that of all iOS applications. Let's take a look at its content:

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }

Tip

Downloading the example code.

You can download the example code files from your account at http://www.packtpub.com for all the Packt Publishing books you have purchased. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

I've just pasted the top block of the file because it is the important one. As you can see, we imported the UIKit framework that will provide the window and the view architecture needed to build an application. It also provides our project with the event-handling infrastructure that is needed to respond to user input and the app model needed to drive the main run loop and interact with the system.

The next line contains an odd-looking instruction, which is @UIApplicationMain. This tells Xcode which is the main file of the project.

Then, you will see that the AppDelegate class inherits from UIResponder and UIApplicationDelegate, which is what happens with all iOS applications. We declared a UIWindow optional variable to avoid runtime errors in case of nil content.

Note

As per Apple's documentation ( https://developer.apple.com/library/mac/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html), optional chaining is a Swift process that is used to call properties and methods on an optional that might currently be nil. If the optional contains a value, the call succeeds, and if the optional is nil, the call returns nil.

Finally, you will see that the only method implemented is application(application:, launchOptions:). This is the point where we can apply some instructions that we want the game to execute as soon as it's launched.

There is nothing more to remark on this class. So let's take a look at the class that will be called just after AppDelegate: GameViewController.

To understand why this class is called, as soon as the main screen is launched, we need to keep in mind that the project is configured to show Main.storyboard as the main interface. In the Project Explorer, select the File and look at the Utilities panel on the right-hand side of screen, and choose the Identity Inspector to have a look at its configuration, as shown in the following screenshot:

This means that the interface is linked to the GameViewController class. It's time to open the class and discover what it contains:

import UIKit
import SpriteKit

class GameViewController: UIViewController {
override func viewDidLoad() {
        super.viewDidLoad()

        if let scene = GameScene(fileNamed:"GameScene") {
            // Configure the view.
            let skView = self.view as! SKView
            skView.showsFPS = true
            skView.showsNodeCount = true
            
            /* Sprite Kit applies additional optimizations to improve   rendering performance */
            skView.ignoresSiblingOrder = true
            
            /* Set the scale mode to scale to fit the window */
            scene.scaleMode = .AspectFill
            
            skView.presentScene(scene)
        }
    }

As you can see at the top of the file, the view controller is a subclass of the UIViewController class, which is commonly seen in many iOS applications. However, the difference is that here, we imported the SpriteKit framework (apart from UIKit), to provide game characteristics to the project.

This class overrides the viewDidLoad method, where we create a scene by using a file called GameScene. This file corresponds to GameScene.sks. If it succeeds, we create a view (an instance of the SKView class), setting the showsFPS and showsNodeCount attributes to True.

This is the reason why we can see these labels at the bottom right of the game's screen; they show the amount of draw calls (node count), and frame rate respectively.

Note

The frame rate value measures how smooth our game will be. In iOS, the maximum frame rate is 60 Hz.

The number of draw calls and the frame rate are values that you need to take care of, as they will let us know if our game will run smoothly.

We will have a look at the last view's configuration (ignoresSiblingOrder), and the scaleMode property later in the chapter, as we just want to have an overview the project. Once the view is configured, we can load the scene by calling the presentScene method.

Next, in the file, you will see four more methods. Take a look at the following two methods:

override func shouldAutorotate() -> Bool {
        return true
   }

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
        if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
            return .AllButUpsideDown
        } else {
            return .All
        }
   }

This code means that the user can rotate the device, and the screen will adapt itself automatically to the new orientation with one restriction, due to the AllButUpsideDown property: the game's screen won't rotate when we hold the iPhone or iPod devices upside down.

Have a look at the following method:

override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Release any cached data, images, etc that aren't in use.
   }

This method should look familiar to you if you have developed an iOS application previously. It's raised by the system when the amount of available memory is low. It allows us to release some memory to avoid an application crash.

Finally, we have the following method that has to do with the way the game is shown:

override func prefersStatusBarHidden() -> Bool {
        return true
   }

This method keeps the status bar hidden, as our application is a game and we want to use the full screen to show it.

We have previously seen that this class creates a scene by calling the constructor method in the GameScene class. Therefore, it's time to open the file:

 import SpriteKit

 class GameScene: SKScene {
    override func didMoveToView(view: SKView) {
        /* Setup your scene here */
        let myLabel = SKLabelNode(fontNamed:"Chalkduster")
        myLabel.text = "Hello, World!";
        myLabel.fontSize = 65;
        myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
        
        self.addChild(myLabel)
    }

As you can see, this class also imports the SpriteKit framework, but the most important thing about this is the class that it is inheriting SKScene. We will study it in detail further in this chapter, but for now, you need to understand that an instance of SKScene or its subclass is the object that will represent a scene of content in a SpriteKit game.

The didMoveToView method means that its code will be executed as soon as the scene is presented by a view. This is the perfect place to initialize a scene and, as we can see, in the default project, we are creating a new label using a font called Chalkduster and configuring some of its properties to set the size, text, and desired position. Adding the label to the scene is as easy as executing the addChild method.

The next method in the class looks like this:

 override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
       /* Called when a touch begins */
        
        for touch in touches {
            let location = touch.locationInNode(self)
            
            let sprite = SKSpriteNode(imageNamed:"Spaceship")
            
            sprite.xScale = 0.5
            sprite.yScale = 0.5
            sprite.position = location
            
            let action = SKAction.rotateByAngle(CGFloat(M_PI), duration:1)
            
            sprite.runAction(SKAction.repeatActionForever(action))
            
            self.addChild(sprite)
        }
    }

This method is called when the user touches somewhere on the screen. It is also one of the methods that we can override to handle touches. There are three more methods, namely touchesMoved, touchesEnded, and touchesCancelled, which will be covered in detail in Chapter 2, What Makes a Game a Game? As soon as user touches on the screen and this whole process gets completed, it gets the location of the touch, creates an SKSpriteNode instance using the Spaceship texture, sets its size to half of the texture's original size, and places it on the touch position. You will find the image that is used to create the spaceship in the Assets.xcassets folder of the Project Navigator.

Then, it applies a rotation to the spaceship by creating an action method, and calling the rotateByAngle method, which accepts an angle value as an input parameter, and running this action on the spaceship. Finally, it adds the ship to the scene.

Note

The SKSpriteNode instance is one of the most used classes in SpriteKit game development as it provides a visual representation and a physical shape to the objects in view.

The last method looks like this:

 override func update(currentTime: CFTimeInterval) {
        /* Called before each frame is rendered */
    }

This is one of the most important methods when developing games with SpriteKit, as it is called just before each frame is rendered and it is the place where we can perform important operations and actions.

If you run the project again and touch anywhere on the screen, you will see something that is similar to what's shown in the following screenshot:

As expected, a spaceship has been created and it has begun to rotate in a counterclockwise direction. Another important thing to note at this point is the number of nodes, which has increased and corresponds to the draw of the scene, the text label, the spaceship, and the background.

Now that we had an overview of the initial project code, it's time to go deeper into some of the classes that we saw earlier. We have seen that the default project creates instances of SKScene, SKLabelNode, and SKSpriteNode, which are subclasses of SKNode, one of the most important classes of the SpriteKit framework. You will understand why if you keep reading.

 

The SKNode class


When developing a scene, we sometimes build what is called a scene hierarchy. This scene graph is a hierarchy of the nodes that are available on it.

We call them nodes because they inherit from the SKNode class. For more information, visit https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SKNode_Ref, which is the main SpriteKit class that renders visual elements.

The following diagram corresponds to the scene graph of the project. You can see that there is a parent SKScene node with two children that correspond to the SKSpriteNode and SKLabelNode that we have added to the game:

The SKNode class is a subclass of UIResponder. This means that all the SKNode instances and every subclass of SKNode will be able to handle touches and other kind of events such as motion events.

If you look at the SKNode class hierarchy in the following diagram, you will realize the importance of this class, as it is the parent of several useful classes:

The SKNode class properties

In this section, we are going to have a look at the most important properties available in the SKNode class in detail.

The position property

An important property of nodes is their position, as we are going to manipulate this several times during the game's development. This property corresponds to the position of the node in the parent's coordinate system. Therefore, we need to take it into account when adding new elements to a scene. Its default value is (0.0, 0.0).

The frame property

Another useful property is the frame of a node, which makes a reference to the rectangle defined by its texture (the visual content). This property size can be modified by applying a scaling factor on both the width and height by applying a value between 0 and 1 to the xScale and yScale attributes. The frame can also be rotated by modifying the zRotation property, which will apply a counterclockwise rotation if the value is greater than 0.

Note

As a node can be used to organize the content by storing other nodes, the scale and rotation modifiers will affect both the node and its descendants.

If we want to take into account a node's descendants when getting its frame, there is a function called calculateAccumulatedFrame() that retrieves the rectangle containing the content of the parent and children while taking into account the scale and rotation factors.

We can, for instance, determine whether this whole frame is intersected by another node's frame thanks to this method.

The zPosition property

This property determines the height of the node related to its parent. Its value is 0.0 by default, but we can set positive or negative values so that, the bigger the zPosition value, the closer the node will be to the user. This way, we will have full control over how the children are rendered.

The hidden property

Sometimes, we will need to keep a node invisible while it is in a scene. We can do this by setting the hidden property to true. It only affects the way the node and its descendants are rendered, as they will still be able to perform actions and collide with other nodes in the scene.

An alpha property

A property that provides a similar effect is the alpha property of the node. It applies a modifier between 0.0 and 1.0 to the alpha component of each pixel and allows us to make the node transparent.

The children node

This read-only array of the AnyObject type contains all the node children in an SKNode object.

name

If a scene contains several nodes, we may need to identify them in order to handle collisions. In such cases, it is a good approach to provide a value to each node's name property. We can use this property to give the same name to a group of nodes in order to differentiate them from the player's node and make collision detection tasks easy.

If we want to find a node by its unique name, we can make use of the childNodeWithName method. On the other hand, if we have used a name to identify a collection of nodes, we can call enumerateChildNodesWithName:usingBlock, which will search a node's children and execute a block of code once for each child that is found.

userInteractionEnabled

There is another property that is commonly used, userInteractionEnabled. This determines whether a node can receive touch events. If its value is false, the node won't react to user input.

Using SKNode to organize a scene

We have seen previously that an SKNode instance can be used to contain other nodes in order to organize the scene content. The following are a few examples:

  • You may want to group several nodes that need to be treated as a unique object to represent an army of alien ships, and you don't want any of the ships to be the root. Grouping them as children of a node will allow you to move them, while always keeping the line-up.

  • In a game, it is common to have a background, several characters, objects to collide, texts, and many more elements. You can create different layers to separate each of these different kind of elements by creating basic nodes and inserting them in the desired order into the scene.

In the preceding screenshot, you can see how we used three different layers, one for the background, another one for the ninja character, and the last one for the score.

By following the afore mentioned approaches, you will be able to add or remove entire groups of objects by deleting a single node. This will make the scene management more efficient. You can also configure the properties of several nodes by applying the configuration to the root node. You can even take advantage of it when running actions or handling physics contacts.

 

SKScene


The SKScene class is a subclass of SKNode that has some specific properties and methods to handle the way content is drawn in an SKView object (the screen).

The game loop

Each node provides content that will be animated and rendered by the scene in a process called game loop. It looks like the following screenshot that was taken from https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG:

According to the preceding screenshot, each frame in a SpriteKit game is calculated in the following order:

  1. Firstly, the scene calls the update method. Here, we can specify the code that we want to execute just before the scene actions are evaluated.

  2. Then, the scene executes the actions on its children nodes.

  3. Once the actions have been executed, the scene triggers its didEvaluateActions method. We should include in this method the code that we want to execute as soon as the actions have been evaluated.

  4. Now, it's time for the physics to be evaluated. SpriteKit provides an easy way to simulate physics in a node such as gravity, collisions, and friction, but we are not going to cover it in this book. You just need to know that there is a step in the game loop where the scene executes every physics simulation on the physic bodies in the scene.

  5. After the physics is simulated, the scene triggers its didSimulatePhysics method. We should include in this method the code that we want to execute as soon as the physics is simulated.

  6. Then, the scene applies the constraints associated to its children nodes. These constraints are an array of instances of the SKConstraint class, which basically are restrictions applied to a node that can be related to another node in the scene. For example, we can create constraints to set a node's zRotation method so that it always points at another node or position in the scene, or keeps a node inside a specified rectangle or within a specified distance of another node.

  7. Once the constraints have been applied, the scene triggers its didApplyConstraints method, which we should take advantage of to include the code that we want to execute as soon as the physics has been simulated.

  8. Then, the scene calls the didFinishUpdate method, which is the last method that is called before the scene is rendered.

  9. Finally, the scene renders all of its children nodes and updates the view.

    Tip

    You don't need to call the aforementioned methods directly, because they are called once per frame as long as the scene is presented in the view and it is not paused.

The SKScene properties

In this section, we are going to study in detail the most important properties that are available in the SKScene class.

scaleMode

An SKScene instance provides some properties that can become interesting when creating a scene. For example, the scaleMode property allows us to specify the way a scene is mapped to the view that presents it, which can be one of the following four values defined in the SKSceneScaleMode enumeration:

  • Fill: Each axis of the scene (x and y) is scaled independently. This way, each axis in the scene exactly maps to the length of the same axis in the view.

  • AspectFill: This is the scale mode that is used by the default project. In this case, we will choose a scaling factor, that will be the larger scaling factor between the two dimensions, and each axis of the scene will be scaled by the same factor. This way, the entire area of the view will be filled, but it's possible that some parts of the scene may be cropped.

  • AspectFit: In this case, we will choose a scaling factor that will be the smaller scaling factor between the two dimensions, and each axis of the scene will be scaled by the same factor. This way, the entire scene will be visible, but letterboxing may be required in the view.

  • ResizeFill: This value will automatically resize the scene so that its dimensions match those of the view.

anchorPoint

This property makes reference to the origin point of the scene. By default, its value is (0,0), which means that the scene will be pinned to the bottom left point of the view, as shown in the following screenshot. When we add the first sprite to the scene, we'll see how important it is:

size

This property specifies the part of the scene's coordinate space that is visible in the view. When this property is changed, the didChangeSize method is triggered. An important aspect that needs to be highlighted is that this property will also be modified if we set the ResizeFill value in the scaleMode property.

backgroundColor

If we are not planning to add a background image to our game, it's a good idea to set a nice color to the scene. We can perform this change by applying an RGBA (Red, Blue, Green, and Alpha) color to this property, which is a gray color (0.15, 0.15, 0.15,1.0) by default.

 

Your first game – InsideTheHat


In this game, we will take control of a little rabbit that is trying to escape from the top hat of a magician, where it is trapped. To achieve its objective, our main character will need to run through magic doors until it gets the ace of diamonds that will let the rabbit escape.

In this chapter, we are going to see how to create the main character's sprite and add it to the scene. On the other hand, we will learn how to set a background for the game. In the preceding pages, we have seen a lot of properties and methods that will help us reach our current goal.

Let's start by cleaning off the unnecessary files and content in the project. We are going to generate the screens programmatically so that you can delete the sks file:

  1. Right-click on the GameScene.sks file.

  2. Choose Delete.

  3. Ensure that you click on the Move to Trash button.

Next, adapt the GameViewController class in order to avoid initializing the scene from the file that we have just removed. Replace the viewDidLoad method from this class with the following block of code:

override func viewDidLoad() {
        super.viewDidLoad()

        let scene = GameScene(size: view.bounds.size)
        // Configure the view.
        let skView = self.view as! SKView
        skView.showsFPS = true
        skView.showsNodeCount = true
        
        /* Sprite Kit applies additional optimizations to improve rendering performance */
        skView.ignoresSiblingOrder = true
        
        /* Set the scale mode to scale to fit the window */
        scene.scaleMode = .AspectFill
        
        skView.presentScene(scene)
   }

We have just modified the old line, which looks like this:

if let scene = GameScene(fileNamed:"GameScene") {

We replaced the preceding line of code with the following code:

let scene = GameScene(size: view.bounds.size)

This way, we initialized the scene using the init(size:) method of the SKScene class, to which we pass a size value as an input parameter in the form of view.bounds.size. We are using the bounds property of the SKView class, which corresponds to a rectangle that occupies the whole size of the screen.

Now, it's time to clean the GameScene class. Therefore, open it and replace the didMoveToView method with the following piece of code:

 override func didMoveToView(view: SKView) {
    }

Replace the touchesBegan method with the following code:

 override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    }

The project is now ready to be updated with our brand-new code, but you can run it just to ensure that we haven't broken anything.

 

Our first SKSpriteNode class


The SKSpriteNode class https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SKSpriteNode_Ref is the one that we are going to use in order to load the sprites that will be a part of our game.

The SKSpriteNode class is a subclass of SKNode, and it's used to represent visual elements called sprites on the screen by using images. As you are going to need an image to create a sprite (an instance of SKSpriteNode class), perform the following steps to add it to the project:

  1. Unzip the 7338_01_Resources.zip file in the desired location.

  2. In Xcode, right-click on the InsideTheHat group on the Navigator tab, select New Group, and call it Art.

  3. Right-click on the Art group and select Add Files to InsideTheHat….

  4. A dialog box will open, where you need to select the rabbit.png image in the 7338_01_Resources folder that you just unzipped.

  5. Ensure that Copy items if needed is selected and click on Add.

Now that the image has been added, we will need a variable to manage the main character. Therefore, on GameScene.swift, add the following line just after class GameScene:SKScene {:

   private var rabbit: SKSpriteNode!

Note that we have declared the variable as a var because its value will change throughout the game's life.

Now that the sprite variable has been declared, modify the didMoveToView method by:

 override func didMoveToView(view: SKView) {
        self.initializeMainCharacter()
    }
    
    func initializeMainCharacter() {
        // Creating the rabbit sprite using an image file and adding it to the scene
  rabbit = SKSpriteNode(imageNamed: "rabbit")
        
        addChild(rabbit)
    }

As soon as the scene is loaded, we call a new method named self.initializeMainCharacter that we created just to keep the code as clean as we can. We will use the self object because we are referencing a method in the current class. If you look at the method, you will see that it initializes the sprite with the init(imageNamed:) method, which takes the image that we have just added to the project to provide the sprite's visual content.

Tip

Note that you don't need to specify the extension of the filename, as it will load a .png, .jpg, .jpeg, .tiff, .tif, .gif, .bmp, .BMPf, .ico, .cur, or .xbm file.

Thanks to this init method, the sprite's size property (and its frame) is automatically set to the dimensions of the image and the color to white (1.0, 1.0, 1.0).

Once the sprite has been initialized, we add it to the scene by using the addChild method, which adds a new child to the specified container (GameScene in this case), and this is how we add new nodes to the scene.

If you run the game now, you will see something similar to what's shown in the following screenshot:

The sprite has been placed at the bottom left corner of the screen (the (0,0) coordinate), which corresponds to the scene's anchor point. You may be wondering why the rabbit is not fully visible.

The answer is that the default value of the anchorPoint on an SKSpriteNode is the center of the texture at (0.5, 0.5), while the anchorPoint of the scene is at (0,0). As soon as the sprite is added to the scene, their anchor points get aligned.

For our game, we want the rabbit to be placed at the center of the screen and near at the bottom of the screen. Therefore, add the following lines of code to initializeMainCharacter just before addChild(rabbit):

 // Positioning the rabbit centered
    rabbit.position = CGPoint(x:(view!.bounds.size.width/2), y: rabbit.size.height)

With the preceding line of code, we created a CGPoint class, which is a commonly used class that is utilized to represent a point in a two-dimensional coordinate system that accepts a CGFloat value for both the x and y axes. At the bottom-center of the screen, we are setting this point as the position of the rabbit.

Also note how we are getting the center of the screen's width. We get the width property from the size method of the bounds property of the current view (passed as an input argument when the scene is loaded), which is the rectangle that contains all the visual elements. As we want our sprite to be centered on the x axis, we just need to divide it by 2, and we have the desired value.

Tip

As there are several devices that support iOS and each of them has its own specific resolution and screen sizes, it's very important to always work with relative positions. This way, you don't need to worry about an element's position.

If you look at the code, you will realize that we are setting the sprite's position before adding it to the scene, but you can place it just after the addChild method, and the result won't vary. Now, if you run the game, the rabbit will be placed in the correct position, as shown in the following screenshot:

At this moment, there is nothing that represents that the rabbit is trying to escape from somewhere. Therefore, we need to add context to the game, which is the same as adding a background.

 

Adding a background


We need to create a road for our rabbit to run on and also for it to find the exit of the top hat. In this case, we will follow almost the same steps than we did to add the rabbit, but with a few differences.

Add the background image in a way that is similar to how we added the rabbit's image:

  1. On the Project Navigator, select the Art group.

  2. Right-click and select Add Files to "InsideTheHat"….

  3. Look for the background.png file in the 7338_01_Resources folder that you unzipped. Select it and click on Add.

Then, add the following lines to GameScene before the addChild(rabbit) line:

  // Creating and adding the background to the scene
        let background = SKSpriteNode(imageNamed: «background»)
        background.anchorPoint = .zero
        addChild(background)

You already know the first line; we are creating a sprite using the background image that we have just added to the project. Then, we set its anchorPoint to .zero, which is a shortcut for CGPoint(x:0, y:0). The image covers the whole screen, and finally we add the background to the scene.

Run the game. Now, the rabbit should appear standing on a lonely road, as shown in the following screenshot:

You may be wondering why the rabbit sometimes appears and sometimes it doesn't. The reason is that it is behind the background, even though it has been added after the background image.

The reason for this behavior lies in the skView.ignoresSiblingOrder = true line in the GameViewController class.

The ignoresSiblingOrder property indicates whether the relationship between the parent nodes and children affects the order of the nodes in the scene. By default, its value is false. This means that SpriteKit will render the children in the same order they appear in the children array, one node at a time.

Setting this property to true will not take into account the position of the nodes in the tree, but their zPosition property groups all the nodes at the same zPosition property in a single draw. Therefore, the reason behind setting the ignoresSiblingOrder property to true is the fact that it will improve the rendering performance.

In the game, the property has been set to true and the nodes have no zPosition specified (0.0 by default). This will render all the children on the same time in an arbitrary way. That's why, the rabbit may sometimes be visible and sometime not.

As we want the game to be very efficient, we will keep the ignoresSiblingOrder property as is. So, we will need to give the zPosition value to some nodes. Open GameScene and add the following line just before addChild(background):

background.zPosition = -1

This way, we set the background behind the default zPosition value so that the rest of the nodes that we will add will always be visible. Let's run the game again and check whether the rabbit is now visible. The output is shown in the following screenshot:

 

Working with screen resolutions


As mentioned previously, iOS games can be executed on devices with different resolutions and screen sizes. This is the reason why it's important to keep in mind the following table, which shows the different families of resolutions and their required file names:

 

iPhone 6 Plus

iPhone 6

iPhone 4s, iPhone 5

iPad Retina

iPad

Devices

iPhone 6 Plus, iPhone 6s Plus

iPhone 6, 6s

iPhone 4s

iPhone 5, 5C, 5S

iPod Touch 5G

iPad Air, Air 2, iPad mini Retina

iPad, iPad 2, iPad mini

Resolution

1242 x 2208

750 x 1334

640 x 960

640 x 1136

1536 x 2048

768 x 1024

File name

file@3x.png

file@2x~iphone.png

file@2x.png

file@2x~ipad.png

file@1x.png

Note that we are showing the devices supported by iOS 9, which is the version that we are using for development purposes.

The above table corresponds to the five resolution families that are available at the time of writing this chapter. In the table, you will see the different devices of each family, their resolutions, and the names that you will need to specify for each of them.

The filenames are composed of a prefix (the filename) and a suffix that can be @3x, @2x~iphone, @2x, @2x~ipad, or @1x (in this case, the suffix can be omitted), depending on the devices that you want the game to be available on.

Providing the needed files will not only result in a better resolution, but also will avoid the programmatic upscaling or downscaling of the image, thus improving the game's performance. Upscaling an image will result in smudgy-looking images but, on the other hand, downscaling images will allow you to reuse a high-resolution image for lower resolution devices. However, this approach is not recommended due to the waste of memory that a non-retina display could lead to.

From now on, when we add a new image to the game, we will need to include the corresponding @2x, @2x~iphone, @2x~ipad and @3x files, if available. Let's add the required images by performing the following steps:

  1. In the project navigator, right-click on Art and select Add Files to "InsideTheHat"….

  2. You'll find rabbit@3x.png, rabbit@2x~ipad.png, rabbit@2x.png, background@3x.png, background@2x~ipad.png, and background@2x.png in the 7338_01_Resources folder. Select these four files and click on Add.

You can now run the game on other devices and check whether the resolution is maintained in all of them.

Another way of including new image files in a project is by taking advantage of the assets catalog that we mentioned at the beginning of the chapter. If you take a look at this folder, you will see something that is similar to the following screenshot:

Here, you can create a New Image Set by clicking on the + button and filling the 1x, 2x, and 3x slots with the corresponding images.

 

Summary


We started the chapter by looking at a default SpriteKit project, creating a new project, and learning how it is configured and what files it consists of.

You learned what a node is and how to create one with the SKNode class, which is the parent of several important classes that take part in a game. Also, we showed the structure of a scene graph, with a parent SKScene node and several children.

I explained some of the key properties and methods of SKNode and SKScene that will take part in the development of our game. You also had a look at the different steps a game loop requires to render all the contents on the screen.

Then, you learned how to create a scene and add a sprite and a background properly, taking into account their anchor point and their zposition value to ensure that the background lies behind the rest of nodes.

In the last section of this chapter, we explored the characteristics that we need to keep in mind when developing a game for both iPhone and iPad devices, such as screen resolutions and image filenames.

Now that we know how to create a project and load sprites efficiently, let's take a step forward in order to make them interactive and the game playable.

About the Author
  • Jorge Jordán

    Jorge Jordán is an iOS indie developer who's passionate about how things work since his childhood. This is the reason why he graduated in computer science and became a Java developer. After buying his first iPhone, he became deeply interested in its technology and spent his spare time learning how to develop apps for Apple's smartphones.

    Over time, he founded www.insaneplatypusgames.com, where he tries to make his dreams and games come true. He is also a member of the tutorial team at www.raywenderlich.com.

    Also, he has worked on a book titled Cocos2d Game Development Blueprints, Packt Publishing.

    In his free time, he loves to play video games, play bass guitar, and watch TV series.

    You can follow him on Twitter; his Twitter handle is @jjordanarenas.

    Browse publications by this author
Latest Reviews (1 reviews total)
This book has been easy to use and understand without being too slow and dull. I have enjoyed working with it, and it has helped me get a better handle on SpriteKit.
Getting Started with SpriteKit
Unlock this book and the full library FREE for 7 days
Start now