How to Detect If an Skspritenode Has Been Touched

How do I detect if an SKSpriteNode has been touched

First set the name property of the SKSpriteNode to a string.

pineapple.name = "pineapple"
pineapple.userInteractionEnabled = false

then in touchesBegan function in the Scene

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch:UITouch = touches.anyObject()! as UITouch
let positionInScene = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(positionInScene)

if let name = touchedNode.name
{
if name == "pineapple"
{
print("Touched")
}
}

}

This is one way to do it.

You can also subclass SKSpriteNode and override the touchesBegan inside it.

class TouchableSpriteNode : SKSpriteNode
{
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
print("touched")
}
}

Then do

let pineapple = TouchableSpriteNode(imageNamed: "Pineappleimg")
pineapple.userInteractionEnabled = true
pineapple.position = CGPoint(x: CGRectGetMidX(self.frame) - 200, y: CGRectGetMidY(self.frame));
self.addChild(pineapple)

how to detect touch on node

every time you touch the screen you are cycling through all balls to see if you're touching one of them. if you have 50 balls on the screen it goes through them all to see if you are touching 1. that's not an efficient way of figuring out if you are touching 1.

There are many ways you can do this but what I would do is handle the touches inside of the Ball class. That way you don't have to figure out if you are touching a ball and which one it might be.

Explanation of protocol (to the best of my ability) this may seem a little much right now, but the faster you learn and understand protocols that better off you will be (IMO).

In this example we will use a protocol to setup a delegate of the
BallNode class. A protocol is a set user defined "rules" that must be
followed by any class that you designate compliant to that protocol.
In my example I state that for a class to be compliant to the
BallNodeDelegate protocol it must contain the didClick func. When you
add the BallNodeDelegate after GameScene you are stating that this
class will be compliant to that protocol. So if in GameScene you did
not have the didClick func it will cause an error. All this is put in
place so that you have an easy way to communicate between your
BallNode instances and your GameScene class (without having to pass
around references to your GameScene to each BallNode). Each BallNode
then has a delegate (GameScene) which you can pass back the
information to.

inside your BallNode class make sure you have isUserInteraction = true

outside of your BallNode class create a protocol that will send the touch info back to the GameScene

protocol BallNodeDelegate: class {
func didClick(ball: BallNode)
}

create a delegate variable in your BallNode class

weak var delegate: BallNodeDelegate!

move the touches began to you BallNode class

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

self.delegate?.didClick(ball: self)
}

in GameScene add the compliance to the BallNode protocol

class GameScene: SKScene, BallNodeDelegate

in GameScene when you create a Ball make sure you set it's delegate

let ball = BallNode()
ball.delegate = self

in GameScene add the nest. func to handle the clicks

func didClick(ball: BallNode) {
print("clicked ball")
}

How do I detect which SKSpriteNode has been touched

What you are trying to do (even if I don't see a reason for this) can be accomplished using delegation pattern. Basically, you will tell your delegate (the scene, or whatever you set as a delegate) to do something for you, and you will do that directly from within the button's touchesBegan method. Also, you will pass the button's name to a scene.

To make this happen, first you have to define a protocol called ButtonDelegate. That protocol defines a requirement which states that any conforming class has to implement a method called printButtonsName(_:):

protocol ButtonDelegate:class {

func printButtonsName(name:String?)
}

This is the method which will be implemented in your GameSceneclass, but called from within button's touchesBegan. Also, this method will be used to pass a button's name to its delegate (scene), so you will always know which button is tapped.

Next thing is button class itself. Button might look like this:

class Button : SKSpriteNode{

weak var delegate:ButtonDelegate?

init(name:String){
super.init(texture: nil, color: .purpleColor(), size: CGSize(width: 50, height: 50))
self.name = name
self.userInteractionEnabled = true
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}


override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

delegate?.printButtonsName(self.name)
}
}

The important thing here is userInteractionEnabled = true, which means that button will accept touches. Another important thing is a delegate property. As already mentioned, buttons will have the scene set as their delegate. Setting a scene as delegate of buttons will be done later when we create some buttons... To make this easier for you, think of a delegate as a worker who works for his boss :) The boss (a button) tells his worker (a scene) to do something for him (to prints his name).

Okay, so lets make sure that scene conforms to a ButtonDelegate protocol...Why is this important? It is important because the worker (scene) must follow the orders of his boss (a button). By conforming to this protocol, the worker is making a contract with his boss where confirming that he knows how to do his job and will follow his orders :)

class GameScene: SKScene, ButtonDelegate {


override func didMoveToView(view: SKView) {

let play = Button(name:"play")
play.delegate = self
let stop = Button(name:"stop")
stop.delegate = self

play.position = CGPoint(x: frame.midX - 50.0, y: frame.midY)
stop.position = CGPoint(x: frame.midX + 50.0, y: frame.midY)

addChild(play)
addChild(stop)
}

func printButtonsName(name: String?) {

if let buttonName = name {
print("Pressed button : \(buttonName) ")
}

//Use switch here to take appropriate actions based on button's name (if you like)
}
}

