Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon

Nodes

Save for later
  • 18 min read
  • 19 Aug 2015

article-image

In this article by Samanyu Chopra, author of the book iOS Game Development By Example, we will study about nodes, which play an important role in understanding the tree structure of a game. Further, we will discuss about types of nodes in the Sprite Kit and their uses in detail.

(For more resources related to this topic, see here.)

All you need to know about nodes

We have discussed many things about nodes so far. Almost everything you are making in a game with Sprite Kit is a node. Scenes that we are presenting to view are instances of the SKScene class, which is a subclass of the SKEffectNode class, which is itself a subclass of the SKNode class. Indirectly, SKScene is a subclass of the SKNode class.

As a game follows the node tree formation, a scene acts like a root node and the other nodes are used as its children. It should be remembered that although SKNode is a base class for the node you see in a scene, it itself does not draw anything. It only provides some basic features to its subclass nodes. All the visual content we see in a Sprite Kit made game, is drawn by using the appropriate SKNode subclasses.

Following are some subclasses of SKNode classes, which are used for different behaviors in a Sprite Kit-based game:

  • SKSpriteNode: This class is used to instantiate a texture sprite in the game;SKVideoNode, this class is used to play video content in a scene.
  • SKLabelNode: This class is used to draw labels in a game, with many customizing options, such as font type, font size, font color, and so on.
  • SKShapeNode: This class is used to make a shape based on a path, at run time. For example, drawing a line or making a drawing game.
  • SKEmitterNode: This class is used for emitting particle effects in scene, with many options, such as position, number of particles, color, and so on.
  • SKCropNode: This class is basically used for cropping its child nodes, using a mask. Using this, you can selectively block areas of a layer.
  • SKEffectNode: SKEffectNode is the parent of the SKScene class and the subclass of the SKNode class. It is used for applying image filter to its children.
  • SKLightNode: SKLightNode class is used to make light and shadow effects in scene.
  • SKFieldNode: This is a useful feature of Sprite Kit. You can define a portion of scene with some physical properties, for example, in space game, having a gravity effect on a blackhole, which attracts the things which are nearby.

So, these are the basic subclasses of SKNode which are used frequently in Sprite Kit. SKNode provides some basic properties to its subclasses, which are used to view a node inside a scene, such as:

  • position: This sets up the position of a node in a scene
  • xScale: This scales in the width of a node
  • yScale: This scales in the height of a node
  • zRotation: This facilitates the rotation of a node in a clockwise or anti-clockwise direction
  • frame: Node content bounding rectangle without accounting its children

We know that the SKNode class does not draw anything by itself. So, what is the use of if? Well, we can use SKNode instances to manage our other nodes in different layers separately, or we can use them to manage different nodes in the same layer. Let's take a look at how we can do this.

Using the SKNode object in the game

Now, we will discover what the various aspects of SKNode are used for. Say you have to make a body from different parts of sprites, like a car. You can make it from sprites of wheels and body. The wheels and body of a car run in synchronization with each other, so that one control their action together, rather than manage each part separately. This can be done by adding them as a child of the SKNode class object and updating this node to control the activity of the car.

The SKNode class object can be used for layering purposes in a game. Suppose we have three layers in our game: the foreground layer, which represents foreground sprites, the middle layer, which represents middle sprites, and the background layer which represents background sprites.

If we want a parallax effect in our game, we will have to update each sprite's position separately or we can make three SKNode objects, referring to each layer, and add the sprites to their respective nodes. Now we have to update only these three nodes' position and the sprites will update their position automatically.

The SKNode class can be used to make some kind of check point in a game, which is hidden but performs or triggers some event when a player crosses them, such as level end, bonus, or death trap.

We can remove or add the whole sub tree inside a node and perform the necessary functions, such as rotating, scaling, positioning, and so on.

Well, as we described that we can use the SKNode object as checkpoints in the game, it is important to recognize them in your scene. So, how we do that? Well the SKNode class provides a property for this. Let's find out more about it.

Recognition of a node

