An iOS 8 Swift Sprite Kit Level Editor Game Tutorial

From Techotopia
Revision as of 18:22, 23 December 2015 by Neil (Talk | contribs) (About the Sprite Kit Demo Game)

Jump to: navigation, search
PreviousTable of ContentsNext
An Introduction to iOS 8 Sprite Kit Game ProgrammingA Swift iOS 8 Sprite Kit Collision Handling Tutorial


<google>BUY_IOS8</google>


In this chapter of iOS 8 App Development Essentials, many of the Sprite Kit Framework features outlined in the previous chapter will be used to create a game based application. In particular, this tutorial will demonstrate practical use of scenes, textures, sprites, labels and actions. In addition, the application created in this chapter will also make use of physics bodies to demonstrate the use of collisions and simulated gravity.

This tutorial will also demonstrate the use of the Sprite Kit Level Editor combined with Swift code to create a Sprite Kit based game.


Contents


About the Sprite Kit Demo Game

The game created in this chapter consists of a single animated character that shoots arrows across the scene when the screen is tapped. For the duration of the game, balls fall from the top of the screen with the objective being to hit as many balls as possible with the arrows. When an arrow hits a ball, the texture of the arrow is changed to make it appear as though the tip of the arrow is embedded in the ball. A physical join is also established at the point of collision so that the arrow sprite remains stuck into the side of the ball as it continues to fall off the bottom of the screen.

The completed game will comprise the following two scenes:

  • GameScene – The scene which appears when the game is first launched. The scene will announce the name of the game and invite the user to touch the screen to begin the game. The game will then transition to the second scene.
  • ArcheryScene – The scene where the game play takes place. Within this scene the archer and ball sprites are animated and the physics behavior and collision detection implemented to make the game work.

In terms of sprite nodes, the game will include the following:

  • Welcome Node – An SKLabelNode instance that displays a message to the user on the Welcome Scene.
  • Archer Node – An SKSpriteNode instance to represent the archer game character. The animation frames that cause the archer to load and launch an arrow are provided via a sequence of image files contained within a texture atlas.
  • Arrow Node – An SKSpriteNode instance used to represent the arrows as they are shot by the archer character. This node is initially assigned a texture showing the entire arrow. This texture is then changed on collision with a ball to show the arrow embedded into the target. This node has associated with it a physics body so that collisions can be detected and to make sure it responds to gravity.
  • Ball Node – An SKSpriteNode used to represent the balls that fall from the sky. The ball has associated with it a physics body for gravity and collision detection purposes.
  • Game Over Node – An SKLabelNode instance that displays the score to the user at the end of the game.

The overall architecture of the game can be represented hierarchically as outlined in Figure 64-1:

<google>ADSDAQBOX_FLOW</google> The node hierarchy of the example iOS 8 Sprite Kit game

Figure 64-1

Creating the SpriteKitDemo Project

To create the project, launch Xcode and select the Create a new Xcode project option from the welcome screen (or use the File -> New -> Project…) menu option. On the template selection panel make sure that the Application category is selected under iOS in the left hand pane before choosing the Game template option.

Click on the Next button to proceed and on the resulting options screen, name the product SpriteKitDemo and choose Swift as the language in which the application will be developed. Finally, set the Game Technology and Devices menus to SpriteKit and Universal respectively. Click Next and choose a suitable location for the project files. Once selected, click Create to create the project.


Reviewing the SpriteKit Game Template Project

Selection of the SpriteKit Game template has caused Xcode to create a template project with a demonstration incorporating some pre-built Sprite Kit behavior. This consists of a View Controller class (GameViewController.swift), an Xcode Sprite Kit scene file (GameScene.sks) and corresponding GameScene class file (GameScene.swift). The code within the GameViewController.swift file loads the scene design contained within the GameScene.sks file and presents it on the view so that it is visible to the user. This, in turn, triggers a call to the didMoveToView method of the GameScene class as implemented in the GameScene.swift file. This method creates an SKLabelNode displaying text that reads “Hello, World!”.

