SKNode class is the fundamental building block of most SpriteKit content.
- iOS 7.0+
- macOS 10.9+
- tvOS 9.0+
- watchOS 3.0+
SKNode class doesn’t draw any visual content. Its primary role is to provide baseline behavior that the other node classes use. All visual elements in a SpriteKit-based game are drawn using predefined
Characteristics of Nodes
Nodes are organized hierarchically into node trees, similar to how views and subviews work. Most commonly, a node tree is defined with a scene node (
SKScene) as the root node and other content nodes as descendants. The scene node runs an animation loop that processes actions on the nodes, simulates physics, and then renders the contents of the node tree for display.
Every node in a node tree provides a coordinate system to its children. After a child is added to the node tree, it is positioned inside its parent’s coordinate system by setting its
position properties. A node’s coordinate system can be scaled and rotated by changing its
z properties. When a node’s coordinate system is scaled or rotated, this transformation is applied both to the node’s own content and to that of its descendants.
SKNode class does not perform any drawing of its own. However, many
SKNode subclasses render visual content and so the
SKNode class understands some visual concepts:
frameproperty provides the bounding rectangle for a node’s visual content, modified by the scale and rotation properties. The frame is non-empty if the node’s class draws content. Each node subclass determines the size of this content differently. In some subclasses, the size of the node’s content is declared explicitly, such as in the
SKSpriteclass. In other subclasses, the content size is calculated implicitly by the class using other object properties. For example, an
SKLabelobject determines its content size using the label’s message text and font characteristics.
A node’s accumulated frame, retrieved by calling the
calculatemethod, is the largest rectangle that includes the frame of the node and the frames of all its descendants.
All nodes are responder objects that can respond directly to user interaction with the node onscreen. You can also convert between coordinate systems and perform hit testing to determine which nodes a point lies in. You can also perform intersections between nodes in the tree to determine if their physical areas overlap.
Any node in the tree may run actions, which are used to animate the properties of a node, add or remove nodes, play sounds, or perform other custom tasks. Actions are the heart of the animation system in SpriteKit.
A node can support a physics body, which is an object that simulates the physical properties of the object. When a node has a physics body, the physics simulation automatically computes a new position for the physics body and then moves and rotates the node to match that position.
A node can supply constraints that express relationships with other nodes or locations in the scene. These constraints are automatically applied by the scene before the scene is rendered. Another set of constraints is used in conjunction with actions to perform inverse-kinematic animations.
Manipulations to nodes must occur in the main thread. All of the SpriteKit callbacks for
SKScene occur in the main thread and these are safe places to make manipulations to nodes. However, if you are performing other work in the background, you should adopt an approach similar to Listing 1.
Use Plain Nodes to Organize Your Game Content
SKNode objects cannot directly draw content, there are useful ways to use them in your game. Here are some ideas to get you started:
You have content that is built up from multiple node objects, either sprites or other content nodes. However, you want this content to be thought of as a single object within your game, without promoting any one of the content nodes to be the root. A basic node is appropriate, because you can give it a position in the scene tree and then make all of the other nodes its descendants. These individual pieces can also be moved or adjusted relative to the parent’s location.
Use node objects to organize your drawn content into a series of layers. For example, many games have a background layer for the world, another layer for characters, and a third layer for text and other game information. Other games have many more layers. Create the layers as basic nodes, and insert them into the scene in order. Then, as necessary, you can make individual layers visible or invisible.
You need an object in the scene that is invisible but that performs some other necessary function. For example, in a dungeon exploring game, an invisible node might be used to represent a hidden trap. When another node intersects it, the trap is triggered. (See
SKNode.) Or for another example, you might add a node as a child of another node that represents the position of the player’s point of view. See
There are advantages to having a node in the tree to represent these concepts:
You can add or remove entire subtrees by adding or removing a single node. This makes scene management efficient.
You can adjust properties of a node in the tree and have the effects of those properties propagate down to the node’s descendants. For example, if the basic node has sprite nodes as its children, rotating the basic node rotates all of the sprite content also.
You can take advantage of actions, physics contacts, and other SpriteKit features to implement the concept.
A Node Provides a Coordinate System to Its Children
When a node is placed in the node tree, its
position property places it within a coordinate system provided by its parent. SpriteKit uses the same coordinate system on both iOS and macOS. Figure 1 shows the SpriteKit coordinate system. Coordinate values are measured in points, as in UIKit or AppKit; where necessary, points are converted to pixels when the scene is rendered. A positive
x coordinate goes to the right and a positive
y coordinate goes up the screen.
SpriteKit also has a standard rotation convention. Figure 2 shows the polar coordinate convention. An angle of
0 radians specifies the positive x axis. A positive angle is in the counterclockwise direction.
Nodes are rotated by setting their
z property to the required angle in radians. If you prefer to work in degrees, the following code shows how you can write an extension to
CGFloat that converts between the two. The example in Listing 2 rotates
sprite by 30 degrees counterclockwise.
Creating the Node Tree
You create the node tree by creating parent-child relationships between nodes. Each node maintains an ordered list of children, referenced by reading the node’s children property. The order of the children in the tree affects many aspects of scene processing, including hit testing and rendering. So, it is important to organize the node tree appropriately.
When you need to directly traverse the node tree, you use the properties in the following table to uncover the tree’s structure.
The array of
If the node is a child of another node, this holds the parent. Otherwise, it holds
If the node is included anywhere in a scene, this returns the scene node that is the root of the tree. Otherwise it holds
Understanding the Drawing Order for a Node Tree
The standard behavior for scene rendering follows a simple pair of rules:
A parent draws its content before rendering its children.
Children are rendered in the order in which they appear in the child array.
Figure 3 shows how a node with four children are rendered.
In the image above, the helicopter body and its components are all immediate children of the
sky node. So the scene rendered its content as follows:
The scene renders itself, clearing its contents to its background color.
The scene renders the
skynode renders its children, which are the helicopter body and its components, in the order they were added as children.
Maintaining the order of a node’s children can be a lot of work. Instead, you can give each node an explicit height in the scene. You do this by setting a node’s
z property. The z-position is the node’s height relative to its parent node, much as a node’s
position property represents its x and y position relative to parent’s position. So you use the z-position to place a node above or below the parent’s position.
When you take z-positions into account, here is how the node tree is rendered:
Each node’s global z-position is calculated by recursively adding its z-position to its parent's z-position.
Nodes are drawn in order from smallest z-position to largest z-position.
If two nodes share the same z-position, ancestors are rendered first, and siblings are rendered in child order.
As you’ve just seen, SpriteKit uses a deterministic rendering order based on the height nodes and their positions in the node tree. But, because the rendering order is so deterministic, SpriteKit may be unable to apply some rendering optimizations that it might otherwise apply. For example, it might be better if SpriteKit could gather all of the nodes that share the same texture and drawing mode and and draw them with a single drawing pass. To enable these sorts of optimizations, you set the view’s
ignores property to
When you ignore sibling order, SpriteKit uses the graphics hardware to render the nodes so that they appear sorted by z-position. It sorts nodes into a drawing order that reduces the number of draw calls needed to render the scene. But with this optimized drawing order, you cannot predict the rendering order for nodes that share the same height. The rendering order may change each time a new frame is rendered. In many cases, the drawing order of these nodes is not important. For example, if the nodes are at the same height but do not overlap on screen, they can be drawn in any order.
Figure 4 shows an example of a tree that uses z-positions to determine the rendering order. In this example, the body of the helicopter is at a height of 100, and its children are rendered relative to its height. The two rotor nodes share the same height but do not overlap.
Because a child node's z-position is added to its parent's z-position, you can interleave child nodes from different parent nodes. Listing 3 shows code that creates two square parent nodes, each with a circular child node. The first node has a z-position of 10 and its child node has a z-position of 10. The second node has a z-position of 15 and its child node also has a z-position of 10.
Once added to the scene, the child of the first node has an effective global z-position of 20 and the child of the second node has an effective global z-position of 25 giving an interleaving effect.
Figure 5 shows the resulting scene.
To summarize, you can use both tree order and z-positions to determine your scene’s rendering order. When rendering a complex scene, you should disable the sorting behavior and use the z-positions of nodes to create a deterministic scene order.
The Hit-Testing Order Is the Reverse of Drawing Order
In a scene, when SpriteKit processes touch or mouse events, it walks the scene to find the closest node that wants to accept the event. If that node doesn’t want the event, SpriteKit checks the next closest node, and so on. The order in which hit-testing is processed is essentially the reverse of drawing order.
For a node to be considered during hit-testing, its
is property must be set to a
true. The default value is a
false for any node except a scene node. A node that wants to receive events needs to implement the appropriate responder methods from its parent class (
UIResponder on iOS and
NSResponder on macOS). This is one of the few places where you must implement platform-specific code in SpriteKit.
Sometimes, you also want to look for nodes directly, rather than relying on the standard event-handling mechanisms. In SpriteKit you can ask a node whether any of its descendants intersect a specific point in their coordinate system. Call the
at method to find the first descendant that intersects the point, or use the
nodes(at:) method to receive an array of all of the nodes that intersect the point.
Using a Node’s Depth to Add Other Effects
SpriteKit uses the
z value only to determine the hit testing and drawing order. You can also the z position to implement your own game effects. For example, you might use the height of a node to determine how it is rendered or how it moves onscreen. In this way, you can simulate fog or parallax effects. SpriteKit does not create these effects for you. Usually, you implement them by processing the scene immediately before it is rendered.
Searching the Node Tree
The nodes in the scene tree are often organized to determine the precise rendering order for the scene, not the role these nodes play in your scene. In addition, you may also be loading nodes from an archive file rather than instantiating them directly at runtime. Because of this, you may need to be able to find specific nodes within a node tree. To do this, you provide names to nodes and then search for those names.
name property holds a node’s name. The name should be an alphanumeric string without any punctuation. Listing 4 shows how you might name three different nodes to distinguish them from each other.
When you name nodes in the tree, decide whether those names will be unique. If a node’s name is unique, you should never include more than one node with that name in the scene tree. On the other hand, if a node name is not unique within your game, it might represent a collection of related nodes. For example, in Listing 4, there are probably multiple goblins within the game, and you might want to identify them all with the same name. But the player might be a unique node within the game.
The node name usually serves two purposes in your app:
You can write your own code that implements game logic based on the node’s name. For example, when two physics objects collide, you might use the node names to determine how the collision affects gameplay.
You can search for nodes that have a particular name. Typically, this is done once when a scene is first loaded.
SKNode class implements the following methods for searching the node tree:
childmethod searches a node’s children until it finds a matching node, then it stops and returns this node. This method is usually used to search for nodes with unique names.
enumeratemethod searches a node’s children and calls your block once for each matching node it finds. You use this method when you want to find all nodes that share the same name.
Child Nodes(with Name: using:)
subscript(_:)method returns an array of nodes that match a particular name.
Listing 5The following code shows how you might create a method on your scene class to find the player node. You might use a method like this inside your code that loads and prepares a scene.
When this method is called on the scene, the scene searches its children (and only its children) for a node whose
name property matches the search string, then returns the node. When specifying a search string, you can either specify the name of the node or a class name. For example, if you create your own subclass for the player node and name it
Player, then you could specify
Player as the search string instead of
player; the same node would be returned.
The default search only searches a node’s children and must exactly match either the node’s name or its class. However, SpriteKit provides an expressive search syntax so that you can perform more advanced searches. For example, you could do the same search as before but search the entire scene tree. Or you could search the node’s children, but match a pattern rather than requiring an exact match.
Table 3 describes the different syntax options. The search uses common regular expression semantics.
When placed at the start of the search string, this indicates that the search should be performed on the tree’s root node. When placed anywhere but the start of the search string, this indicates that the search should move to the node’s children.
When placed at the start of the search string, this specifies that the search should begin at the root node and be performed recursively across the entire node tree. Otherwise, it performs a recursive search from its current position.
Refers to the current node.
The search should move up to the node’s parent.
The search matches zero or more characters.
[characters delimited by commas or dashes]
The search matches any of the characters contained inside the brackets.
The search matches only the specified characters.
Table 4 shows some useful search strings to help get you started.
This search string matches any of the current node’s children that are named
This search string matches any of the root node’s children that are named
This search string matches any nodes in the tree that are named
This search string matches any descendants of the current node that are named
This search string finds the parent nodes of any nodes in the tree that are named
This search string matches every node in the tree.
This search string matches any of the current node’s children that are named
This search string matches any grandchild of the current node when the grandchild is named
This search string matches matches any nodes in the tree that are named
A Node Applies Many of Its Properties to Its Descendants
When you change a node’s property, often the effects are propagated to the node’s descendants. The net effect is that a child is rendered based not only on its own properties but also on the properties of its ancestors.
The node’s coordinate system is scaled by these two factors. This property affects coordinate conversion, the node’s frame, drawing, and hit testing. Its descendants are similarly scaled.
The node’s coordinate system is rotated. This property affects coordinate conversion, the node’s frame, drawing, and hit testing. Its descendants are similarly scaled.
If the node is rendered using a blend mode, the alpha value is multiplied into any alpha value before the blend operation takes place. The descendants are similarly affected.
If a node is hidden, the node and its descendants are not rendered.
The speed at which a node processes actions is multiplied by this value. The descendants are similarly affected.
Converting Between Coordinate Spaces
When working with the node tree, sometimes you need to convert a position from one coordinate space to another. For example, when specifying joints in the physics system, the joint positions are specified in scene coordinates. So, if you have those points in a local coordinate system, you need to convert them to the scene’s coordinate space.
The following code shows how to convert a node’s position into the scene coordinate system. The scene is asked to perform the conversion. Remember that a node’s position is specified in its parent’s coordinate system, so the code passes
node as the node to convert from. You could perform the same conversion in reverse by calling the
One situation where you need to perform coordinate conversions is when you perform event handling. Mouse and touch events need to be converted from window coordinates to view coordinates, and from there into the scene. To simplify the code you need to write, SpriteKit adds a few convenience methods:
Unlike views, you cannot create
SKNode subclasses that perform custom drawing. To implement this kind of behavior you need to work with existing classes in SpriteKit. For example, you might create a hierarchy of nodes that each perform some of the rendering behavior you need, or you might use node classes that support custom graphics shaders and implement your graphical effects in a shader.
In many cases, expect to add methods that can be called during the scene’s pre-processing and post-processing steps. Your scene coordinates these steps, but focused node subclasses perform the work.
In some game designs, you can rely on the fact that a particular combination of classes is always going to be used together in a specific scene. In other designs, you may want to create classes that can be used in multiple scenes. When two classes are dependent on each other, use delegation to break that dependency. Most often, you do this by defining a delegate on your node and a protocol for delegates to implement. Your scene (or another node, such as the node’s parent) implements this protocol. Your node class can then be reused in multiple scenes, without needing to know the scene’s class.
Keep in mind that when a node class is being initialized, it is not yet in the scene, so its
scene properties are
nil. You may need to defer some initialization work until after the node is added to the scene.