SpriteKit is Apple's 2D game engine—a rendering engine built on top of OpenGL. It was introduced with iOS 7, and each subsequent release has brought great additions to the framework. With the use of textured sprites, a built-in physics engine, and the very powerful SKAction
class, you can very quickly build functional 2D games.
SpriteKit has built-in editors for scenes and particles, a camera node since the release of iOS9, and built-in support for tilesets since the release of iOS 10. With these new additions, SpriteKit is quickly becoming a powerhouse for creating 2D games.
To follow along with this tutorial, just download the accompanying GitHub repo. It has a folder called ExampleProject Starter. Open the project in that folder in Xcode, and you're ready to go!
Nodes
Nodes are the fundamental building blocks of SpriteKit, and SKNode
is the base class of all nodes. All of your
onscreen assets will be an SKNode
or a subclass thereof. SKNode
s by
themselves do not provide any visual content, however. All visual content is
drawn using one of a number of predefined SKNode
subclasses. SKNode
s
and its subclasses share several properties you can alter. Some of
the more important ones are as follows.
position
(CGPoint
): the node's position within its parent's coordinate systemxScale
(CGFloat
): scales the width of a node by a multiplieryScale
(CGFloat
): scales the height of a node by a multiplieralpha
(CGFloat
): the transparency of the nodezRotation
(CGFloat
): the Euler rotation about the z axis (in radians)
One of the most important SKNode
s is the SKScene
. This is the root node to which all other nodes are added. By itself, SKScene
does not provide any visual elements, but it displays the nodes which are added to it.
Scene Nodes
SKScenes
are the root nodes to which all other nodes are added. The scene animates and renders the content from its child nodes. To display a scene, you add it to an SKView
(which is a subclass of UIView
and therefore has many of the same properties as UIView
).
In the SpriteKit starter project, the initial scene is showing when the project loads. For now, this is just a blank black screen. It is shown when the GameViewController
invokes presentScene(_:)
on the view instance, passing in the scene as a parameter:
override func viewDidLoad() { super.viewDidLoad() let scene = GameScene(size:CGSize(width: 768, height: 1024)) let skView = self.view as! SKView skView.showsFPS = false skView.showsNodeCount = false skView.ignoresSiblingOrder = false scene.scaleMode = .aspectFill skView.presentScene(scene) // Present the Scene }
Don't worry about the other options for now; I'll explain them later in this series.
Creating a Scene
Many games have more than one screen or scene, so we will create a new scene from scratch and then show it from our initial scene.
Select File> New> File from Xcode's menu, and choose Cocoa Touch Class.
Make sure Class is set to NewScene and that Subclass of is set to SKScene
. Press Next and then Create, making sure the main target is checked. Below is the code for the NewScene.swift.
import UIKit import SpriteKit class NewScene: SKScene { }
Now we have two scenes in our project, and neither has any visual content. Let's add an SKLabelNode
(like all nodes, this is a subclass of SKNode
). The SKLabelNode
's sole purpose is to display a text label.
Label Nodes
Label nodes, implemented in the SKLabelNode
class, are used to show text within your game. You can use custom fonts if you wish, but for our purposes we will just stick to the default, which displays white text and is set to Helvetica Neue Ultra Light, 32 point.
Add the following inside the didMove(to:)
method within GameScene.swift. This method is called immediately after a scene is presented by a view. Generally, this is where you would set up any of your game's assets and add them to the scene.
override func didMove(to view: SKView) { let startGameLabel = SKLabelNode(text: "Start Game") }
Here we create an SKLabelNode
using the convenience initializer init(text:)
, which takes as a parameter a string of text.
Adding and Removing Nodes
Just initializing nodes will not show them in the scene. To get the nodes to show, you have to invoke the addChild(_:)
method on the receiving node, passing the SKNode
that you wish to add as a parameter.
Add the following within the didMove(to:)
method.
override func didMove(to view: SKView) { let startGameLabel = SKLabelNode(text: "Start Game") addChild(startGameLabel) }
The addChild(_:)
method is not exclusive to SKScene
s, but is a method of SKNode
. This allows you to build a complex hierarchy of nodes—known as the "node tree". For example, suppose you have a game character and you wanted to move its arms and legs separately. You could create an SKNode
instance and then add each individual part as a child of that SKNode
(the containing node is known as the parent node). This would give you the benefit of being able to move the character as a whole unit by moving the parent SKNode
, but also allow you to move each individual part individually.
Another important method for adding nodes is the insertChild(_:at:)
method, which inserts a child into a specific position within the receiver node's list of children. When you add a child to a node, the node maintains an ordered list of children which is referenced by reading the node's children
property. It is important when adding multiple nodes to a parent node to take this into consideration, as the order in which you add the nodes affects some of the aspects of scene processing, including hit testing and rendering.
To remove a node, you invoke the removeFromParent()
method on the node you wish to remove.
Now that we have covered adding and removing nodes, we can move our focus back to the example project. If you recall, we had just added an SKLabelNode
to the GameScene
. If you test now, you will see just half of the text off to the bottom left of the screen.
Why is only half the text showing, though? Now would be a good time to talk about SpriteKit's coordinate and positioning system.
Positioning and Coordinates
By default, SpriteKit's coordinate system places (0,0) at the bottom left of the screen. Also by default, SpriteKit places nodes so they are positioned at (0,0). Still, though... why are we only seeing half of the text? This is because by default the text label is centered horizontally on the label node's origin, which is (0,0). Below is an image that shows how a node's coordinate system works.
Node's origins are at (0,0), and a positive x coordinate moves to the right and a positive y coordinate goes up the screen. Remember that an SKScene is a node, and therefore its origin is also (0,0).
Setting a Node's Position
Now that we have learned SpriteKit's coordinate system works and how it places nodes, we can move the SKLabelNode
to a different position so we can see all of the text. Add the following to the didMove(to:)
method within GameScene.swift.
override func didMove(to view: SKView) { let startGameLabel = SKLabelNode(text: "Start Game") startGameLabel.position = CGPoint(x: size.width/2, y: size.height/2) addChild(startGameLabel) }
Here we position the label to the center of the scene. The position
property is of type CGPoint
, which has x and y values that represent a single point within the scene.
If you test now, you should see the label has been positioned in the center of the scene.
Switching Between Scenes
As it currently stands, NewScene
is just a blank scene. Let's also add a label to it, and then we can learn how to switch between scenes. Here's a challenge: before you read ahead, try to add a label to NewScene
that says, "Go Back". My solution is below.
The first thing we need to do is add the didMove(to:)
method. Add the following to NewScene.swift.
class NewScene: SKScene { override func didMove(to view: SKView) { } }
Next, we need to add the label. Add the following within the didMove(to:)
method that you added above.
override func didMove(to view: SKView) { let goBackLabel = SKLabelNode(text: "Go Back") goBackLabel.position = CGPoint(x: size.width/2, y: size.height/2) addChild(goBackLabel) }
This adds a label to NewScene
with the text "Go Back". Next, we'll implement the functionality this label suggests—we'll respond to touch events by switching scenes.
Responding to Touch
Nearly all mobile games will be interacted with using touch. In this step, you will learn how to respond to touch events within your game.
To register touch event handlers within your game, you must implement the view's touchesBegan(_:with:)
method. Add the following to GameScene.swift:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { print("YOU TOUCHED") }
If you want to test this now, you will see YOU TOUCHED printed to the console when you touch on the screen. What we usually need, however, is to be able to tell when a specific node has been touched. To do this, we need some way to find and identify the nodes. We will learn how to accomplish this, and then come back and finish the touchesBegan(_:with:)
method.
Searching the Node Tree
To be able to identify a node, you use the node's name
property and the search the node tree for a node with that name. The node's name
property takes an alphanumeric string without any punctuation.
There are a couple of methods to search for a node by its name
property. If you already have a reference to the node, you can just check its name
property directly, which is what we will do in the touchesBegan(_:with:)
method. However, it is important to know how to search the node tree for a particular node by name, or to search for a group of nodes with the same name.
The childNode(withName:)
method searches the children of a node for the specific name passed in as a parameter.
The enumerateChildNodes(withName:using:)
method searches a node's children and calls the block once for each matching node it finds. You use this method when you want to find all nodes that share the same name.
The subscript(_:)
method returns an array of nodes that match the name parameter.
You can also search for nodes using an advanced searching syntax that allows you to search the entire scene tree, or search for a pattern rather than an exact name, for example. This advanced searching capability is beyond the scope of this tutorial. However, if you wish to learn more, you can read about in the SKNode
programming reference.
Now that we know how to search for nodes within the node tree, let's give our labels a name.
Add the following within the didMove(to:)
method within GameScene.swift.
override func didMove(to view: SKView) { let startGameLabel = SKLabelNode(text: "Start Game") startGameLabel.name = "startgame" startGameLabel.position = CGPoint(x: size.width/2, y: size.height/2) addChild(startGameLabel) }
Here, we set startGameLabel
's name property to startgame
.
We also need to set the label's name within NewScene
. Add the following with the didMove(to:)
method within NewScene.swift.
override func didMove(to view: SKView) { let goBackLabel = SKLabelNode(text: "Go Back") goBackLabel.name = "goback" goBackLabel.position = CGPoint(x: size.width/2, y: size.height/2) addChild(goBackLabel) }
We set the name
property to goback
.
Detecting Which Node Is Touched
Add the following within the touchesBegan(_:with:)
method within GameScene.swift.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } let touchLocation = touch.location(in: self) let touchedNode = self.atPoint(touchLocation) if(touchedNode.name == "startgame"){ let newScene = NewScene(size: size) newScene.scaleMode = scaleMode let doorsClose = SKTransition.doorsCloseVertical(withDuration: 2.0) view?.presentScene(newScene, transition: doorsClose) } }
The multiTouchEnabled
property of the scene's view is set to false
by default, which means the view only receives the first touch of a multitouch sequence. With this property disabled, you can retrieve the touch by using the first
computed property of the touches set, since there is only one object in the set.
We can get the touchLocation
within the scene from the location
property of the touch. We can then figure out which node was touched by invoking atPoint(_:)
and passing in the touchLocation
.
We check if the touchedNode
's name property is equal to "startgame"
, and if it is, we know that the user has touched the label. We then create an instance of NewScene
and set its scalemode
property to be the same as the current scene—this ensures the scene acts the same across different devices. Finally, we create an SKTransition
and invoke the presentScene(_:transition:)
method, which will present the scene along with the transition.
The SKTransition
class has many class methods that you can invoke to show different transitions between scenes instead of immediately showing the scene. This provides a bit of "eye candy" for the end user, and makes showing a new scene seem less abrupt. To see all of the available transition types, check out the SKTransition
class in the reference guide.
I'm not going to implement the touchesBegan(_:with:)
method in NewScene
. Why don't you try doing that on your own and have the label transition back to the GameScene
using a different transition type? The code will closely resemble what we have above, only remember we named the SKLabelNode
"goback"
.
Conclusion
We have learned a good bit about nodes so far using scenes, and you've seen how to use a label node as a generic example to learn some of the characteristics of nodes. We've studied their coordinate systems, how to locate them within the node tree, how to position them, and how to respond to touch events.
There are several other kinds of nodes available, and we'll take a look at them in the next tutorial—starting with SKSpriteNode
!
To learn more about how to get started with SpriteKit, you should also check out Davis Allie's post here on Envato Tuts+.
Also, check out our SpriteKit courses! These will take you through all the steps of building your first SpriteKit game for iOS, even if you've never coded with SpriteKit before.
- SwiftCode a Side-Scrolling Game With Swift 3 and SpriteKit
- Game DevelopmentGame Development With Swift and SpriteKit