The SKNode class provides a property with a name, to recognize the correct node. It takes string as a parameter. Either you can search a node by its name or can use one of the two methods provided by SKNode, which are as follows:

  • func childNodeWithName(name:String) -> SKNode: This function takes the name string as a parameter, and if it finds a node with a specific name, it returns that node or else it returns nil. If there is more than one node sharing the same name, it will return the first node in the search.
  • func enumerateChildNodesWithName(name:String, usingBlock:((SKNode!,UnsafeMutablePointer<ObjCBool>)->Void)!): When you need all the nodes sharing the same name, use this function. This function takes the name and block as a parameter. In usingBlock, you need to provide two parameters. One matching node, and the other a pointer of type Boolean. In our game, if you remember, we used the name property inside PlayButton to recognize the node when a user taps on it. It's a very useful property to search for the desired node.

So, let's have a quick look at other properties or methods of the SKNode class.

Initializing a node

There are two initializers to make an instance of SKNode. Both are available in iOS 8.0 or later.

  • convenience init (fileNamed filename: String): This initializer is used for making a node by loading an archive file from main bundle. For this, you have to pass a file name with an sks extension in the main bundle.
  • init(): It is used to make a simple node without any parameter. It is useful for layering purposes in a game.

As we already discussed the positioning of a node, let's discuss some functions and properties that are used to build a node tree.

Building a node tree

SKNode provides some functions and properties to work with a node tree. Following are some of the functions:

  • addChild(node:SKNode): This is a very common function and is used mostly to make a node tree structure. We already used it to add nodes to scenes.
  • insertChild(node:SKNode,atIndex index: Int): This is used when you have to insert a child in a specific position in the array.
  • removeFromParent(): This simply removes a node from its parent.
  • removeAllChildren(): It is used when you have to clear all the children in a node.
  • removeChildrenInArray(nodes:[AnyObject]!): It take an array of SKNode objects and removes it from the receiving node.
  • inParentHierarchy(parent:SKNode) -> Bool: It takes an SKNode object to check as a parent of the receiving node, and returns a Boolean value according to that condition.

There are some useful properties used in a node tree, as follows:

  • children: This is a read only property. It contains the receiving node's children in the array.
  • parent: This is also a read only property. It contain the reference of the parent of the receiving node, and if there is none, then it returns nil.
  • scene: This too is a read only property. If the node is embedded in the scene, it will contain the reference of the scene, otherwise nil.

In a game, we need some specific task on a node, such as changing its position from one point to another, changing sprites in a sequence, and so on. These tasks are done using actions on node. Let's talk about them now.

Actions on a node tree

Actions are required for some specific tasks in a game. For this, the SKNode class provides some basic functions, which are as follows.

  • runAction(action:SKAction!): This function takes an SKAction class object as a parameter and performs the action on the receiving node.
  • runAction(action:SKAction!,completion block: (() -> Void)!): This function takes an SKAction class object and a compilation block as object. When the action completes, it calls the block.
  •  runAction(action:SKAction,withKey key:String!): This function takes an SKAction class object and a unique key, to identify this action and perform it on the receiving node.
  • actionForKey(key:String) -> SKAction?: This takes a String key as a parameter and returns an associative SKAction object for that key identifier. This happens if it exists, otherwise it returns nil.
  • hasActions() -> Bool: Through this action, if the node has any executing action, it returns true, or else false.
  • removeAllActions(): This function removes all actions from the receiving node.
  • removeActionForKey(key:String): This takes String name as key and removes an action associated with that key, if it exists.

Some useful properties to control these actions are as follows:

  • speed: This is used to speed up or speed down the action motion. The default value is 1.0 to run at normal speed; with increasing value, speed increases.
  • paused: This Boolean value determines whether an action on the node should be paused or resumed.

Sometimes, we require changing a point coordinate system according to a node inside a scene. The SKNode class provides two functions to interchange a point's coordinate system with respect to a node in a scene. Let's talk about them.

Coordinate system of a node

