Swift nodeAtPoint difficulty - swift

I am building a game in which the player drags a piece around the gameboard. I wish to know what are all of the nodes underneath that piece, and I am getting odd results. Here is the touchesMoves func:
override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) {
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self.parent)
self.position = location
println("checker x: \(location.x)")
println("node at point: \(self.nodeAtPoint(location))")
println("nodes at point: \(self.nodesAtPoint(location))")
}
The sprite moves around the board just fine, but what is reported as the nodeAtPoint is always the sprite being moved around (which kind of makes sense but is not useful. Any oddly, the nodesAtPoint is always reported as an empty array! How is this possible? What should I be doing differently?
Update: This continues to be a struggle. I want to keep the touch methods in the node itself, and not the scene. The most recent version of the touchesMoved is the following:
override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) {
// Turn off the touched bool:
touched = false
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self.parent)
let loc = touch.locationInView(scene.view)
let loc2 = touch.locationInNode(scene)
self.position = location
println("checker: \(loc2.x), \(loc2.y)")
println("node at point: \(self.nodeAtPoint(loc2).name)")
println("nodes at point: \(self.nodesAtPoint(loc2))")
}
The nodesAtPoint array continues to be empty with one really odd exception. When hovering near the center of the scene, I get this:
nodes at point: [ name:'(null)' accumulatedFrame:{{-30, -19.80000114440918}, {60, 39.600002288818359}}]
There is not shape node there that I am aware of! Why am I not detecting the nodes I pass over?

I discovered the answer to this. Essentially I was trying to detect nodesAtPoint on self, which in this case was the node being moved around the screen. Once I changed that to self.scene, the nodesAtPoint array populated as expected. So, to be clear:
println("nodes at point: \(self.nodesAtPoint(loc2))")
Needed to be changed to this:
println("nodes at point: \(self.scene.nodesAtPoint(loc2))")

If self is SKScene, try to change
let location = touch.locationInNode(self.parent)
to
let location = touch.locationInNode(self)
because SKScene's parent is nil

Related

Moving SKSpriteNode with finger only if it is of type Fruit (subclass of SKSpriteNode)

I am creating a game where the user can move some fruits around in the scene. I want to user to be able to move only the fruits and not any other SKSpriteNode in the scene, so I wrote the code below to implement it. However the code doesn't work properly as I can't seem to be able to drag any of my sprites around, but rather they change position only when I stop touching the screen and they don't move by much anyway.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
if let location = touch?.location(in: self){
let nodesTouched = nodes(at: location)
for node in (nodesTouched) {
if node is Fruit{
for t in touches {
let locationMoved = t.location(in: self)
node.position.x = locationMoved.x
node.position.y = locationMoved.y
}
}
}
}
}
Anyone knows what's wrong with it?
Thanks in advance!
I found a solution to this, which was to basically set the physicsBody.affectedByGravity property to false for that specific Fruit instance every time I touch it, and then set it back to true as soon as I stop touching it. that makes it possible to drag all the fruits everywhere I want.

SpriteKit - Why SKNode's are not being touch detected

