Detect SKSpriteNode that is lit by SKLightNode

Is there a way to detect if a Sprite is lit or casting a shadow? I have several sprites in my scene and they are casting shadows. I also have a sprite in one of the sprites shadow. I want to know when the user moves the sprite that is in a shadow out into the light. Thanks.

Replies

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.