iOS 8 detect if SKSpriteNode is lit by SKLightNode

186 views Asked by At

I have several sprite nodes in my scene that are casting shadows. I also have a sprite that is in one of the shadows. I want to be able to tell if the user moves a sprite out of a shadow into the light. Anyway to do this in swift? Thanks.

1

There are 1 answers

0
AudioBubble On

Unfortunatly this functionality still isn't included in SpriteKit, however it is possible to implement a decent solution with some caveats.

To determine if a sprite casts a shadow, its shadowCastBitMask property "is tested against the light's categoryBitMask property by performing a logical AND operation." SpriteKit appears to generate the exact same mask data for lighting and physics body calculations, based on the description given in the documentation for the shadowColor property defined on SKLightNode:

When lighting is calculated, shadows are created as if a ray was cast out from the light node's position. If a sprite casts a shadow, the rays are blocked when they intersect with the sprite's physics body. Otherwise, the sprite's texture is used to generate a mask, and any pixel in the sprite node's texture that has an alpha value that is nonzero blocks the light.

SKPhysicsWorld has a method, enumerateBodies(alongRayStart:end:using:), for performing this kind of ray intersection test performantly. That means we can test if a sprite is shadowed by any sprite with a physics body. So we can write a method like this to extend SKSpriteNode:

func isLit(by light: SKLightNode) -> Bool {
    guard light.isEnabled else {
        return false
    }

    var shadowed = false

    scene?.physicsWorld.enumerateBodies(alongRayStart: light.position, end: position) { (body, _, _, stop) in
        if let sprite = body.node as? SKSpriteNode, light.categoryBitMask & sprite.shadowCastBitMask != 0 {
            shadowed = true

            stop.pointee = true
        }
    }

    if shadowed {
        return false
    } else {
        return true
    }
}

We can also retrieve which lights in the scene are lighting a particular sprite:

func lights(affecting sprite: SKSpriteNode) -> [SKLightNode] {
    let lights = sprite.scene?.children.flatMap { (node) -> SKLightNode? in
        node as? SKLightNode
    } ?? []

    return lights.filter { (light) -> Bool in
        sprite.isLit(by: light)
    }
}

It'd be great if SpriteKit provided a way to retrieve this information without coupling it to the physics API, or else requiring developers to roll their own ray cast implementations.