Search code examples
swiftskspritenodeskphysicsbody

Trying to get Physics Shape That Matches the Graphical Representation


what I'm trying to achieve is that the Node would have the same shape as PhysicsBody/texture (fire has a complicated shape), and then I'm trying to make only fireImage touchable. So far when I'm touching outside of the fireImage on the screen and it still makes a sound. It seems that I'm touching the squared Node, but I want to touch only the sprite/texture. Would appreciate your help. The code is below:

import SpriteKit
import AVFoundation

private var backgroundMusicPlayer: AVAudioPlayer!

class GameScene2: SKScene {

    var wait1 = SKAction.waitForDuration(1)

    override func didMoveToView(view: SKView) {      
        setUpScenery()
    }



    private func setUpScenery() { 
        //I'm creating a Fire Object here and trying to set its Node a physicsBody/texture shape: 
        let fireLayerTexture = SKTexture(imageNamed: fireImage)
        let fireLayer = SKSpriteNode(texture: fireLayerTexture)

        fireLayer.anchorPoint = CGPointMake(1, 0)
        fireLayer.position = CGPointMake(size.width, 0)
        fireLayer.zPosition = Layer.Z4st

        var firedown = SKAction.moveToY(-200, duration: 0)
        var fireup1 = SKAction.moveToY(10, duration: 0.8)
        var fireup2 = SKAction.moveToY(0, duration: 0.2)

        fireLayer.physicsBody = SKPhysicsBody(texture: fireLayerTexture, size: fireLayer.texture!.size())
        fireLayer.physicsBody!.affectedByGravity = false

        fireLayer.name = "fireNode"

        fireLayer.runAction(SKAction.sequence([firedown, wait1, fireup1, fireup2]))

        addChild(fireLayer)

    }

    //Here, I'm calling a Node I want to touch. I assume that it has a shape of a PhysicsBody/texture:
    override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
        for touch: AnyObject in touches {
        let touch = touches.anyObject() as UITouch

        let touchLocation = touch.locationInNode(self)
        let node: SKNode = nodeAtPoint(touchLocation)

            if node.name == "fireNode" {

                var playguitar = SKAction.playSoundFileNamed("fire.wav", waitForCompletion: true)
                node.runAction(playguitar)    
            }           
        }   
    }
}

Solution

  • Physics bodies and their shapes are for physics — that is, for simulating things like collisions between sprites in your scene.

    Touch handling (i.e. nodeAtPoint) doesn't go through physics. That's probably the way it should be — if you had a sprite with a very small collision surface, you might not necessarily want it to be difficult to touch, and you also might want to be able to touch nodes that don't have physics. So your physics body doesn't affect the behavior of nodeAtPoint.

    An API that lets you define a hit-testing area for a node — that's independent of the node's contents or physics — might not be a bad idea, but such a thing doesn't currently exist in SpriteKit. They do take feature requests, though.

    In the meantime, fine-grained hit testing is something you'd have to do yourself. There are at least a couple of ways to do it:

    • If you can define the touch area as a path (CGPath or UIBezierPath/NSBezierPath), like you would for creating an SKShapeNode or a physics body using bodyWithPolygonFromPath:, you can hit-test against the path. See CGPathContainsPoint / containsPoint: / containsPoint: for the kind of path you're dealing with (and be sure to convert to the right local node coordinate space first).
    • If you want to hit-test against individual pixels... that'd be really slow, probably, but in theory the new SKMutableTexture class in iOS 8 / OX X v10.10 could help you. There's no saying you have to use the modifyPixelDataWithBlock: callback to actually change the texture data... you could use that once in setup to get your own copy of the texture data, then write your own hit testing code to decide what RGBA values count as a "hit".