SKSpriteNode does not let touches pass through whe

2020-07-25 07:26发布

问题:

I'm attempting to create an overlay in SpriteKit which by using an SKSpriteNode. However, I want the touches to pass through the overlay, so I set isUserInteractionEnabled to false. However, when I do this, the SKSpriteNode still seems to absorb all the touches and does not allow the underlying nodes to do anything.

Here's an example of what I'm talking about:

class 

TouchableSprite: SKShapeNode {
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("Touch began")
    }
}

class GameScene: SKScene {
    override func sceneDidLoad() {
        // Creat touchable
        let touchable = TouchableSprite(circleOfRadius: 100)
        touchable.zPosition = -1
        touchable.fillColor = SKColor.red
        touchable.isUserInteractionEnabled = true
        addChild(touchable)


        // Create overlay
        let overlayNode = SKSpriteNode(imageNamed: "Fade")
        overlayNode.zPosition = 1
        overlayNode.isUserInteractionEnabled = false
        addChild(overlayNode)
    }
}

I've tried subclassing SKSpriteNode and manually delegating the touch events to the appropriate nodes, but that results in a lot of weird behavior.

Any help would be much appreciated. Thanks!

回答1:

You need to be careful with zPosition. It is possible to go behind the scene, making things complicated.

What is worse is the order of touch events. I remember reading that it is based on the node tree, not the zPosition. Disable the scene touch and you should be seeing results.

This is what it says now:

The Hit-Testing Order Is the Reverse of Drawing Order In a scene, when SpriteKit processes touch or mouse events, it walks the scene to find the closest node that wants to accept the event. If that node doesn’t want the event, SpriteKit checks the next closest node, and so on. The order in which hit-testing is processed is essentially the reverse of drawing order.

For a node to be considered during hit-testing, its userInteractionEnabled property must be set to YES. The default value is NO for any node except a scene node. A node that wants to receive events needs to implement the appropriate responder methods from its parent class (UIResponder on iOS and NSResponder on OS X). This is one of the few places where you must implement platform-specific code in SpriteKit.

But this may not have always been the case, so you will need to check between 8 , 9, and 10 for consistency



回答2:

Reading the actual sources you find:

/**
  Controls whether or not the node receives touch events
*/
open var isUserInteractionEnabled: Bool

but this refers to internal node methods of touches. By default isUserInteractionEnabled is false then the touch on a child like your overlay SKSpriteNode is, by default, a simple touch handled to the main (or parent) class (the object is here, exist but if you don't implement any action, you simply touch it)

To go through your overlay with the touch you could implement a code like this below to your GameScene.Remember also to not using -1 as zPosition because means it's below your scene.

P.S. :I've added a name to your sprites to recall it to touchesBegan but you can create global variables to your GameScene:

override func sceneDidLoad() {
        // Creat touchable
        let touchable = TouchableSprite(circleOfRadius: 100)
        touchable.zPosition = 0
        touchable.fillColor = SKColor.red
        touchable.isUserInteractionEnabled = true
        touchable.name = "touchable"
        addChild(touchable)
        // Create overlay
        let overlayNode = SKSpriteNode(imageNamed: "fade")
        overlayNode.zPosition = 1
        overlayNode.name = "overlayNode"
        overlayNode.isUserInteractionEnabled = false
        addChild(overlayNode)
    }
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("GameScene Touch began")
        for t in touches {
            let touchLocation = t.location(in: self)
            if let overlay = self.childNode(withName: "//overlayNode") as? SKSpriteNode {
                if overlay.contains(touchLocation) {
                    print("overlay touched")
                    if let touchable = self.childNode(withName: "//touchable") as? TouchableSprite {
                        if touchable.contains(touchLocation) {
                            touchable.touchesBegan(touches, with: event)
                        }
                    }
                }
            }
        }
    }
}