Swift + Sprite Kit Touch Detection - ios

I am just wondering how to remove an SKSprite Node from the scene. This is what I have so far:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = (touch as UITouch).locationInNode(self)
if let theName = self.nodeAtPoint(location).name {
if theName == "monster" {
monster! .removeFromParent()
}
}
}
}
I am creating lots of these monsters on the screen but when I tap on one of them it doesn't do anything. If I trying adding println("touched") it tells me that it has been touched.

When you do monster.removeFromParent() this does not remove the touched node because monster is not a reference to the touched node. To remove the touched node you can use the following code:
for touch in touches {
let location = (touch as UITouch).locationInNode(self)
if let theMonster = self.nodeAtPoint(location)
if theMonster.name == "monster" {
theMonster.removeFromParent()
}
}
}

Are you keeping track of your monsters? If not please keep track of those by adding these to a Mutable Array. Also add unique name to each sprite.
Then just compare the object with your array and remove that object. Hope this helps.. :)

Related

SpriteKit: How to make touchesBegan work for node not on the highest zPosition

I am making a SpriteKit game that has a button node that is at the highest zPosition. I am overriding the touchesBegan function to add functionality to the button. It works because that button is at the highest position in the z dimension. The .zPosition is set to the highest amount compared to any other nodes. I need to have another button in the scene but it needs to have a lower .zPosition because it needs to be covered by another node at one point. When I have a .zPosition that is lower than another node, the touchesBegan method does not trigger. It works if I have a higher .zPosition but that makes the other button node not work. How can I make the touchesBegan method work regardless of what the .zPosition is? Please ask me for more clarification. Thanks!
Once you get the point in touchesBegan did you try to test if the button node contains the point :
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) -> Bool {
if touches.count == 1 {
if let touch = touches.first {
let location = touch.location(in: self)
if myButtonNode.contains(location) {
...
You want to use the nodes(at:) method. This is an array of all nodes at a particular point, the point being where you touch.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let location = t.location(in: self)
let touchedNodes = self.nodes(at: location) // Array of all nodes at the point
for node in touchedNodes { // Iterate through all nodes in the touchedNode array
if node.name == "your button node" {
... // Add your button code here
}
}
}
}

SKSpriteNode does not let touches pass through when isUserInteractionEnabled false

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

SKNode now shown from another class

I've two classes in separate files.
In the "main" file, where my scene is, I'm calling this function:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
character().spawnCharacter()
}
}
So, in my second file, character.swift I've this code:
import SpriteKit
public func spawnCharacter() -> SKSpriteNode{
let charNode = SKSpriteNode(imageNamed: "Hero")
charNode.name = "Hero"
charNode.postion = CGPointZero
charNode.zPosition = 3
addChild(charNode)
print("Node added")
return charNode
}
So, the print line is printed, but the node is never added.
I've been over my code a couple of times, but I can't crack it.
Any clues?
I am not entirely sure how your main file is organised, but I think the problem is here:
for touch in touches {
character().spawnCharacter() // <- SKSpriteNode is created but not added
}
You create sprite-node in spawnCharacter() but you do not use the returned object.
Potential fix would be:
for touch in touches {
let character = character().spawnCharacter()
scene.addChild(character)
}

iOS Spritekit intersectsnode does not work for SKShapeNode

I am using the iOS SpriteKit Game Swift template on iOS8.3. I am trying to use the function func intersectsNode(_ node: SKNode) -> Bool to detect overlap of two circles created as SKShapeNodes. Turns out the function does not detect the intersection if its SKShapeNodes. But on further troubleshooting, it turns out the function works if I use the default Spaceship sprite of the template. Here is the code which works:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
let sprite = SKSpriteNode(imageNamed:"Spaceship")
sprite.xScale = 0.3
sprite.yScale = 0.3
sprite.position = location
self.circles.append(sprite)
self.addChild(sprite)
if circles.count == 1 {
println("One circle")
} else {
for var index = 0; index < circles.count-1; ++index {
if sprite.intersectsNode(circles[index]) {
println(circles[index].frame)
println("Circle intersects another")
}
}
}
}
}
When two sprites overlap in the above code the function returns YES and prints the intersection string. Here is the code which does NOT work:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
let sprite = SKShapeNode(circleOfRadius: 50)
sprite.position = location
self.circles.append(sprite)
self.addChild(sprite)
if circles.count == 1 {
println("One circle")
} else {
for var index = 0; index < circles.count-1; ++index {
if sprite.intersectsNode(circles[index]) {
println(circles[index].frame)
println("Circle intersects another")
}
}
}
}
}
As you can see the code blocks are completely identical except in one case its an SKSpriteNode and the other case its SKShapeNode. I also printed the frames of the SKShapeNode Circles and I can see they have have valid frames. So I am puzzled by this as I would like to use SKShapeNodes in my code for now but I cannot use the intersectNode function as it does not work.
The documentation for intersectsNode says this:
The two nodes are considered to intersect if their frames intersect. The children of both nodes are ignored in this test.
Which means you won't get the result you want when using circles.
However, checking if two circles overlap is as easy as checking if the distance between their centers is less than the sum of their radiuses.

SKLabelNode will disappear but is still clickable

I am making a game using SpriteKit and Swift, running Xcode 6. I have an SKLabelNode, let's call it myLabelNode for this example. When I call myLabelNode.removeFromParent() it removes the node from the scene, as it should. The node count drops by 1, and it isn't visible anywhere on the screen. However, when I click the spot where myLabelNode previously was, my program will still call out the function that should only happen when myLabelNode is touched. I also tried combining myLabelNode.removeFromParent() with myLabelNode.hidden = true, but it is still touchable, and calls the function even though it shouldn't. How should I fix this? Is there a different method I should be using? Is this supposed to happen?
Edit:
let lemonadeLabel = SKLabelNode(fontNamed: "Optima-ExtraBlack")
override func didMoveToView(view: SKView) {
lemonadeLabel.text = "Lemonade Stand"
lemonadeLabel.fontSize = 24
lemonadeLabel.fontColor = SKColor.yellowColor()
lemonadeLabel.position = CGPoint(x: size.width/2, y: size.height*0.66)
lemonadeLabel.zPosition = 2.0
addChild(lemonadeLabel)
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self)
if lemonadeLabel.containsPoint(location) {
println("lemonadeLabel pressed")
lemonadeLabel.removeFromParent()
/*lemonadeLabel is now be removed,
however if I click the area where it
used to be, "lemonadeLabel pressed"
will print to the console*/
}
}
You are trying to determine if the constrainPoints' location are being touched. Even if you remove the label from the scene, it is still an object in memory, i.e: you could re-ad it later.. it still has all it's properties including position, etc..
I would try this instead:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
if nodeAtPoint(touch.locationInNode(self)) == lemonadeLabel {
println("lemonadeLabel pressed")
lemonadeLabel.removeFromParent()
}
}
}
You basically determine if the lemonadeLabel is the node at that position, if yes you remove it. Since you compare with the added node in the scene, if it's gone, it will not be there for comparison ;)
Your labelNode may not be inside the SKScene anymore. This does not mean that it will not respond to the containsPoint function. The labelNode still has a position assigned to it and it can calculate if a point falls inside it using containsPoint function.
Instead you can try this.
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self)
if self.nodeAtPoint(location) === lemonadeLabel {
println("lemonadeLabel pressed")
lemonadeLabel.removeFromParent()
}
}

Resources