The GameScene class also includes a touchesBegan method implementation which, when triggered, creates an SKSpriteNode instance textured with the Spaceship.png image file and displays it at the location at which the screen was touched. A rotation SKAction is then created and run on the space ship sprite node so that the sprite spins in place. To see the template project in action (Figure 64-2), run it on a physical device or the iOS simulator.


Xcode 6 default sprite project.png

Figure 64-2


As impressive as this may be given how little code is involved, this bears no resemblance to the game that will be created in this chapter, so some of this functionality needs to be removed to provide a clean foundation on which to build. Begin the tidying process by selecting and editing the GameScene.swift file to remove the code to create and present nodes in the scene:

import SpriteKit

class GameScene: SKScene {
    override func didMoveToView(view: SKView) {
        /* Setup your scene here */
 
    }

    override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
        /* Called when a touch begins */

    }
.
.
}

With these changes made, it is time to start creating the SpriteKitDemo game.

Restricting Interface Orientation

The game created in this tutorial assumes that the device on which it is running will be in landscape orientation. To prevent the user from attempting to play the game with a device in portrait orientation, the supportedInterfaceOrientations method located in the GameViewController.swift file needs to be modified to only allow Landscape Left orientation as follows:

override func supportedInterfaceOrientations() -> Int {
      return Int(UIInterfaceOrientationMask.LandscapeLeft.rawValue)
}

Modifying the GameScene SpriteKit Scene File

As previously outlined, Xcode has provided a SpriteKit scene file (GameScene.sks) for a scene named GameScene together with a corresponding class declaration contained within the GameScene.swift file. The next task is to repurpose this scene to act as the welcome screen for the game. Begin by selecting the GameScene.sks file so that it loads into the SpriteKit Level Editor as shown in Figure 64 3:


Xcode 6 sprite kit level editor.png

Figure 64-3


When working with the Level Editor to design SpriteKit scenes there are a number of key areas of importance, each of which has been labelled in the above figure:

  • A – Scene Canvas - This is the canvas onto which nodes may be placed, positioned and configured.
  • B – SKNode Inspector Panel - This panel provides a range of configuration options for the currently selected node in the scene canvas.
  • C – Object Library - The Object Library panel contains a range of node and effect types that can be dragged and dropped onto the scene. Clicking on the far right button in the Object Library toolbar displays the Media Library containing items such as textures for sprites. At this stage in the tutorial, the only item in the Media Library is the Spaceship texture.
  • D – Simulate/Edit Button - Toggles between the editor’s simulate and edit modes. Simulate mode provides a useful mechanism for previewing the scene behavior without the need to compile and run the application.
  • E – Zoom Buttons – Buttons to zoom in and out of the scene canvas.

With the scene selected in the scene canvas, click on the Color swatch in the SKNode Inspector panel and use the color selection dialog to change the scene color to a shade of green.

From within the Object Library panel, locate the Label node object and drag and drop an instance onto the center of the scene canvas. With the label still selected, change the Text property in the inspector panel to read “SpriteKitDemo – Tap Screen to Play”. Remaining within the inspector panel, click on the T next to the font name and use the font selector to assign a 24-point Marker Felt Wide font to the label from the Fun font category. Finally, set the Name property for the label node to “welcomeNode”. Save the scene file before proceeding.

With these changes complete, the scene should resemble that of Figure 64-4:


Ios 8 sprite kit welcome scene.png

Figure 64-4

Creating the Archery Scene

As previously outlined, the first scene of the game is a welcome screen on which the user will tap to begin playing. Add a new class to the project to represent this second scene by selecting the File -> New -> File… menu option. In the file template panel, make sure that the iOS Source option is selected in the left hand panel and that the Cocoa Touch Class template is selected in the main panel. Click on the Next button and configure the new class to be a subclass of SKScene named ArcheryScene. Click on the Next button and create the new class file within the project folder.

