Search code examples
iosswiftsprite-kitpositioncoordinates

Why do I always have to change the position of a sprite to something else before I can get the desired position to work


I personally think this is really really weird.

I was just developing a game using SpriteKit. I created a subclass of SKSpriteNode in order to create a sprite that shows how much time is left in the game, the score, etc. I called it GameBanner.

I am now trying to put an SKLabelNode in the top left corner of banner. I don't know the exact position of it so I wrote this:

timerTitle.position = //CGPointMake(CGRectGetMinX(self.frame) + 
    timerTitle.frame.width / 2 + 20, CGRectGetMaxY(self.frame) - 
    self.frame.height / 2 - 20)
// timerTitle is the label node that shows "Time Left"

But I can only see the banner's background image on the screen, not "Time Left". I think this is because somehow I set the position of timerTitle to be outside of the banner.

Then I thought, well, this is to much trouble for me to figure where it should be, let's "trial and error" this!

I first tried with (0, 0). It puts the label right in the center. Then I tried these:

  1. (-100, 0) - Label not visible ("Maybe it's too far left?" I thought)
  2. (-40, 0) - Label in about the center, a little offset to the left ("If -40 is this little left, then -100 would work!", I thought)
  3. (-100, 0) - Label visible, seems in the correct position. ("Wait a minute! Why the first time it didn't work?", I thought)
  4. (The same thing happens when I tried with (-160, 0) and (-160, 20), i.e. The first time it is not visible, changed it to another value and run it, changed it back to (-160, 0) and it worked)

I hope you can understand my problem with screenshots:

This is when I first set the position to (-160, 20):

enter image description here

Then I changed it to (-160, 0), it shows up:

enter image description here

After that, I changed it back to (-160, 20) and it magically works!

enter image description here

Here is the GameBanner class:

class GameBanner: SKSpriteNode {
    let timerTitle = SKLabelNode(text: "Time Left:")
    let timerLabel = SKLabelNode(text: "60")
    let collectedBlocksLabel = SKLabelNode(text: "Collected Blocks")
    let scoreLabel = SKLabelNode(text: "Score:")

    override init(texture: SKTexture?, color: UIColor, size: CGSize) {
        super.init(texture: texture, color: color, size: size)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    //Call this initializer with Position: (302.08, 860.78), Size: (604.16, 100.0) to reproduce
    convenience init(position: CGPoint, size: CGSize) {
        self.init(texture: SKTexture(imageNamed: "banner"), color: UIColor.clearColor(), size: size)
        self.position = position
        configureLabels(timerTitle, timerLabel, collectedBlocksLabel, scoreLabel)
        // This is the part that doesn't work as I have said above
        timerTitle.position = CGPointMake(-160, 20)

        addChild(timerTitle)
        //addChild(timerLabel)
        //addChild(collectedBlocksLabel)
        //  addChild(scoreLabel)

        zPosition = 10000
    }

    private func configureLabels(labels: SKLabelNode...) {
        for label in labels {
            label.fontColor = UIColor.whiteColor()
            label.fontName = "Chalkduster"
            label.fontSize = 25
        }
    }
}

And this is the background image of the banner. Maybe this matters:

enter image description here


Solution

  • I believe that your issue is related to ignoresSiblingOrder property.

    Currently, you have something like this :

    parent (which is GameBanner) has zPosition set to 10000.

    label (timerTitle) is added to the parent, and has default zPosition set to 0.0, which means, the rendering (global) zPosition will be 10000 + 0 = 10000. Means, it will be rendered sometimes before (below), and sometimes after (above) its parent.

    Try to set zPosition of a label explicitly, to place it above its parent:

     convenience init(position: CGPoint, size: CGSize) {
            //...
            timerTitle.zPosition = 1
           //...
            zPosition = 10000
        }
    

    Now, the global rendering zPositiot will be 10000+1 = 10001, means, that label will be always on top of the banner image.

    About ignoresSiblingOrder property:

    The default value is NO, which means that when multiple nodes share the same z position, those nodes are sorted and rendered in a deterministic order. Parents are rendered before their children, and siblings are rendered in array order. When this property is set to YES, the position of the nodes in the tree is ignored when determining the rendering order. The rendering order of nodes at the same z position is arbitrary and may change every time a new frame is rendered. When sibling and parent order is ignored, SpriteKit applies additional optimizations to improve rendering performance. If you need nodes to be rendered in a specific and deterministic order, you must set the z position of those nodes.

    More about how node tree is rendered:

    Creating The Node Tree