We can convert a point with respect to the coordinate system of any node tree. The functions to do that, are as follows:

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at €18.99/month. Cancel anytime
  • convertPoint(point:CGPoint, fromNode node : SKNode) -> CGPoint: This takes a point in another node's coordinate system and the other node as its parameter, and returns a converted point according to the receiving node's coordinate system.
  • convertPoint(point:CGPoint, toNode node:SKNode) ->CGPoint: It takes a point in the receiving node's coordinate system and the other nodes in the node tree as its parameters, and returns the same point converted according to the other node's coordinate system.

We can also determine if a point is inside a node's area or not.

  • containsPoint(p:CGPoint) -> Bool: This returns the Boolean value according to the position of a point inside or outside of a receiving node's bounding box.
  • nodeAtPoint(p:CGPoint) -> SKNode: This returns the deepest descendant node that intersects the point. If that is not there, then it returns the receiver node.
  • nodesAtPoint(p:CGPoint) -> [AnyObject]: This returns an array of all the SKNode objects in the subtree that intersect the point. If no nodes intersect the point, an empty array is returned.

Apart from these, the SKNode class provides some other functions and properties too. Let's talk about them.

Other functions and properties

Some other functions and properties of the SKNode class are as follows:

  • intersectsNode(node:SKNode) -> Bool: As the name suggest, it returns a Boolean value according to the intersection of the receiving node and another node from the function parameter.
  • physicsBody: It is a property of the SKNode class. The default value is nil, which means that this node will not take part in any physical simulation in the scene. If it contains any physical body, then it will change its position and rotation in accordance with the physical simulation in the scene.
  • userData : NSMutableDictionary?: The userData property is used to store data for a node in a dictionary form. We can store position, rotation, and many custom data sets about the node inside it.
  • constraints: [AnyObject]?: It contains an array of constraints SKConstraint objects to the receiving node. Constraints are used to limit the position or rotation of a node inside a scene.
  • reachConstraints: SKReachConstraints?: This is basically used to make restricted values for the receiving node by making an SKReachConstraints object. For example, to make joints move in a human body.
  • Node blending modes: The SKNode class declares an enum SKBlendMode of the int type to blend the receiving node's color by using source and destination pixel colors. The constant’s used for this are as follows:
    • Alpha: It is used to blend source and destination colors by multiplying the source alpha value
    • Add: It is used to add the source and destination colors
    • Subtract: It is used to subtract the source color from the destination color
    • Multiply: It is used to multiply the source color by the destination color
    • MultiplyX2: It is used to multiply the source color by the destination color, and after that, the resulting color is doubled
    • Screen: It is used to multiply the inverted source and the destination color respectively and it then inverts the final result color
    • Replace: It is used to replace the destination color by source color
  • calculateAccumulatedFrame()->CGRect: We know that a node does not draw anything by itself, but if a node has descendants that draw content, then we may be required to know the overall frame size of that node. This function calculates the frame that contains the content of the receiver node and all of its descendants.

Now, we are ready to see some basic SKNode subclasses in action. The classes we are going to discuss are as follows:

  • SKLabelNode
  • SKCropNode
  • SKShapeNode
  • SKEmitterNode
  • SKLightNode
  • SKVideoNode

To study these classes, we are going to create six different SKScene subclasses in our project, so that we can learn them separately.

Now, having learned in detail about nodes, we can proceed further to utilize the concept of nodes in a game.

Creating subclasses for our Platformer game

With the theoretical understanding of nodes, one wonders how this concept is helpful in developing a game. To understand the development of a game using the concept of Nodes, we now go ahead with writing and executing code for our Platformer game.

Create the subclasses of different nodes in Xcode, following the given steps:

  1. From the main menu, select New File | Swift | Save As | NodeMenuScene.swift:

    Make sure Platformer is ticked as the target. Now Create and Open and make the NodeMenuScene class by subclassing SKScene.

  2. Following the previous same steps as, make CropScene, ShapeScene, ParticleScene, LightScene, and VideoNodeScene files, respectively.
  3. Open the GameViewController.swift file and replace the viewDidLoad function by typing out the following code:
