Search code examples
swiftsprite-kithierarchyskspritenodezposition

How to handle multiple sprites' zPositions as they move around each other?


I'm new to programming in Swift. I'm trying to create a game where I have a bunch of sprites that move randomly around the screen. Let's say I have a bunch of simple 2D image sprites. As a sprite move upwards (as in y position is increasing) I want it to appear to move further back behind other sprites, so I want the zPosition to decrease in comparison to the other sprites. How do games handle this? What if I have 100 sprites on the screen that are all moving around randomly? I don't really even have a general idea of how this is handled, so any help would be much appreciated.

Second more advanced concept. So let's say each sprite has multiple layers of child sprites, like arms, legs, clothes, weapons, etc. (My sprites are highly customizable.) Then each sprite walks around randomly and needs to appear in the correct zPosition as they move behind other sprites. How would I make sure all the arms, legs, clothes, and other layers of sprites appear correctly in front or behind every other sprite? What if I have a sprite with 20 layers of sprite children and it needs to move seamlessly in front and behind other sprites with 20 sprite children? This might be the wrong direction, but can I somehow make one sprite and its children layers into 1 layer (or make all the left arm sprites into 1 layer and the right arm sprites into another layer)? I see games that handle this, but what's the concept behind how this is handled? (One game I see that kind of does what I want is Fallout Shelter, where each person has arms, legs, weapons, etc and they walk in front of or behind other people and all the sprites appear in the correct manner, but unlike Fallout Shelter my game has completely flat looking 2D imagery and that's how I want it to stay.)

Third even more advanced concept. What if I wanted these sprites to interact like one sprite's arms grab around another sprite (so one arm will be behind a second sprite person, but the opposite arm will be completely in front of the other sprite person)? How are the layers handled to appear in the correct zPosition? Maybe this is not possible? Okay I'm done. Sorry for putting a million questions into one post. I'm looking for the concept behind these questions so I can start planning and programming. Thanks for any help.


Solution

  • 1) As a sprite move upwards (as in y position is increasing) I want it to appear to move further back behind other sprites, so I want the zPosition to decrease in comparison to the other sprites. How do games handle this?

    This can simply be done by creating a custom class (I would not recommend extending this) and overriding the zPosition to be the inverse of Y

    E.G.

    class Player : SKSpriteNode
    {
    
        override var zPosition : CGFloat
        {
            get
            { 
                 //Yes it looks weird to be setting zPosition on get, but we want to ensure the variable storing zPosition is also changed in case any property reads it
                 super.zPosition = -super.position.y
                 return super.zPosition
            }
            set
            {
                //do nothing
            }
        }
    }
    

    2)What if I have 100 sprites on the screen that are all moving around randomly?

    Overriding this will handle every sprite on the screen (that is of type 'Player' in my example) the y position determines the zPosition

    3)How would I make sure all the arms, legs, clothes, and other layers of sprites appear correctly in front or behind every other sprite? Now this gets a little tricky. zPosition is relative to its parent, so you would need to account for how many layers deep your sprite can get zPosition wise, and plan accordingly. Say we have a sprite with shirt pants and a belt.

    If we structured the sprite like this:
    body
    ->shirt
    ->pants
    ->belt

    Then we can set all the zPositions to 0 of those child nodes, because the rendering order would use the zPosition followed by tree order, and nothing would change:

    But if we structured it like this: body
    ->shirt
    ->pants
    --->belt

    and we want to give the affect of the shirt being between the pants and belt, then we would need to compensate by setting a zPosition of 1 to both shirt and belt.

    4)What if I have a sprite with 20 layers of sprite children and it needs to move seamlessly in front and behind other sprites with 20 sprite children? Now we have a problem with battling other sprites, so we need to apply some math to our zPosition on the Player to fix it. class Player : SKSpriteNode {

        override var zPosition : CGFloat
        {
            get
            { 
                 //Yes it looks weird to be setting zPosition on get, but we want to ensure the variable storing zPosition is also changed in case any property reads it
                 super.zPosition = -super.position.y * 2 //(We need at least 2 zPosition slots reserved for every sprite)
                 return super.zPosition
            }
            set
            {
                //do nothing
            }
        }
    }
    

    5)But can I somehow make one sprite and its children layers into 1 layer (or make all the left arm sprites into 1 layer and the right arm sprites into another layer)? 6) I see games that handle this, but what's the concept behind how this is handled? Yes, you can use view.texture(from:) to convert any node to a texture, then create a new sprite out of the texture.

    7)What if I wanted these sprites to interact like one sprite's arms grab around another sprite (so one arm will be behind a second sprite person, but the opposite arm will be completely in front of the other sprite person 8)How are the layers handled to appear in the correct zPosition 9)Maybe this is not possible?

    This part now gets tricky, instead of doing the multiplication, we would need every nodes zPosition to change.

    We also would need to calculate the absolute position of every sprite to know where we are on the y axis

    In order to do that, we have at least 3 options,
    a)we can make a custom protocol + custom classes for every node we place on the scene b) can write extensions for the the node types we place on scene (e.g. SKSpriteNode and SKShapeNode) with the exception of SKNode because that is the base node class c) we can do something called Key Value Observation, and we need to listen to when the position changes on a sprite. The reason why we need to do KVO is because your sprite may be structured where you have SKNodes, and we can't override position on this.

    All three of these methods would require extensive work and would require optimizations unique to your game environment to run efficiently, which i am not going to write in detail how to do this.