4

I'm struggling to discover why I cannot detect a bodyAtPoint with SpriteKit. bodyAtPoint always returns nil, even when it appears I'm always tapping the sprite node.

Here's the code:

...

let spaceship = SKSpriteNode(color: UIColor.blueColor(), size: CGSizeMake(100, 100))

override func didMoveToView(view: SKView) {
    /* Setup your scene here */

    var borderBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
    self.physicsBody = borderBody
    self.physicsBody.friction = 0.0
    self.physicsWorld.gravity = CGVectorMake(0.0, 0.0)

    spaceship.name = "spaceship"
    spaceship.position = CGPointMake(400, 300)

    var bodySize = CGSizeMake(spaceship.size.width / 1.15, spaceship.size.height / 1.15);
    spaceship.physicsBody = SKPhysicsBody(rectangleOfSize: bodySize)

    spaceship.physicsBody.dynamic = false
    spaceship.physicsBody.restitution = 1.0
    spaceship.physicsBody.friction = 0.0
    spaceship.physicsBody.linearDamping = 0.0
    spaceship.physicsBody.allowsRotation = false

    self.addChild(spaceship)
}

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {

    /* Called when a touch begins */
    super.touchesBegan(touches, withEvent: event)

    var touch : UITouch! = touches.anyObject() as UITouch
    var touchLocation : CGPoint! = touch.locationInNode(self)

    if self.physicsWorld.bodyAtPoint(touchLocation) {
        NSLog("true")
    } else {
        NSLog("false")
    }
}

...

RESULTS:

spaceship.physicsBody outputs:

<SKPhysicsBody> type:<Rectangle> representedObject:[<SKSpriteNode> name:'spaceship' texture:['nil'] position:{400, 300} size:{100, 100} rotation:0.00]

touchLocation output:

(411.943664550781,553.014099121094)

self.physicsWorld.bodyAtPoint(touchLocation) is always:

nil

... Therefore the conditional always returns false.

Can anybody explain where I'm going wrong? I want to ultimately detect a touch on a sprite node and perform an action.

EDIT:

Even if I simplify to the following I still always get false:

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {

    ...

    for touch: AnyObject in touches {
        let location = touch.locationInNode(self)

        if self.physicsWorld.bodyAtPoint(location) {
            NSLog("true")
        } else {
            NSLog("false")
        }
    }
}

...
Anh Pham
  • 2,108
  • 9
  • 18
  • 29
lkemitchll
  • 2,751
  • 2
  • 15
  • 16
  • Does SpriteKit use origin from left-up corner or you need revert it to left-bottom? – Bogdan Jun 05 '14 at 09:51
  • @eXhausted I'm sorry, but I'm not sure... – lkemitchll Jun 05 '14 at 10:02
  • 1
    There are one more method called `self.physicsWorld.bodyInRect`, try to call `self.physicsWorld.bodyInRect(self.frame)` if you subclassed from SKNode. Does it return some value? – Bogdan Jun 05 '14 at 10:06
  • 1
    Also you can use: `let location = touch.locationInNode(self) let nodeAtPoint = self.nodeAtPoint(location) println("NODE AT POINT: \(nodeAtPoint)")` – Bogdan Jun 05 '14 at 10:23
  • @eXhausted Excellent thanks! I've updated the post with the answer. – lkemitchll Jun 05 '14 at 10:54
  • My solution Sorry code objective-c http://stackoverflow.com/questions/22578564/detect-click-touch-on-isometric-texture/27150398#27150398 – Roman Bambura Nov 26 '14 at 13:29

3 Answers3

1

If you want to use exactly bodyAtPoint, you cannot access to the name of the node. The problem is if you want a pixel perfect touch detection, for example, you need to detect the shape not the rect of the sprite with alpha part.

The trick is to use the categoryBitMask. You can assign the category 0x1 << 1, for example, to an object and then ask for its category. Category is an Uint32 with the same function like name, mainly for physics collision detection, but you can use it for recognize a physicsBody:

        let nodo = self.physicsWorld.bodyAtPoint(punto)
        if (nodo?.categoryBitMask == 0x1 << 1) {

        }

So easy! Hope it helps you!

0

With many thanks to @eXhausted for the answer.

The trick is to call nodeAtPoint and access nodeAtPoint.name.

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {

    for touch: AnyObject in touches {

        let location: CGPoint! = touch.locationInNode(self)

        let nodeAtPoint = self.nodeAtPoint(location)

        if nodeAtPoint.name {
            println("NODE FOUND: \(nodeAtPoint.name)")
        } else {
            println("NULL")
        }

    }

}
lkemitchll
  • 2,751
  • 2
  • 15
  • 16
  • 2
    You will need to use `nodeAtPoint.name!` if it is an Optional. If it isn't an Optional then `if nodeAtPoint.name` doesn't make sense so that needs to change. – Fogmeister Jun 05 '14 at 10:56
  • This doesn't really explain why bodyAtPoint doesn't work and is in fact doing something different. It finds an SKNode whose frame intersects this point as apposed to a SKPhysicsBody which is attached to a SKNode. The SKNode accumulated frame is not necessarily equal to the physics body path, meaning these two "alternatives" do not always produce the same result. I only bring this up because I have had no luck with these methods in swift but have used them without issue in the past using objective-c code. – nacross Jul 11 '14 at 07:38
0

I had a similar problem as nodes approached another node that had an attached particle emitter. Seems the nodeAtPoint() detected the emitter even though it was hidden at the time.

I solved this by using the alternative nodesAtPoint() and iterating through the nodes until I found the node I really wanted to detect.

Not sure if the z order if the particle emitter is bringing it to the top of the stack or not, but not wanting to fiddle with the z ordering at this stage, finding all touched nodes at the touch point worked for me.