An iOS 10 Sprite Kit Level Editor Game Tutorial
Previous | Table of Contents | Next |
An Introduction to iOS 10 Sprite Kit Game Programming | An iOS 10 Sprite Kit Collision Handling Tutorial |
Learn SwiftUI and take your iOS Development to the Next Level |
In this chapter of iOS 10 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 Xcode Sprite Kit Level, Live and Action editors combined with Swift code to create a Sprite Kit based game.
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.
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 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 68-1:
Figure 68-1
In addition to the nodes outlined above, the Xcode Live and Action editors will be used to implement animation and audio actions which will be triggered from within the code of the application.
Learn SwiftUI and take your iOS Development to the Next Level |
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 choose 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 didMove(to view:) 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 variety of touch method implementations which, when triggered, create SKShapeNode instances into which graphics are drawn. These nodes, in turn, are displayed in response to touches and movements on the device screen. To see the template project in action, run it on a physical device or the iOS simulator and perform tapping and swiping motions on the display.
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. Once modified, the file should read as follows:
import SpriteKit import GameplayKit class GameScene: SKScene { override func didMove(to view: SKView) { } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { } override func update(_ currentTime: TimeInterval) { // Called before each frame is rendered } }
With these changes made, it is time to start creating the SpriteKitDemo game.
Learn SwiftUI and take your iOS Development to the Next Level |
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 Device Orientation properties for the project need to be restricted. To achieve this, select the SpriteKitDemo entry located at the top of the Project Navigator and, in the resulting General settings panel, change the device orientation settings so that only the Landscape options are selected:
Figure 68-2
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 68-3:
Figure 68-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 – Attribute Inspector Panel - This panel provides a range of configuration options for the currently selected item in the editor panel. This allows SKNode and SKAction objects to be customized from within the editor environment.
• 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 – Animate/Layout Button - Toggles between the editor’s simulation and layout editing 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.
• F – Live Editor – The live editor allows actions and animations to be placed within a timeline and simulated within the editor environment. It is possible, for example, to add animation and movement actions within the live editor and play them back live within the scene canvas.
• G – Timeline View Slider – Pans back and forth through the view of the live editor timeline.
• H – Playback Speed – When in Animation mode, this control adjusts the playback speed of the animations and actions contained within the live editor panel.
• I – Scene Graph View – This panel provides an overview of the hierarchy of the scene and can be used to select, delete, duplicate and reposition scene elements within the hierarchy.
Within the scene editor, click on the Hello, World! Label node and press the keyboard delete key to delete it from the scene. With the scene selected in the scene canvas, click on the Color swatch in the Attribute Inspector panel and use the color selection dialog to change the scene color to a shade of green. Remaining within the Attributes Inspector panel, change the Size setting from Custom to iPad 9.7”:
Figure 68-4
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 64-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 68-5:
Figure 68-5
Learn SwiftUI and take your iOS Development to the Next Level |
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 within a second scene. 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 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 SpriteKit Scene from the main panel (Figure 68 6). Click Next, name the scene ArcheryScene and click on the Create button to add the scene file to the project.
Figure 68-6
Edit the newly added ArcheryScene.swift file and modify it to import the SpriteKit Framework as follows:
import UIKit import SpriteKit class ArcheryScene: SKScene { }
Learn SwiftUI and take your iOS Development to the Next Level |
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 while 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<UITouch>, with event: UIEvent?) { let welcomeNode = childNode(withName: "welcomeNode") if (welcomeNode != nil) { let fadeAway = SKAction.fadeOut(withDuration: 1.0) welcomeNode?.run(fadeAway, completion: { let doors = SKTransition.doorway(withDuration: 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 childNode(withName:) method of the scene instance, therefore, a reference to the node is being obtained within the touchesBegan method as follows:
let welcomeNode = childNode(withName: "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.fadeOut(withDuration: 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.fadeOut(withDuration: 1.0) welcomeNode?.run(fadeAway, completion: { let doors = SKTransition.doorway(withDuration: 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 asset catalog 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/ios10
Within the code sample archive, locate the folder named sprite_images. 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, select the Assets.xcassets catalog file in the Project Navigator to display the image assets panel. Locate the archer.atlas folder in a Finder window and drag and drop it onto the asset catalog panel so that it appears beneath the existing AppIcon entry as shown in the following figure:
Figure 68-7
Before proceeding, be sure to select and delete the SpaceShip image set from the catalog.
Learn SwiftUI and take your iOS Development to the Next Level |
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 Attributes Inspector panel to change the color property to white and the Size property to iPad 9.7”.
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 68-8:
Figure 68-8
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:
Figure 68-9
With the archer node selected, use the Attributes Inspector panel to assign the name “archerNode” to the sprite. The next task is to define 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 is to have SpriteKit define 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 Attribute Inspector panel until the Physics Definition section comes into view. Using the Body Type menu, change the setting to Alpha mask:
Figure 68-10
Once the change has been made, note that the sprite node is now outlined by a physical body shape matching the texture image:
Figure 68-11
Before proceeding with the next phase of the development process, test that the scene behaves as required by clicking on the Animate 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 Layout 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 animation 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 didMove(to:) method:
Learn SwiftUI and take your iOS Development to the Next Level |
import UIKit import SpriteKit class ArcheryScene: SKScene { var score = 0 var ballCount = 20 override func didMove(to view: SKView) { let archerNode = self.childNode(withName: "archerNode") archerNode?.position.y = 0 archerNode?.position.x = -self.size.width/2 + 40 self.initArcheryScene() } . . }
When the archer node was added to the ArcheryScene it was positioned using absolute X and Y coordinates. This means that the node will be positioned correctly on an iPad with a 9.7” screen, but not on any other screen sizes. The first task performed by the didMove method, therefore, is to position the archer node correctly relative to the screen size. In terms of the scene, position 0, 0 corresponds to the center point of the screen. To position the archer node in the vertical center of the screen, therefore, the y-coordinate it set to zero. The code then obtains the width of the screen, performs a basic calculation to identify a position 40 points in from the left-hand edge of the screen and assigns it to the x-coordinate of the node.
The above code then 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 create an action to animate this sequence of frames. One option would be to write some code to perform this task. A much easier option, however, is to create an animation action using the SpriteKit Live Editor.
Begin by selecting the ArcheryScene.sks file so that it loads into the editor. Once loaded, the first step is to add an AnimateWithTextures action within the timeline of the live editor panel. Within the Object Library panel, scroll down the list of objects until the AnimateWithTextures Action object comes into view. Once located, drag and drop an instance of the object onto the live editor timeline for the archerNode as indicated in Figure 68-12:
Figure 68-12
With the animation action added to the timeline, the action now needs to be configured with the texture sequence to be animated. With the newly added action selected in the timeline, switch the Object Library panel into Media Library mode so that the archer texture images are listed. Use the Command-A keyboard sequence to select all of the images in the library and then drag and drop those images onto the Textures box in the Animate with Textures attributes panel as shown in Figure 68-13:
Figure 68-13
Test the animation by clicking on the Animate button. Note that it is possible to move back and forth through the animation sequences by moving the position marker referenced by the arrow in Figure 68-14:
Figure 68-14
Compile and run the app and tap on the screen to enter the archery scene. On appearing, the animation sequence will execute once. In practice, the animation sequence should only run when the user taps on the screen to launch an arrow. Having this action within the timeline, therefore, does not provide the required behavior for the game. Instead, the animation action needs to be converted to a named action reference, placed in an action file and triggered from within the touchesBegan method of the archer scene class.
Learn SwiftUI and take your iOS Development to the Next Level |
Creating the Named Action Reference
With the ArcherScene.sks file loaded into the level editor, Ctrl-click on the Animate with Textures action in the timeline and select the Convert to Reference option from the popup menu:
Figure 68-15
In the Create Action panel, name the action animateArcher and change the File menu to Create New File. Click on the Create button and, in the Save As panel, navigate to the SpriteKitDemo subfolder of the main project folder and enter ArcherActions into the Save As: field before clicking on Create.
Since the animation action is no longer required in the timeline of the archer scene, select the ArcherScene.sks file, Ctrl-click on the Animate with Texture action in the timeline and select Delete from the menu.
Testing Actions in an Action File
The separation of an action from a scene raises the question of how the action can be tested within the live editor environment. In fact, this can easily be achieved using the Xcode action editor by temporarily assigning the action to a scene and a specific node.
To see this in practice, select the newly created ArcherActions.sks file to load it into the Action Editor panel. When the editor first loads, the action is not associated with any particular scene. To associate the action with a scene, click on the Select… button and select the ArcheryScene.sks file from the list of scenes in the project:
Figure 68-16
Once the archery scene file has been selected, the archer node should appear within the scene canvas. Select this node and refer to the Attributes Inspector panel where the Preview Actions list should be visible. Click on the + button and in the popup panel, select the archerAnimation action before clicking on the Assign button to assign the action to the node:
Figure 68-17
Test the action by clicking on the triangular play button located in the lower left-hand corner of the scene canvas. Once clicked, the scene should animate through the texture sequence of the archer loading and shooting the arrow.
Learn SwiftUI and take your iOS Development to the Next Level |
Triggering the Named Action from the Code
With the previous steps completed, the project now has a named action (named animateArcher) which can be triggered each time the screen is tapped by adding some code to the touchesBegan method of the ArcheryScene.swift file. With this file selected in the Project Navigator panel, implement this method as follows:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { if let archerNode = self.childNode(withName: "archerNode") { let animate = SKAction(named: "animateArcher") archerNode.run(animate!) } }
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. To demonstrate the alternative to using the action and live editors, this task will be performed entirely in code.
Begin by locating the ArrowTexture.png file in the sprite_images folder of the sample code archive and drag and drop it onto the left-hand panel of the Assets.xcassets catalog screen beneath the archer texture atlas entry. Next, add a new method named createArrowNode within the ArcheryScene.swift file so that it reads as follows:
func createArrowNode() -> SKSpriteNode { let archerNode = self.childNode(withName: "archerNode") let archerPosition = archerNode?.position let archerWidth = archerNode?.frame.size.width let arrow = SKSpriteNode(imageNamed: "ArrowTexture.png") arrow.position = CGPoint(x: archerPosition!.x + archerWidth!, y: archerPosition!.y) arrow.name = "arrowNode" arrow.physicsBody = SKPhysicsBody(rectangleOf: arrow.frame.size) arrow.physicsBody?.usesPreciseCollisionDetection = true return arrow }
Learn SwiftUI and take your iOS Development to the Next Level |
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<UITouch>, with event: UIEvent?) { if let archerNode = self.childNode(withName: "archerNode") { let animate = SKAction(named: "animateArcher") let shootArrow = SKAction.run({ let arrowNode = self.createArrowNode() self.addChild(arrowNode) arrowNode.physicsBody?.applyImpulse(CGVector(dx: 60, dy: 0)) }) let sequence = SKAction.sequence([animate!, shootArrow]) archerNode.run(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 sprite_images folder of the sample code package and drag and drop it onto the Assets.xcassets catalog.
Learn SwiftUI and take your iOS Development to the Next Level |
func createBallNode() { let ball = SKSpriteNode(imageNamed: "BallTexture.png") let screenWidth = self.size.width ball.position = CGPoint(x: randomBetween(-screenWidth/2, max: screenWidth/2-200), y: 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. Since position 0 on the X axis corresponds to the horizontal center of the screen (as opposed to the far left side) some calculations are performed to make sure that the balls can fall from most of the width of the screen.
The node is assigned a name and a circular physics body that is slightly less than the radius of the ball texture image. 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(_ min: CGFloat, max: CGFloat) -> CGFloat { return CGFloat(Float(arc4random()) / Float(UINT32_MAX)) * (max - min) + min }
Next, modify the initArcheryScene method to create an action to release a total of 20 balls at one second intervals:
func initArcheryScene() { let releaseBalls = SKAction.sequence([SKAction.run({ self.createBallNode() }), SKAction.wait(forDuration: 1)]) self.run(SKAction.repeat(releaseBalls, count: ballCount)) }
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:
Figure 68-18
The goal for the completed game is to have the balls burst with a sound effect when hit by the arrow and for a score to be presented at the end of the game. The steps to implement this behavior will be covered in the next chapters.
Learn SwiftUI and take your iOS Development to the Next Level |
override func didMove(to view: SKView) { let archerNode = self.childNode(withName: "archerNode") archerNode?.position.y = 0 archerNode?.position.x = -self.size.width/2 + 40 self.physicsWorld.gravity = CGVector(dx: 0, dy: -1.0) self.initArcheryScene() }
Summary
The goal of this chapter has been to create a simple game for iOS 10 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 use demonstrating both the use of the Xcode Sprite Kit editors and Swift code.
In the next chapter, this game example will be further extended to demonstrate the detection of collisions.
Learn SwiftUI and take your iOS Development to the Next Level |
Previous | Table of Contents | Next |
An Introduction to iOS 10 Sprite Kit Game Programming | An iOS 10 Sprite Kit Collision Handling Tutorial |