override func viewDidLoad() {

        super.viewDidLoad()

 

        let menuscene = NodeMenuScene()

 

        let skview = view as SKView

 

        skview.showsFPS = true

        skview.showsNodeCount = true

        skview.ignoresSiblingOrder = true

        menuscene.scaleMode = .ResizeFill

 

        menuscene.anchorPoint = CGPoint(x: 0.5, y: 0.5)

        menuscene.size = view.bounds.size

        skview.presentScene(menuscene)

 

    }

In this code, we just called our NodeMenuScene class from the GameViewController class. Now, it's time to add some code to the NodeMenuScene class.

NodeMenuScene

Open the NodeMenuScene.swift file and type in the code as shown next. Do not worry about the length of the code; as this code is for creating the node menu screen, most of the functions are similar to creating buttons:

import Foundation

import SpriteKit

 

let BackgroundImage = "BG"

let FontFile = "Mackinaw1"

 

let sKCropNode = "SKCropNode"

 

let sKEmitterNode = "SKEmitterNode"

 

let sKLightNode = "SKLightNode"

let sKShapeNode = "SKShapeNode"

let sKVideoNode = "SKVideoNode"

class NodeMenuScene: SKScene {

 

    let transitionEffect = SKTransition.flipHorizontalWithDuration(1.0)

    var labelNode : SKNode?

    var backgroundNode : SKNode?

 

 

 

    override func didMoveToView(view: SKView) {

        backgroundNode = getBackgroundNode()

        backgroundNode!.zPosition = 0

        self.addChild(backgroundNode!)

        labelNode = getLabelNode()

        labelNode?.zPosition = 1

        self.addChild(labelNode!)

 

 

    }

        func getBackgroundNode() -> SKNode {

        var bgnode = SKNode()

        var bgSprite = SKSpriteNode(imageNamed: "BG")

        bgSprite.xScale = self.size.width/bgSprite.size.width

        bgSprite.yScale = self.size.height/bgSprite.size.height

        bgnode.addChild(bgSprite)

        return bgnode

    }

    func getLabelNode() -> SKNode {

    var labelNode = SKNode()

        var cropnode = SKLabelNode(fontNamed: FontFile)

        cropnode.fontColor = UIColor.whiteColor()

        cropnode.name = sKCropNode

        cropnode.text = sKCropNode

        cropnode.position =
CGPointMake(CGRectGetMinX(self.frame)+cropnode.frame.width/2,
CGRectGetMaxY(self.frame)-cropnode.frame.height)

        labelNode.addChild(cropnode)

        var emitternode = SKLabelNode(fontNamed: FontFile)

        emitternode.fontColor = UIColor.blueColor()

        emitternode.name = sKEmitterNode

        emitternode.text = sKEmitterNode

        emitternode.position =
CGPointMake(CGRectGetMinX(self.frame) + emitternode.frame.width/2
, CGRectGetMidY(self.frame) - emitternode.frame.height/2)

        labelNode.addChild(emitternode)

 

        var lightnode = SKLabelNode(fontNamed: FontFile)

        lightnode.fontColor = UIColor.whiteColor()

        lightnode.name = sKLightNode

        lightnode.text = sKLightNode

        lightnode.position = CGPointMake(CGRectGetMaxX(self.frame)
- lightnode.frame.width/2 , CGRectGetMaxY(self.frame) -
lightnode.frame.height)

        labelNode.addChild(lightnode)

       

        var shapetnode = SKLabelNode(fontNamed: FontFile)

        shapetnode.fontColor = UIColor.greenColor()

        shapetnode.name = sKShapeNode

        shapetnode.text = sKShapeNode

        shapetnode.position =
CGPointMake(CGRectGetMaxX(self.frame) - shapetnode.frame.width/2 ,
CGRectGetMidY(self.frame) - shapetnode.frame.height/2)

        labelNode.addChild(shapetnode)

 

        var videonode = SKLabelNode(fontNamed: FontFile)

        videonode.fontColor = UIColor.blueColor()

        videonode.name = sKVideoNode

        videonode.text = sKVideoNode

        videonode.position = CGPointMake(CGRectGetMaxX(self.frame)
- videonode.frame.width/2 , CGRectGetMinY(self.frame) )

        labelNode.addChild(videonode)

 

        return labelNode

    }