And that's it. When you tap the play button, the touchesBegan on a button itself will be called, then the button will tell its delegate to print its name using the method defined inside of scene class.

How to detect if child node has been touched: Sprite Kit

Here is one way to track continuous touches (i.e.. directional arrows).

You need to keep track of the touches, in the SKScene node, or in the parent view controller:

var touches = Set<UITouch>()

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.touches.formUnion(touches)
self.updateTouches()
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
self.touches.formUnion(touches)
self.updateTouches()
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.touches.subtract(touches)
self.updateTouches()
}

override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
self.touches.subtract(touches)
self.updateTouches()
}

For each touch, translate the coordinates to relative to the SKNode. Check if any of the touches is within the accumulated frame of the SKNode. The accumulated frame is the total bounding area of the node and all of its descendants.

private func isTouching(node: SKNode) -> Bool {

guard let parent = node.parent else {
return false
}

let frame = node.calculateAccumulatedFrame()

for touch in touches {

let coordinate = touch.location(in: parent)

if frame.contains(coordinate) {
return true
}
}

return false
}

For example:

override func update(_ currentTime: NSTimeInterval) {

let touchingJump = isTouching(node: jumpButtonNode)

if touchingJump {
print("jump")
}
}

Detect touch on specific area of a node in SpriteKit

You can define a rectangle for the yellow area as below,

let yellowBox = CGRect(x: 0, y: 0, width: 40, height: 40)

You can update the x, y, width and height values to your needs.

Then you can find the touch location inside any node for a touch as below,

let touchPoint = touch.location(in: bubbleNode)

Now you can simply check if the touchPoint is inside the yellowBox or not as below,

if yellowBox.contains(touch) {
print("Yellow box is touched!")
}

You can check here for more details on how to get the touchLocation in a node.

How to detect touches on a skspritenode created in a separate scene using a custom class of that node

There is a good explanation in this answer that should be clear your ideas about SKS files and subclassing.
About your code it is a good habit to use uppercase letter for the class names, in your case I prefer to use class Tree instead of class tree.
About your second issue, you can use userData to transfer your object from a scene to another as explained below in my example:

import SpriteKit
class GameScene: SKScene {
private var label : SKLabelNode?
var tree : Tree!
override func didMove(to view: SKView) {
self.label = self.childNode(withName: "//helloLabel") as? SKLabelNode
if let label = self.label {
label.alpha = 0.0
label.run(SKAction.fadeIn(withDuration: 2.0))
}
self.isUserInteractionEnabled = true
tree = Tree()
tree.name = "tree"
addChild(tree)
tree.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let nodeUserTapped = atPoint(pointOfTouch)
if nodeUserTapped.name == "tree" {
tree.removeFromParent()
let sceneToMoveTo = Scene2.init(size: self.size)
sceneToMoveTo.userData = NSMutableDictionary()
sceneToMoveTo.userData?.setObject(tree, forKey: "tree" as NSCopying)
let gameTransition = SKTransition.fade(withDuration: 0.5)
self.view!.presentScene(sceneToMoveTo, transition: gameTransition)

}
}
}
}
class Scene2: SKScene {
var tree:Tree!
override func didMove(to view: SKView) {
print("This is the scene: \(type(of:self))")
guard let previousValue = self.userData?.value(forKey: "tree") else { return }
if previousValue is Tree {
tree = previousValue as! Tree
addChild(tree)
tree.position = CGPoint(x:self.frame.midX,y:self.frame.midY)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in: self)
let nodeUserTapped = atPoint(pointOfTouch)
if nodeUserTapped.name == "tree" {
tree.removeFromParent()
if let sceneToMoveTo = SKScene(fileNamed: "GameScene") {
sceneToMoveTo.scaleMode = .aspectFill
sceneToMoveTo.userData = NSMutableDictionary()
sceneToMoveTo.userData?.setObject(tree, forKey: "tree" as NSCopying)
let gameTransition = SKTransition.fade(withDuration: 0.5)
self.view!.presentScene(sceneToMoveTo, transition: gameTransition)
}
}
}
}
}

As you can read I've the control of touches both in my parent class and in my node, this can be made possible by changing the touchesBegan method of your custom SKSpriteNode as:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
print("touched!")
}
guard let parent = self.parent else { return }
parent.touchesBegan(touches, with: event)
}

Remember that you should extend this approach also to the other touches method if you want to use them..

Touch detection of SKSpriteNode with child nodes

You can do this inside the node subclass:

class PlanListItem:SKSpriteNode {

var isSelected: Bool = false
override init(texture size etc) {
//your init
self.userInteractionEnabled = true
}

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
print("PlanListItem touched")
isSelected = !isSelected
}
}


Related Topics



Leave a reply



Submit