The new scene class will also require a corresponding SpriteKit scene file. Select File -> New -> File… once again, this time selecting the iOS Resource option from the left hand panel and SpriteKit Scene from the main panel (Figure 64-5). Click Next, name the scene ArcheryScene and click on the Create button to add the scene file to the project.


Xcode 6 add new spritekit scene.png

Figure 64-5


Edit the newly added ArcheryScene.swift file and modify it to import the SpriteKit Framework as follows:

import UIKit
import SpriteKit

class ArcheryScene: SKScene {

}

Transitioning to the Archery Scene

Clearly having instructed the user to tap the screen in order to play the game, some code now needs to be written to make this happen. This behavior will be added by implementing the touchesBegan method in the GameScene class. Rather than move directly to ArcheryScene, however, some effects will be added in the form of an action and a transition.

When implemented, the SKAction will cause the node to fade away from view whilst an SKTransition instance will be used to animate the transition from the current scene to the archery scene using a “doorway” style of animation. Implement these requirements by adding the following code to the touchesBegan method in the GameScene.swift file:

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
    /* Called when a touch begins */
    let welcomeNode = childNodeWithName("welcomeNode")

    if (welcomeNode != nil) {
        let fadeAway = SKAction.fadeOutWithDuration(1.0)

        welcomeNode?.runAction(fadeAway, completion: {
            let doors = SKTransition.doorwayWithDuration(1.0)
            let archeryScene = ArcheryScene(fileNamed: "ArcheryScene")
            self.view?.presentScene(archeryScene, transition: doors)
        })
    }
}

Before moving on to the next steps, we will take some time to provide more detail on the above code.

From within the context of the touchesBegan method we have no direct reference to the welcomeNode instance. We do know, however, that when it was added to the scene in the SpriteKit Level Editor that it was assigned the name “welcomeNode”. Using the childNodeWithName method of the scene instance, therefore, a reference to the node is being obtained within the touchesBegan method as follows:

let welcomeNode = childNodeWithName("welcomeNode") 

The code then checks that the node was found before creating a new SKAction instance configured to cause the node to fade from view over a one second duration:

let fadeAway = SKAction.fadeOutWithDuration(1.0)

The action is then executed on the welcomeNode. A completion block is also specified to be executed when the action completes. This block creates an instance of the ArcheryScene class preloaded with the scene contained within the ArcheryScene.sks file and an appropriately configured SKTransition object. The transition to the new scene is then initiated:

let fadeAway = SKAction.fadeOutWithDuration(1.0)

welcomeNode?.runAction(fadeAway, completion: {
    let doors = SKTransition.doorwayWithDuration(1.0)
    let archeryScene = ArcheryScene(fileNamed: "ArcheryScene")
    self.view?.presentScene(archeryScene, transition: doors)
})

Compile and run the application. Once running, touch the screen and note that the label node fades away and that after the transition to the ArcheryScene takes effect we are presented with a grey scene that now needs to be implemented.

Adding the Texture Atlas

Before textures can be used on a sprite node, the texture images first need to be added to the project. Textures take the form of image files and may be added individually to the Supporting Files folder of the project. For larger numbers of texture files, it is more efficient (both for the developer and the application) to create a texture atlas. In the case of the archer sprite, this will require twelve image files to animate the loading and subsequent shooting of an arrow. As such, a texture atlas will be used to store these animation frame images. The images for this project can be found in the sample code download which can be obtained from the following web page:

http://www.ebookfrenzy.com/web/ios8

Within the code sample archive, locate the folder named SpriteImages. Located within this folder is the archer.atlas sub-folder which contains the animation images for the archer sprite node.

To add the atlas to the project, drag and drop the archer.atlas folder into the Supporting Files folder in the Xcode project navigator panel so that it appears as shown in the following figure:


Ios 8 archer texture atlas added.png

Figure 64-6

<google>BUY_IOS8</google>

Designing the Archery Scene

The layout for the archery scene is contained within the ArcheryScene.sks file. Select this file so that it loads into the Level Editor environment. With the scene selected in the canvas, use the SKNode Inspector panel to change the color property to white.

From within the SpriteKit Level Editor the next task is to add to the scene the sprite node representing the archer. Referring to the Object Library panel, click on the Media Library toolbar option and locate the archer001.png texture image file as outlined in Figure 64-7:


Spritekit level editor image in media library.png

Figure 64-7


Once located, drag and drop the texture onto the canvas and position it so that it is located in the vertical center of the scene at the left hand edge as shown in the following figure:


Xcode 6 spritekit editor archer Added.png

Figure 64-8


With the archer node selected, use the SKNode Inspector panel to assign the name “archerNode” to the sprite. The next task is to define the outline of the physical outline of the archer sprite. This will be used by the SpriteKit system when deciding whether the sprite has been involved in a collision with another node within the scene. By default, the physical shape is assumed to be a rectangle surrounding the sprite texture (represented by the blue boundary around the node in the scene editor). Another option is to define a circle around the sprite to represent the physical shape. A much more accurate approach, and one that has been introduced in iOS 8, is to have SpriteKit base the physical shape of the node based on the outline of the sprite texture image. With the archer node selected in the scene, scroll down within the SKNode inspector panel until the Physics Definition section comes into view. Using the Body Type menu, change the setting from Bounding rectangle to Alpha mask:


Ios 8 archer sprite alpha mask.png

Figure 64-9


Once the change has been made, note that the sprite node is now outlined by a physical body shape matching the texture image:


Xcode 6 spritekit alpha mask.png

Figure 64-10


Before proceeding with the next phase of the development process, test that the scene behaves as required by clicking on the Simulate button located along the bottom edge of the editor panel. Note that the archer slides down and disappears off the bottom edge of the scene. This is because the sprite is configured to be affected by gravity. For the purposes of the game, the archer is required to be pinned to the same location and not subject to the laws of gravity. Click on the Edit button to leave simulation mode, select the archer sprite and within the Physical Definition section turn the Pinned option on and the Dynamic, Allows Rotation and Affected by Gravity options off. Re-run the simulation to verify that the archer sprite now remains in place.

Save the scene file before proceeding.

Preparing the Archery Scene

Select the ArcheryScene.swift file and modify it as follows to add some private variables and implement the didMoveToView method:

import UIKit
import SpriteKit

class ArcheryScene: SKScene {

    var score = 0
    var ballCount = 20

    override func didMoveToView(view: SKView) {
            self.initArcheryScene()
    }
.
.
}

The above code calls another method named initArcheryScene which now needs to be implemented as follows within the ArcheryScene.swift file ready for code which will be added later in the chapter:

func initArcheryScene() {
}

Preparing the Animation Texture Atlas

When the user touches the screen, the archer sprite node will launch an arrow across the scene. For the purposes of this example we want the loading and shooting of the arrow by the sprite character to be animated. The texture atlas already contains the animation frames needed to implement this (named sequentially from archer001.png through to archer012.png), so the next step is to write some code to implement the animation.

In the first instance, an array of the textures in the animation atlas needs to be created. Whilst it is possible to write code to manually reference each texture file, this would quickly become a cumbersome task for animations involving large numbers of textures. A more efficient approach is to write a for loop to iterate through the files in the atlas, assigning each file name to the texture array. Since this only needs to be performed once each time the scene is presented, this is best achieved within the initArcheryScene method.

Edit the ArcheryScene.swift file and modify it as follows to declare and initialize the array:

import UIKit
import SpriteKit

class ArcheryScene: SKScene {

    var score = 0
    var ballCount = 20
    var archerAnimation = [SKTexture]()
.
.
.
    func initArcheryScene() {
        let archerAtlas = SKTextureAtlas(named: "archer")

        for index in 1...archerAtlas.textureNames.count {
            let texture = String(format: "archer%03d", index)
            archerAnimation += [archerAtlas.textureNamed(texture)]
        }
    }
.
.
}