    var once:Bool = true

    override func touchesBegan(touches: NSSet, withEvent event:
    UIEvent) {

        if !once {

            return

        }

        for touch: AnyObject in touches {

            let location = touch.locationInNode(self)

            let node = self.nodeAtPoint(location)

            if node.name == sKCropNode {

                once = false

                var scene = CropScene()

                scene.anchorPoint = CGPointMake(0.5, 0.5)

                scene.scaleMode = .ResizeFill

                scene.size = self.size

                self.view?.presentScene(scene,
                transition:transitionEffect)

            }

 

            else if node.name == sKEmitterNode {

                once = false

                var scene = ParticleScene()

                scene.anchorPoint = CGPointMake(0.5, 0.5)

                scene.scaleMode = .ResizeFill

                scene.size = self.size

                self.view?.presentScene(scene,
                transition:transitionEffect)

            }

            else if node.name == sKLightNode {

                once = false

                var scene = LightScene()

                scene.scaleMode = .ResizeFill

                scene.size = self.size

                scene.anchorPoint = CGPointMake(0.5, 0.5)

                self.view?.presentScene(scene ,
                transition:transitionEffect)

            }

            else if node.name == sKShapeNode {

                once = false

                var scene = ShapeScene()

                scene.scaleMode = .ResizeFill

                scene.size = self.size

 

                scene.anchorPoint = CGPointMake(0.5, 0.5)

                self.view?.presentScene(scene,
                transition:transitionEffect)

            }

            else if node.name == sKVideoNode {

                once = false

                var scene = VideoNodeScene()

                scene.scaleMode = .ResizeFill

                scene.size = self.size

                scene.anchorPoint = CGPointMake(0.5, 0.5)

                self.view?.presentScene(scene ,
                transition:transitionEffect)

            }

        }

    }

}

We will get the following screen from the previous code:

nodes-img-0

The screen is obtained when we execute the NodeMenuScene,swift file

In the preceding code, after import statements, we defined some String variables. We are going to use these variables as Label names in scene .We also added our font name as a string variable. Inside this class, we made two node references: one for background and the other for those labels which we are going to use in this scene. We are using these two nodes to make layers in our game. It is best to categorize the nodes in a scene, so that we can optimize the code. We make an SKTransition object reference of the flip horizontal effect. You can use other transition effects too.

Inside the didMoveToView() function, we just get the node and add it to our scene and set their z position.

Now, if we look at the getBackgroundNode() function, we can see that we made a node by the SKNode class instance, a background by the SKSpriteNode class instance, and then added it to node and returned it. If you see the syntax of this function, you will see -> SKNode. It means that this function returns an SKNode object.

The same goes in the function, getLabelNode(). It also returns a node containing all the SKLabelNode class objects. We have given a font and a name to these labels and set the position of them in the screen. The SKLabelNode class is used to make labels in Sprite Kit with many customizable options.

In the touchBegan() function, we get the information on which Label is touched, and we then call the appropriate scene with transitions.

With this, we have created a scene with the transition effect. By tapping on each button, you can see the transition effect.

d shadows will also change themselves according to the source.

Summary

In this article, we learned about nodes in detail. We discussed many properties and functions of the SKNode class of Sprite Kit, along with its usage. Also, we discussed about the building of a node tree, and actions on a node tree. Now we are familiar with the major subclasses of SKNode, namely SKLabelNode, SKCropNode, SKShapeNode, SKEmitterNode, SKLightNode, and SKVideoNode, along with their implementation in our game.

Resources for Article:


Further resources on this subject: