There are already a few questions out there regarding how to make a button in SpriteKit such as Swift Spritekit Adding Button Programaticly and Setting up buttons in SKScene.
Either way, the solution is to make an SKSpriteNode
with a texture for the button and then in the function touchesEnded
, test to see if the touch falls within the button-SKSpriteNode
.
Interestingly, however, is that if one were to make the button a child to, say a SKCameraNode
, or another SKSpriteNode
, then this method no longer works.
My question is why? and how to overcome this dilemma.
UPDATE:
In regards to the post below here are two alternate version of simple GameScene.swift file. The main difference is that in the first case, sprite2
is not a child of the camera, whereas in the version 2 it is.
Note: In the gifs, notice how on version 1, clicking the purple sprite (sprite2
) causes it to print "You tapped the purple sprite.", whereas it says "blue sprite" in version 2. Thus the issue is made clear:
Tapping the child of another SKNode registers as tapping the uppermost parent node, rather than as the node actually tapped!
New question: How to correct this.
Addendum: in version 2 sprite2
has changed positions a bit due to becoming a child of the camera - as it is an M.W.E. and it doesn't impact this demonstration, I chose not to correct it.
Version 1
class GameScene: SKScene {
var cam: SKCameraNode!
var sprite = SKSpriteNode(imageNamed: "sprite")
var sprite2 = SKSpriteNode(imageNamed: "sprite2")
override func didMove(to view: SKView) {
backgroundColor = SKColor.white
sprite.position = CGPoint(x: size.width/4,y: size.height/2)
sprite2.position = CGPoint(x: size.width/4 * 3,y: size.height/2)
sprite.anchorPoint = CGPoint(x:0.5, y:0.5)
// Set up the camera
cam = SKCameraNode()
self.camera = cam
cam.setScale(3.0)
cam.position = CGPoint(x: size.width/4, y: 0)
sprite.addChild(cam)
addChild(sprite); addChild(sprite2)
}
func touchDown(atPoint pos : CGPoint) {
}
func touchMoved(toPoint pos : CGPoint) {
}
func touchUp(atPoint pos : CGPoint) {
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchMoved(toPoint: t.location(in: self)) }
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if sprite.contains(touchLocation) {
print("You tapped the blue sprite")
}
if sprite2.contains(touchLocation) {
print("You tapped the purple sprite")
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchUp(atPoint: t.location(in: self)) }
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
Version 2
class GameScene: SKScene {
var cam: SKCameraNode!
var sprite = SKSpriteNode(imageNamed: "sprite")
var sprite2 = SKSpriteNode(imageNamed: "sprite2")
override func didMove(to view: SKView) {
backgroundColor = SKColor.white
// Scale Sprites
// sprite.setScale(0.3)
sprite2.setScale(0.3)
sprite.position = CGPoint(x: size.width/4,y: size.height/2)
// sprite2.position = CGPoint(x: size.width/4 * 3,y: size.height/2)
sprite.anchorPoint = CGPoint(x:0.5, y:0.5)
// Set up the camera
cam = SKCameraNode()
self.camera = cam
cam.setScale(3.0)
cam.position = CGPoint(x: size.width/4, y: 0)
sprite.addChild(cam)
addChild(sprite); //addChild(sprite2)
cam.addChild(sprite2)
}
func touchDown(atPoint pos : CGPoint) {
}
func touchMoved(toPoint pos : CGPoint) {
}
func touchUp(atPoint pos : CGPoint) {
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchMoved(toPoint: t.location(in: self)) }
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if sprite.contains(touchLocation) {
print("You tapped the blue sprite")
}
if sprite2.contains(touchLocation) {
print("You tapped the purple sprite")
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchUp(atPoint: t.location(in: self)) }
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
I don't think what you're saying is true. You can detect a touch from a child node. To demonstrate, I just ran a little test code within one of my SpriteKit projects where I detected touch on my camera node
Then in
didMove(to:)
:Detect touch on
cameraNode
usingtouchesEnded
:Here was my print out:
To further explain, if you added a button as a child of your
cameraNode
, you would then do something like this:Further Edit -
Your problem stems from the node in which you are requesting the touch location. As I mentioned in my answer you need to extrapolate the touch location based on the node within which you're check the sprite's frame. In your edit you are detecting touch only on
self
, which will then give you coordinates relative to yourscene
. If you want to detect touch on a subview like the camera node, you need to request the touch location within the camera node. Here is what I'm talking about based on the code you added:I just tried this and it worked fine for me.