Search code examples
swiftxcodesprite-kitskspritenodesktilemapnode

SOLVED: Anchor control button when moving the character in tile map?


I am working on a game where the character will be controled by a set of 4 buttons like in the old consoles. I used Tile Map in spritekit to create the map. Everything works fine (the players is moving when pressing the buttons, the scene follow the player) except for the fact that my 4 buttons also move when the character move. Up to the point that the 4 buttons move off screen and i have no control of it anymore. How can we anchor the 4 buttons to, let say, bottom right of the screen? Below is the code i used to create the controlled buttons

func controlButton() {

        button = SKNode()

        moveUpButton = SKSpriteNode(imageNamed: "moveup")
        moveUpButton.alpha = 1
        moveUpButton.setScale(1.5)
        moveUpButton.position = CGPoint(x: 400 - self.frame.size.width/2, y: 0 - self.frame.size.height/2)
        moveUpButton.zPosition = 2

        moveLeftButton = SKSpriteNode(imageNamed: "moveleft")
        ...
        moveRightButton = SKSpriteNode(imageNamed: "moveright")
        ...

        moveDownButton = SKSpriteNode(imageNamed: "movedown")
        ...

        button.addChild(moveUpButton)
        button.addChild(moveLeftButton)
        button.addChild(moveRightButton)
        button.addChild(moveDownButton)
        self.addChild(button)
    }

and here is the code i used to create the tile map, adding node with physics body for tile containing wall:

func setUpSceneWithMap(map: SKTileMapNode) {
        let tileMap = map
        tileMap.setScale(1)
        tileMap.position = CGPoint(x: 0 - self.frame.size.width/2, y: 0 - self.frame.size.height/2)
        let tileSize = tileMap.tileSize
        let halfWidth = CGFloat(tileMap.numberOfColumns) / 2.0 * tileSize.width
        let halfHeight = CGFloat(tileMap.numberOfRows) / 2.0 * tileSize.height

        for col in 0..<tileMap.numberOfColumns {
            for row in 0..<tileMap.numberOfRows {

                let tileDefinition = tileMap.tileDefinition(atColumn: col, row: row)
                let isEdgeTile = tileDefinition?.userData?["isWalls"] as? Bool

                if (isEdgeTile ?? false) {

                    let x = CGFloat(col) * tileSize.width - halfWidth
                    let y = CGFloat(row) * tileSize.height - halfHeight

                    let rect = CGRect(x: 0, y: 0, width: tileSize.width, height: tileSize.height)
                    let tileNode = SKShapeNode(rect: rect)

                    tileNode.position = CGPoint(x: x, y: y)
                    tileNode.physicsBody = SKPhysicsBody.init(rectangleOf: tileSize, center: CGPoint(x: tileSize.width / 2.0, y: tileSize.height / 2.0))
                    tileNode.physicsBody?.categoryBitMask = gamePhysics.Wall
                    tileNode.physicsBody?.collisionBitMask = gamePhysics.Player
                    tileNode.physicsBody?.contactTestBitMask = gamePhysics.Player
                    tileNode.physicsBody?.isDynamic = false

                    tileMap.addChild(tileNode)
                }
            }
        }
    }

Below is also the code where i add the map to scene:

func createScene() {

        self.physicsWorld.contactDelegate = self

        for node in self.children {
            if (node is SKTileMapNode){
                if let theMap:SKTileMapNode = node as? SKTileMapNode {
                    setUpSceneWithMap(map: theMap)
                }
            }

        }
        createPlayer()
        createCamera()
    }

Solution

  • Basically, SpriteKit subscribes to a child-parent node structure.

    Let me use an image to illustrate.

    enter image description here

    Where each circle represents a node. Currently, you are moving the SKScene node (self). The position of the child nodes are relative to the parent node it is attached to. When you move SKScene node, all of the nodes attached to it follow suite because they are the child nodes.

    For example, position a node at (0,0), and then attach a node to that node at position (10,0). Move the parent node to (10,0), and the child node at (10,0) will move to (20,0) because its origin point is relative to its parent, not the general scene.

    To fix your issue, you need to create another level of nodes. Let me use another image to illustrate.

    Image 2

    If you only apply movement to the Map Node, then only the children (nodes attached to Map Node) will move.

    So in summary, your code would look something like this.

    class GameScene: SKScene 
    {
        let MapNode,
            UINode = SKNode; //This is a fancy way to declare multiple variables of the same type
    
        func controlButton() 
        {
            ... //Your other code here
            UINode.addChild(button) //Instead of self.addChild(button)
        }
    
        func setUpSceneWithMap(map: SKTileMapNode)
        {
            //Your other code in here
    
            MapNode.addChild(map) 
            // I don't know where you are adding your tile map to your scene
            // However, it most likely is self.addChild(map) which needs to be changed to MapNode.addChild(map)
        }
    }
    

    Attach all scene elements to MapNode, and all UI elements to UINode. Only apply positional change to MapNode. The UI elements will not move around unless you want them to.