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!
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
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)
}
}
}
}
}
}
}