The above code begins by obtaining a reference to the archer texture atlas. A loop is then initialized based on the number of textures in the atlas. With knowledge of the naming convention of the image files, a texture file name is created and the textureNamed method of the SKTextureAtlas instance used to extract the texture with the corresponding name. The texture is then added to the archerAnimation array where it will be referenced later to animate the sprite node.

Animating the Archer Sprite Node

Now that an array of textures for the animation action has been created and initialized, code now needs to be written to perform the animation. Since this animation is triggered by a touch on the screen, this code needs to go in the touchesBegan method of the ArcheryScene class. Edit the ArcheryScene.swift file and implement this method as follows:

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {

    let archerNode = self.childNodeWithName("archerNode")

    if archerNode != nil {
        let animate = SKAction.animateWithTextures(archerAnimation, 
					timePerFrame: 0.05)
        archerNode?.runAction(animate)
    }
}

The method obtains a reference to the sprite node representing the archer character in the game, creates an SKAction object configured to animate the texture frames in the archerAnimation array and then runs the action on the archer node.

Run the application and touch the screen within the Archery Scene. Each time a touch is detected, the archer sprite will run through the animation sequence of shooting an arrow.

Creating the Arrow Sprite Node

At this point in the tutorial, the archer sprite node goes through an animation sequence of loading and shooting an arrow but no actual arrow is being launched across the scene. In order to implement this, a new sprite node needs to be added to the ArcheryScene. This node will be textured with an image of an arrow and will be placed to the right of the archer sprite at the end of the animation sequence. A physics body will be associated with the arrow and an impulse force applied to it to propel it across the scene as though shot by the archer’s bow.

Begin by locating the ArrowTexture.png file in the SpriteImages folder of the sample code archive and drag and drop it onto the Supporting Files folder in the Xcode project navigator panel. Next, add a new method named createArrowNode within the ArcheryScene.swift file so that it reads as follows:

func createArrowNode() -> SKSpriteNode {
    let archerNode = self.childNodeWithName("archerNode")
    let archerPosition = archerNode?.position
    let archerWidth = archerNode?.frame.size.width

    let arrow = SKSpriteNode(imageNamed: "ArrowTexture.png")
    arrow.position = CGPointMake(archerPosition!.x + archerWidth!, 
				archerPosition!.y)
    arrow.name = "arrowNode"

    arrow.physicsBody = SKPhysicsBody(rectangleOfSize: 
		arrow.frame.size)

    arrow.physicsBody?.usesPreciseCollisionDetection = true

    return arrow
}

The code creates a new SKSpriteNode object, positions it to the right of the archer sprite node and assigns it the name arrowNode. A physics body is then assigned to the node, using the size of the node itself as the boundary of the body and enabling precision collision detection. Finally the node is returned.

Shooting the Arrow

In order to propel the arrow across the scene, a physical force needs to be applied to it. The creation and propulsion of the arrow sprite needs to be timed to occur at the end of the archer animation sequence. This can be achieved via some minor modifications to the touchesBegan method:

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
    let archerNode = self.childNodeWithName("archerNode")

    if archerNode != nil {
        let animate = SKAction.animateWithTextures(archerAnimation, timePerFrame: 0.05)

        let shootArrow = SKAction.runBlock({
            let arrowNode = self.createArrowNode()
            self.addChild(arrowNode)
            arrowNode.physicsBody?.applyImpulse(CGVectorMake(60.0, 0))
        })

        let sequence = SKAction.sequence([animate, shootArrow])
        archerNode?.runAction(sequence)
    }
}

A new SKAction object is created, this time specifying a block of code to be executed. This run block calls the createArrowNode method, adds the new node to the scene and then applies an impulse force of 60.0 on the X axis of the scene. An SKAction sequence is then created comprising the previously created animation action and the new run block action. This sequence is then run on the archer node.