I have reviewed countless references to try to understand why my scene is not behaving the way i expected it to, such as this.
Here is my very simple SKScene (2 child nodes):
The scene has a SpriteNode (which covers the entire scene as a background image). This has a zPosition = 0.
The scene has a 2nd node (SKNode) which itself has another child (up to 2 levels). This has a zPosiiton - 2.
ALL nodes have .userInteractionEnabled = false
Issue:
When i click anywhere all i see is that the 1st child (SpriteNode) is touched. The 2nd child (SKNode) is never touch-detected.
Note that the z-ordering of the Nodes are being rendered as I expect them. It is the touch-detection that doesnt appear to be working.
Snippet of my touchesBegan method:
for touch in touches {
let touchLocation = touch.locationInNode(self)
let sceneTouchPoint = self.convertPointToView(touchLocation)
let touchedNode = self.nodeAtPoint(sceneTouchPoint)
if (touchedNode.name != nil) {
print("Touched = \(touchedNode.name! as String)")
}
}
I had a similar issue (background in z: 999 + spawning "ducks" nodes in z: <999) that I solved with the following code in Swift 4:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch:UITouch = touches.first!
let positionInScene = touch.location(in: self)
let touchedNodes = self.nodes(at: positionInScene)
for touch in touchedNodes {
let touchName = touch.name
if (touchName != nil && touchName!.hasPrefix("pato_")) {
touch.removeFromParent()
}
}
}
I had several layers of nodes because I used a mask over my game with buttons to make selections and move forward. I had issues with the buttons not working until I made a "startState:Bool = true" and updated this to false when the start screen was clicked through. I then had each of my buttons on that start page to have && startState==true for there clicks to be taken. It may be that your clicks are being recorded - but its not the node you think you are using. I would put print("NodeXXX") on each entry in touches and give it a unique name so you can see where the touches are actually happening.
Hope that helps.
Best regards,
mpe

touchesMoved not functioning properly

I am trying to implement an overriden touchesMoved func to enable SKSpriteNodes to be moved around by the user. However, I have to move the node very slowly for it to follow my touches when I drag. In addition, there are three SKSpriteNodes in the background (which you will see I explicitly set to .userInteractionEnabled = false) and these nodes will occasionally respond to the touches. Any help would be greatly appreciated. Let me know if there are any other parts of the code you need.
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
var positionInScene = CGPoint(x: 375.0, y: 400.0) //sets a default position
titleLabel.userInteractionEnabled = false
drawingBoard.userInteractionEnabled = false
sideBar.userInteractionEnabled = false
for touch in touches {
positionInScene = touch.locationInNode(self)
if self.nodeAtPoint(positionInScene) is SKSpriteNode {
if (self.nodeAtPoint(positionInScene)).name == movableNodeName { //movableNodeName is the name assigned to all SKSpriteNodes that should be draggable
//I know this might be a strange way of doing it
(self.nodeAtPoint(touch.previousLocationInNode(self))).position = positionInScene
}
}
}
}
I've managed to fix this issue by just setting the coordinates of the node that shouldn't move back to what they were originally whenever it is moved. This seems to work as I haven't been able to replicate the bug again in testing. If anyone could come up with a better solution though, I would love to hear it.
Your problem is you are using touch.locationInNode(self), where self is your SKSpriteNode. This means that your touch move code will only respond to what is going on inside of your SKSpriteNode. What you need to do, is use either the parent of the sprite, or the scene, based on how you want to apply this logic.

How to make SKSpriteNode with fixed position (after adding physics)?

I have several programmatically made SKSpriteNode's. Some of them I want to move around, some I want to be static (have a fixed position). When adding physics to the nodes (need that to be able to do collision detection, right?) and set physicsBodyXXXX.dynamic = false they stay in the same position when moving other object over them. That's fine!
But, I'm still able to grab the node I want to be statically positioned, and move them around. How can I mask out the node I don't want to move in touches function? Or is there another solution?
Tried to find a property like static which made the node's position fixed, but can't find it...
Here's my code for auto generating nodes (in override func didMoveToView(view: SKView):
for Character in englishWord{
// Make letters:
let letterToMove = SKSpriteNode(imageNamed: "\(Character)")
//then setting size and position
var physicsBodyLetterToMove = SKPhysicsBody(rectangleOfSize: letterToMove.size)
physicsBodyLetterToMove.affectedByGravity = false
physicsBodyLetterToMove.allowsRotation = false
physicsBodyLetterToMove.dynamic = false
letterToMove.physicsBody = physicsBodyLetterToMove
self.addChild(letterToMove)
// Make empty boxes for the letters:
let letterRecBox = SKSpriteNode(imageNamed: "EmptyBox")
//then setting size and position
var physicsBodyLetterRecBox = SKPhysicsBody(rectangleOfSize: letterRecBox.size)
physicsBodyLetterToMove.affectedByGravity = false
physicsBodyLetterRecBox.dynamic = false
letterRecBox.physicsBody = physicsBodyLetterRecBox
self.addChild(letterRecBox)
}
So the touches func's:
var selected: [UITouch: SKNode] = [:]
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
selected = [:]
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
selected[touch as UITouch] = nodeAtPoint(location)
}
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
for (touch, node) in selected{
if !contains([self], node){
let action = SKAction.moveTo(location, duration: 0.1)
node.runAction(SKAction.repeatAction(action, count: 1))
}
}
}
}
Any idea?
Setting dynamic to false will make the node unaffected by physics. SKActions and touch events are not considered physics so they will still affect your nodes that are not dynamic.
You could do something like:
YourSpriteNode.name = #"staticNode"; //Right after you create it
Then alter your touch method:
for (touch, node) in selected{
if !contains([self], node){
if(![node.name isEqualToString:#"staticNode"])
{
let action = SKAction.moveTo(location, duration: 0.1)
node.runAction(SKAction.repeatAction(action, count: 1))
}
}
}
The newly introduced if statement will prevent any node named "staticNode" from getting moved due to your SKActions. Other nodes will move as expected.
Thanx, that worked.
Only, I had to write it like this:
letterRecBox.name = "staticNode"
...
if !(node.name == "staticNode"){
...then do something...
}

Detecting Touch on SKShapeNode that is a Line

I have an SKShapeNode that I have created and given a CGPath to. This is in my GameScene.swift in didMoveToView:
let myNode = SKShapeNode()
let lineToDraw = CGPathCreateMutable()
CGPathMoveToPoint(lineToDraw, nil, 0, 0)
CGPathAddLineToPoint(lineToDraw, nil, 87, 120)
myNode.path = lineToDraw
myNode.strokeColor = SKColor.redColor()
myNode.lineWidth = 20
myNode.name = "My Node!"
world.addChild(myNode) // World is a higher-level node in my scene
And this is how I'm detecting touches, again in the Scene:
override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) {
if let touch = touches.anyObject() as? UITouch {
if let shapeNode = nodeAtPoint(touch.locationInNode(self)) as? SKShapeNode {
println("Touched \(shapeNode.name)")
} else {
println("Nothing here")
}
}
}
The line node is showing up with no issues. However, it is not registering touchesEnded at all.
Really, I have three questions nebulous to this:
Is there a better way to create an SKShapeNode that is a line between two points without calling a convenience initializer? I plan on subclassing SKShapeNode to add additional logic to the node, and the subclass doesn't have access to convenience initializers of the superclass.
More importantly, how do I get the scene to recognize my line node there and trigger touchesEnded?
Is there a way, using that mechanism, I can make it a bit more "fuzzy", so it handles touches close to the line, instead of on the line? My eventual plan is to make it thinner, but I'd still like to have a large touch zone. I figure if nothing else, I can create two nodes: one clear/thick and the other red/thin, and handle touch events on the clear one, but I would like to know if there's a better way.
In answer to your main question, there is a problem in your touchesEnded method. The template shown by apple recommends the following layout:
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
}
}
You can then use this method to see if the location value is inside the lines frame (in order to do this, i created the "myNode" variable outside of the didMoveToView so that i could access it from the rest of the functions):
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(world)
if CGRectContainsPoint(myNode.frame, location) {
println("Touched \(myNode.name)")
} else {
println("Nothing here")
}
}
}
As for a "fuzzier" line, you can simply check a larger CGRect. In the code above, i check myNode.frame but you could create a variable with a CGRect which is slightly larger than the line so that you can detect touches which don't directly hit it.
As for more concise code, i cannot think of any at the moment but that isn't to say that there isn't a way. However, I don't quite understand what you mean about subclasses not having access to convenience methods as they can have access to whatever the superclass does so long as you import correctly.
I hope this helps.