Search code examples
swiftsprite-kitsknode

How to put objects on different Z layers in SpriteKit


In my game I am trying to put images on different layers so that it is clear where things are.

E.g. Terrain is at Z layer 0, while improvements and cities are at Z layer 1, while units are at a higher layer.

I am following a tutorial (http://helpmecodeswift.com/sprite-kit/spritekit-gameift-2-0-part-2) and am copying their method (changing the names to fit my needs) and it does not seem to work.

Here are two images from the exact same code (I want the one on the right, although the red city should have a unit on top of it too):

Units under cities

Blue unit above city, Red unit still under city

Question (not topic question): Why won't these images load?

Note: The large squares of color are "cities", while the checker looking pieces are units, the graphics are just filler until I finish off most of the game and actually make dedicated textures.

Here is the code that deals with the layers (units and cities are being put on the right layers):

class GameScene: SKScene {

    var mapTerrain: SKTileMapNode!

    override func sceneDidLoad() {
        cam = SKCameraNode()
        cam.xScale = 1
        cam.yScale = 1
        self.camera = cam
        self.addChild(cam)
        cam.position = CGPoint(x: 100, y: 100)
        setUpLayers()
        loadSceneNodes()
        setUpUI()
        testSpawn()
        //print("\(self.frame.width), \(self.frame.height)")
    }

    func setUpUI(){
        zoomIn.setButtonType(NSMomentaryLightButton)
        zoomIn.title = "Zoom In"
        zoomIn.alternateTitle = "Zoom In"
        zoomIn.acceptsTouchEvents = true
        view?.addSubview(zoomIn) //put button on screen

        zoomOut.setButtonType(NSMomentaryLightButton)
        zoomOut.title = "Zoom Out"
        zoomOut.alternateTitle = "Zoom Out"
        zoomOut.acceptsTouchEvents = true
        view?.addSubview(zoomOut) //put button on screen

        endTurn.setButtonType(NSMomentaryLightButton)
        endTurn.title = "End Turn"
        endTurn.alternateTitle = "End Turn"
        endTurn.acceptsTouchEvents = true
        view?.addSubview(endTurn) //put button on screen

    }

    func loadSceneNodes(){
        guard let mapTerrain = childNode(withName: "mapTerrain")
            as? SKTileMapNode else{
            fatalError("Background node not loaded")
        }
        self.mapTerrain = mapTerrain
        //GKGridGraph
    }

    func setUpLayers() {
        improvementsLayer = SKNode()
        improvementsLayer.name = "Objects Layer"
        addChild(improvementsLayer)
        unitsLayer = SKNode()
        unitsLayer.name = "Units Layer"
        addChild(unitsLayer)
    }

    func testSpawn(){
        if spawnCount == 0{
            for i in 0...10{
                spawnRedLegion(at: mapTerrain.centerOfTile(atColumn: 2, row: i), i: i)
            }
            for i in 0...10{
                spawnBlueLegion(at: mapTerrain.centerOfTile(atColumn: 3, row: i), i: i)
            }
            spawnCount += 1
            for i in 0...0{
                spawnRedCity(at: mapTerrain.centerOfTile(atColumn: 0, row: 1), i: i)
                spawnBlueCity(at: mapTerrain.centerOfTile(atColumn: 5, row: 1), i: i) 
            }
        }
    }

    func spawnRedLegion(at: CGPoint, i: Int){
        let RedLegion = legion(texture: textureRedLegion, moveTo: nil, tag: i, health: 2)
        RedLegion.position = at
        RedLegion.team = "Red"
        unitsLayer.addChild(RedLegion)
        legionList.append(RedLegion)
    }

    func spawnBlueLegion(at: CGPoint, i: Int){
        let BlueLegion = legion(texture: textureBlueLegion, moveTo: nil, tag: i, health: 2)
        BlueLegion.position = at
        BlueLegion.team = "Blue"
        unitsLayer.addChild(BlueLegion)
        legionList.append(BlueLegion)
    }

    func spawnRedCity(at: CGPoint, i: Int){
        let RedCity = city(texture: textureRedCity, tag: i, health: 1, production: 1, workDone: 0)
        RedCity.position = at
        RedCity.team = "Red"
        improvementsLayer.addChild(RedCity)
        cityList.append(RedCity)
    }

    func spawnBlueCity(at: CGPoint, i: Int){
        let BlueCity = city(texture: textureBlueCity, tag: i, health: 1, production: 1, workDone: 0)
        BlueCity.position = at
        BlueCity.team = "Blue"
        improvementsLayer.addChild(BlueCity)
        cityList.append(BlueCity)
    }
    ....
}

//this is in another file

let textureRedLegion = SKTexture(imageNamed: "Red Checker")
let textureBlueLegion = SKTexture(imageNamed: "Black Checker")
let textureRedCity = SKTexture(imageNamed: "Red Square")
let textureBlueCity = SKTexture(imageNamed: "Black Square")

struct layers {

    static let background: CGFloat = 0
    static let improvements: CGFloat = 1
    static let units: CGFloat = 3

}

var improvementsLayer: SKNode!
var unitsLayer: SKNode!

Solution

  • this is how I do it

    The gameLayer is setup in an SKS file that is why it is initialized differently than the controlsLayer

    enum Layer: CGFloat {
        case background = 0
        case gameLayer = 20
        case controls = 100
    }
    
    class GameScene: SKScene, SKPhysicsContactDelegate {
    
        private var gameLayer = SKNode()
        private var controlsLayer = SKNode()
    
        override func didMove(to view: SKView) {
    
            if let gameLayer = self.childNode(withName: "gameLayer") {
                self.gameLayer = gameLayer
                self.gameLayer.zPosition = Layer.gameLayer.rawValue
            }
    
            controlsLayer.zPosition = Layer.controls.rawValue
            addChild(controlsLayer)
    
            setupObjects()
        }
    
        func setupObjects() {
    
            let pauseButton = SKSpriteNode(texture: nil, color: .red, size: CGSize(width: 100, height: 100)
            pauseButton.position = CGPoint(x: 100, y: 100)
            controlsLayer.addChild(pauseButton)
    
            //OR USE IT THIS WAY
    
            let ball = SKSpriteNode(texture: nil, color: .red, size: CGSize(width: 100, height: 100)
            ball.position = CGPoint(x: 400, y: 400)
            ball.zPosition = Layer.gameLayer.rawValue
            self.addChild(ball)
        }
    }