When executed with these changes, touching the screen should now cause an arrow to be launched after the archer animation completes. Note that as the arrow flies across the scene it gradually falls towards the bottom of the display. This is, of course, due to the effect of gravity imposed upon the physics body assigned to the node.

Adding the Ball Sprite Node

The objective of the game is to score points by hitting balls with arrows. Clearly, the next logical step is to add the ball sprite node to the scene. Begin by locating the BallTexture.png file in the SpriteImages folder of the sample code package and drag and drop it onto the Supporting Files section of the project navigator panel.

Next add the corresponding createBallNode method to the ArcheryScene.swift file as outlined in the following code fragment:

func createBallNode() {
    let ball = SKSpriteNode(imageNamed: "BallTexture.png")
    ball.position = CGPointMake(randomBetween(0, high: 
			self.size.width-200), self.size.height-50)

    ball.name = "ballNode"
    ball.physicsBody = SKPhysicsBody(circleOfRadius: 
			(ball.size.width/2))

    ball.physicsBody?.usesPreciseCollisionDetection = true
    self.addChild(ball)
}

This code creates a sprite node using the ball texture and then sets the initial position at the top of the scene but a random position on the X axis. The node is assigned a name and a circular physics body that is slightly less than the radius of the ball texture image (this will ensure that there is no gap when the arrow is made to stick into the ball in the next chapter). Precision collision detection is enabled and the ball node is added to the scene.

Before proceeding, add the following method to facilitate the random number generation used in calculating the X coordinate of the ball sprite node:

func randomBetween(low: CGFloat, high: CGFloat) -> CGFloat
{
    let lowInt = UInt32(low)
    let highInt = UInt32(high) - UInt32(low)
    let result = arc4random_uniform(highInt) + UInt32(low)
    return CGFloat(result)
}

Next, modify the initArcheryScene method to create an action to release a total of 20 balls at one second intervals:

func initArcheryScene() {
    let archerAtlas = SKTextureAtlas(named: "archer")

    for index in 1...archerAtlas.textureNames.count {
        let texture = String(format: "archer%03d", index)
        archerAnimation += [archerAtlas.textureNamed(texture)]
    }

    let releaseBalls = SKAction.sequence([SKAction.runBlock({ 
		self.createBallNode() }),
        SKAction.waitForDuration(1)])

    self.runAction(SKAction.repeatAction(releaseBalls, 
		count: ballCount), completion: nil)
}

Run the application and verify that the balls now fall from the top of the scene. Attempt to hit the balls as they fall by tapping the background to launch arrows. Note, however, that when an arrow hits a ball it simply bounces off:


Ios 8 spritekit demo running.png

Figure 64-11


The goal for the completed game is to have the arrows stick into the balls on collision and for a score to be updated for each hit. The steps to implement this behavior will be covered in the next chapter entitled An iOS 8 Sprite Kit Collision Handling Tutorial.

The balls fall from the top of the screen because they have been assigned a physics body and are subject to the simulated forces of gravity within the Sprite Kit physical world. To reduce the effects of gravity on both the arrows and balls, modify the didMoveToView method to change the current gravity setting on the scene’s physicsWorld object:

override func didMoveToView(view: SKView) {
    self.physicsWorld.gravity = CGVectorMake(0, -1.0)
    self.initArcheryScene()
}

Summary

The goal of this chapter has been to create a simple game for iOS 8 using the Sprite Kit framework. In the course of creating this game topics such as using sprite nodes, actions, textures, sprite animations and physical forces have been put to practical use.

In the next chapter, this game example will be further extended to demonstrate the detection of collisions and the use of physical joins.


<google>BUY_IOS8</google>



PreviousTable of ContentsNext
An Introduction to iOS 8 Sprite Kit Game ProgrammingA Swift iOS 8 Sprite Kit Collision Handling Tutorial