Jozemite Apps Jozemite Apps - 3 months ago 14
Swift Question

Why are objects in the same SKNode layer not interacting with each other?

I have less than 1 year using SpriteKit so I didn't use SKNodes as layers before until recently.

I have an SKNode layer that holds all of the fish and the user's position, for example:

var layerMainGame = SKNode()
layerMainGame.zPosition = 50
layerMainGame.addChild(userPosition)
layerMainGame.addChild(pipFish)
addChild(layerMainGame)


The interaction whether the user touched a fish or not is handled with this function, which is basically checking if their frames crossed:

if CGRectIntersectsRect(CGRectInset(node.frame, delta.dx, delta.dy), self.userPosition.frame) {
print("You got hit by \(name).")
gameOver()
}


It works. The interaction between the userPosition and pipFish works. What doesn't work is fish that are added as the game progresses. I have a function spawning different types of fish in intervals like this:

func spawnNew(fish: SKSpriteNode) {
layerMainGame.addChild(fish)
}


The interaction between the user and those fish that get added to the same layer later in the game does not work. I can pass right through them and no game over happens. When I completely remove the entire layerMainGame variable and just add them to the scene like normal, all the interactions work. Adding them all to the same SKNode layer doesn't work.

This is the function that creates a hit collision for every fish.

func createHitCollisionFor(name: String, GameOver gameOver: String!, delta: (dx: CGFloat, dy: CGFloat), index: Int = -1) {
enumerateChildNodesWithName(name) { [unowned me = self] node, _ in
if CGRectIntersectsRect(CGRectInset(node.frame, delta.dx, delta.dy), self.userPosition.frame) {
me.gameOverImage.texture = SKTexture(imageNamed: gameOver)
didGetHitActions()
me.runAction(audio.playSound(hit)!)
if index != -1 {
me.trophySet.encounterTrophy.didEncounter[index] = true
}
print("You got hit by \(name).")
}
}
}


And I call it like this:

createHitCollisionFor("GoldPiranha", GameOver: model.gameOverImage["Gold"], delta: (dx: 50, dy: 50), index: 1)


It works when the fish are not in the layer, but doesn't work when they are added to the layer.

Answer

When a node is placed in the node tree, its position property places it within a coordinate system provided by its parent.

Sprite Kit uses a coordinate orientation that starts from the bottom left corner of the screen (0, 0), and the x and y values increase as you move up and to the right.

  • For SKScene, the default value of the origin – anchorPoint is (0, 0), which corresponds to the lower-left corner of the view’s frame rectangle. To change it to center you can specify (0.5, 0.5)

  • For SKNode, the coordinate system origin is defined by its anchorPoint which by default is (0.5, 0.5) which is center of the node.

In your project you have layerMainGame added for example to the scene, his anchorPoint is by default (0.5,0.5) so the origin for the children like your fish is the center, you can see it if you change the fish positions like:

func spawnNew(fish: SKSpriteNode) {
    layerMainGame.addChild(fish)
    fish.position = CGPointZero // position 0,0 = parent center 
}

Hope it help to understand how to solve your issue.


Update: (after your changes to the main question)

To help you better understand what happens I will give an example right away:

override func didMoveToView(view: SKView) {
        var layerMainGame = SKNode()
        addChild(layerMainGame)

        let pipFish = SKSpriteNode(color: UIColor.yellowColor(), size: CGSizeMake(50,50))
        pipFish.name = "son"
        self.addChild(pipFish)

        let layerPipFish = SKSpriteNode(color: UIColor.yellowColor(), size: CGSizeMake(50,50))
        layerPipFish.name = "son"
        layerMainGame.addChild(layerPipFish)

        enumerateChildNodesWithName("son") { [unowned me = self] node, _ in
            print(node)
        }
}

Output:

enter image description here

Now I will simply change the line:

layerMainGame.addChild(layerPipFish)

with:

self.addChild(layerPipFish)

Output:

enter image description here

What happened?

As you can see enumerateChildNodesWithName written as your and my code print only childs directly added to self (because actually we launch enumerateChildNodesWithName which it is equal to launch self.enumerateChildNodesWithName )

How can I search in the full node tree?

If you have a node named "GoldPiranha" then you can search through all descendants by putting a // before the name. So you would search for "//GoldPiranha":

enumerateChildNodesWithName("//GoldPiranha") { [unowned me = self] ...