4
votes

I am encountering an issue with the zPosition of nodes on my SpriteKit scene.

I am building a manager node that enables "layer like" management of nodes zPosition, essentially enabling you to get rid of the manual management of the zPosition property and instead to add nodes with methods like addNodeUnder(node:SKNode) and also to create groups.

Here is a test scene I have made :
We can see a blue node, itself under a green node, itself under a yellow node, itself under a red node.

The problem is that with the operations I have made, the green and the yellow node should be on top of the red node.

I made my own little debugger, displaying the SceneKit "scene graph" in hierarchical order :

Node of type LMManagerNode  has zPosition = 0.0 and has children :
           Node of type LMWarpperNode  has zPosition = 0.0 and has children :
                     Node of type SKSpriteNode ('Red node') has zPosition = 0.0.

           Node of type LMWarpperNode  has zPosition = -100.0 and has children :
                     Node of type SKSpriteNode ('Blue node') has zPosition = 0.0.

           Node of type LMWarpperNode  has zPosition = 100.0 and has children :
                     Node of type LMGroupNode ('Test group') has zPosition = 0.0 and has children :
                               Node of type LMWarpperNode  has zPosition = -150.0 and has children :
                                         Node of type SKSpriteNode ('Yellow node') has zPosition = 0.0.

                               Node of type LMWarpperNode  has zPosition = -200.0 and has children :
                                         Node of type SKSpriteNode ('Green node') has zPosition = 0.0.

As you can see :

  • The red node is contained inside a node that has zPosition = 0
  • The blue node is contained inside a node that has zPosition = -100
  • The yellow and green nodes are contained inside a group node, itself contained inside a node that has zPosition = 100

Since the node that contains the red node has zPosition = 0 and the node that contains the green and yellow nodes has zPosition = 100, shouldn't these two nodes be on top of the red node ?

The ignoresSiblingOrder of my SKView is set to false by the way...

EDIT : That's weird : yellowNode.zPosition = 1000 does make the yellow node go on top of the red node. How can it be possible ?

EDIT #2 Here is the code of my debugging function :

func renderNodeHieararchyForNode(node:SKNode, index:Int) {

    var i = 0
    var beginning = ""
    while i != index {
        beginning += "          "
        i++
        if i == index {
            beginning += " "
        }
    }

    print("\(beginning)Node of type \(node.dynamicType) \(node.name != nil ? "('\(node.name!)')" : "") has zPosition = \(node.zPosition)\(node.children.count > 0 ? " and has children :" : ".")")
    for (i,child) in node.children.enumerate() {
        renderNodeHieararchyForNode(child, index: index+1)

        if i == node.children.count-1 {
            print("")
        }
    }

}

func renderNodeHiearchyForNode(node:SKNode) {
    renderNodeHieararchyForNode(node, index: 0)
}

As you can see, I use only the SpriteKit framework methods to display it, so the output of my debugger is definitely correct.

2
I believe things got changed, and the nodes inside the parent nodes no longer respect the zPosition in some way, did you try setting the yellow and green nodes zPosition and not the parents zPosition, and see if it comes on top?Knight0fDragon
That's wired : yellowNode.zPosition = 1000 does make the yellow node go on top of the red node. How can it be possible ?Trevör
Answer? you mean comment, I am currently typing up an answer for youKnight0fDragon
I suggest you read the section entitled Understanding the Drawing Order for a Node Tree0x141E
@TrevörAnneDenise Yeah, I understood that, I just meant that you are allowed to mimic the issue with regular SKNode as a container node, and SKSpriteNode for the sprites instead of using the real code...Whirlwind

2 Answers

4
votes

From Apple's documentation,

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.

and

When you take z positions into account, here is how the node tree is rendered:

  1. Each node’s global z position is calculated.
  2. Nodes are drawn in order from smallest z value to largest z value.
  3. If two nodes share the same z value, ancestors are rendered first, and siblings are rendered in child order.

where

node's global z position = node's z position + node's parent's z position + node's parent's parent's z position + ...

With this formula, the global z positions of your nodes are

red is 0        (0 + 0 + 0)
blue is -100    (0 + -100 + 0)
yellow = -50    (-150 + 0 + 100 + 0)
green = -100    (-200 + 0 + 100 + 0)

From highest to lowest global z position (based on rules 1 and 2), the order in which the nodes appear in the scene is

red
yellow
green / blue

where red appears on top of yellow and yellow is on top of green/blue. Since green and blue have the same global z value, sibling order (rule 3) is used to determine the drawing order. In this case, green's top-level LMWarpperNode node (i.e., its great grandparent) was added to the LMManagerNode node after blue's LMWarpperNode node, so blue is drawn first and green is drawn over blue.

Here's the final (reversed) draw order

red                0
yellow           -50
green           -100
blue            -100 (parent was added to scene before green's great grandparent)
3
votes

Here is how you are expecting your code right now

 -100: Node
     # 0: Node
        # 0: Blue
   0 : Node
     # 0: Node
        # 0:Red
 100 : Node
     # 0: Node
        # -200:Green
        # -150:Yellow

But what is really happening is this

 -100: Node
     : Node
     : Blue
     : Green
 -50 : Yellow
   0 : Node
     : Node
     : Red
 100 : Node
     : Node

This is because the zPosition is just subtracted from the parent, and the screen then draws in 1 pass all the nodes.

To combat this, you need to create a custom class that overrides the addChild function and do somethings that keep track of your zPosition in another variable (probably a dictionary), then set the child zPosition to zero, and use insertChild(node:, atIndex:) to place your children in order in the children array using this new variable to preserve your order. Then if you are going even more crazy, you would have to keep track of when a child changes its zPosition, and use the above style to set it correctly in the array