0

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()
    }
Khoi Le
  • 3
  • 4
  • Ok so basically, SpriteKit complies to a parent-child structure. Each node is the child of the node that you attached it to. You should have a universal node at the very tippy top. Attach another node for the scene, and a separate node for the UI to that parent node. Only attach UI elements to that UI Node. The scene elements (IE character, the world, etc.) should be attached to the world node. Just move the world node instead of the universal node and you should be good to go. – E. Huckabee May 25 '20 at 03:46
  • So right now i am defining the SKSpriteNode just right below class GameScene. It should be world node already, right? – Khoi Le May 25 '20 at 04:25
  • By default, the SKScene is the top-level node. You need to add two more SKNodes and attach them to the SKScene. Don't attach anything to your SKScene. Define two more nodes like so: `let WorldNode, UINode = SKNode;` and then on the next line (or wherever you want) do `self.addchild(WorldNode); self.addChild(UINode);` then you want to do `UINode.addChild(button);` instead of `self.addChild(button);` – E. Huckabee May 25 '20 at 16:39
  • My apologize for being a novice here, but i declared "button" as an SKNode already, in which it contains 4 childrens "moveup", "movedown", "moveright", "moveleft" spritenode. So my "button" SKNode should work the same as the UINode. And for the WorldNode, what is its children? Can you give me some sample code? – Khoi Le May 25 '20 at 17:17
  • I'll write an answer so I can explain better. – E. Huckabee May 25 '20 at 17:29
  • Thanks Huckabee, i appreciate it! – Khoi Le May 25 '20 at 17:39
  • If my post correctly helped you solve your problem, then mark my post as the answer to your question so that future viewers can see what helped you solve your problem. – E. Huckabee May 29 '20 at 03:00

1 Answers1

0

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.

E. Huckabee
  • 1,788
  • 1
  • 13
  • 29
  • If that the case, why do we have to add the Map Node? Can we just apply the movement to TileMap only? – Khoi Le May 26 '20 at 02:12
  • The Map Node is to handle all map elements. If you ever plan on adding other elements like animals, structures, (I don't know the nature of your game) then the map node can be used to handle all of that. Assuming that all the map elements would want to appear to be "on the ground" then the same positional movement needs to be applied to all the map elements. A simple movement decision tree can handle movement inside the Map Node when you create enemies or NPC-type sprites. – E. Huckabee May 26 '20 at 17:07