5

Here's my setup, using Sprite Kit. First, I create a simple sprite node within a SKScene, like so:

let block = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(90, 160))
block.zPosition = 2
block.shadowCastBitMask = 1
addChild(block)

Then add a light node to the scene:

let light = SKLightNode()
light.categoryBitMask = 1
light.falloff = 1
addChild(light)

Sure enough, the block now casts a nice little shadow:

SKSpriteNode with alpha = 1.0 casting a shadow

Now I fade the block by manipulating its alpha value, for example by running an action:

let fadeOut = SKAction.fadeAlphaTo(0.0, duration: 5.0)
block.runAction(fadeOut)

Here's the awkward situation: while the block becomes more and more translucent, the shadow stays exactly the same. This is how it looks like just a moment before the end of the action:

enter image description here

And once the alpha drops to 0.0 entirely, the shadow suddenly disappears, from one frame to the next.

It would be much nicer, however, to have the shadow slowly become weaker and weaker, as the object casting it becomes more and more transparent.

Question:

Is an effect like this possible with Sprite Kit? If so, how would you go about it?

Gamma
  • 1,347
  • 11
  • 18
  • SKLightNode inherits from SKNode, so maybe you can try to do the same fadeOut action on the light node simultaneously? – prototypical Sep 25 '14 at 21:32

2 Answers2

3

This is a little tricky because the shadow cast by an SKLightNode isn't affected by the node's alpha property. What you need to do is fade out the alpha channel of the shadowColor property of the SKLightNode at the same time you're fading out your block.

The basic steps are:

  1. Store the light's shadowColor and that color's alpha channel for reference.
  2. Create a SKAction.customActionWithDuration which:
    1. Re-calculates the value for the alpha channel based on the original and how much time has past so far in the action.
    2. Sets the light's shadowColor to its original color but with the new alpha channel.
  3. Run the block's fade action and the shadow's fade action in parallel.

Example:

let fadeDuration = 5.0 // We're going to use this a lot

// Grab the light's original shadowColor so we can use it later
let shadowColor = light.shadowColor

// Also grab its alpha channel so we don't have to do it each time
let shadowAlpha = CGColorGetAlpha(shadowColor.CGColor)

let fadeShadow = SKAction.customActionWithDuration(fadeDuration) {
    // The first parameter here is the node this is running on.
    // Ideally you'd use that to get the light, but I'm taking
    // a shortcut and accessing it directly.
    (_, time) -> Void in

    // This is the original alpha channel of the shadow, adjusted
    // for how much time has past while running the action so far
    // It will go from shadowAlpha to 0.0 over fadeDuration
    let alpha = shadowAlpha - (shadowAlpha * time / CGFloat(fadeDuration))

    // Set the light's shadowColor to the original color, but replace
    // its alpha channel our newly calculated one
    light.shadowColor = shadowColor.colorWithAlphaComponent(alpha)
}

// Make the action to fade the block too; easy!
let fadeBlock = SKAction.fadeAlphaTo(0.0, duration: fadeDuration)

// Run the fadeBlock action and fadeShadow action in parallel
block.runAction(SKAction.group([fadeBlock, fadeShadow]))
Mike S
  • 41,895
  • 11
  • 89
  • 84
  • First of all, thanks for the elaborate answer! Your custom action does indeed produce the desired effect of neatly co-fading the block and shadow. Nice! I'll mark this as the correct answer. The only thing that feels a bit unsatisfactory about this approach is that manipulating the light source itself makes it somewhat necessary that it's exclusive to the fading block. If there are other shadow-casting, but non-fading objects in the same scene, these will have to supply their own light nodes... but I guess you can get there by playing around with different bit masks. – Gamma Sep 30 '14 at 23:20
  • @Gamma Glad I could help! I agree that it would be great if we could fade an object and have its shadow fade accordingly on its own, but I think it's a limitation of the current implementation of SpriteKit. Like you said though, you can probably get something to work by playing around with bit masks and such. – Mike S Sep 30 '14 at 23:40
1

The following is one way to ensure that the shadow and block fade-in/fade-out together. To use this approach, you will need to declare light and block as properties of the class.

override func didEvaluateActions() {
    light.shadowColor = light.shadowColor.colorWithAlphaComponent(block.alpha/2.0)
}

EDIT: Here's how to implement the above.

class GameScene: SKScene {

    let light = SKLightNode()
    let block = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(90, 160))

    override func didMoveToView(view: SKView) {

        /* Setup your scene here */
        block.zPosition = 2
        block.shadowCastBitMask = 1
        block.position = CGPointMake(100, 100)

        addChild(block)

        light.categoryBitMask = 1
        light.falloff = 1
        addChild(light)

        let fadeOut = SKAction.fadeAlphaTo(0.0, duration: 5.0);
        let fadeIn = SKAction.fadeAlphaTo(1.0, duration: 5.0);

        block.runAction(SKAction.sequence([fadeOut,fadeIn,fadeOut]))
    }

    override func didEvaluateActions() {
        light.shadowColor = light.shadowColor.colorWithAlphaComponent(block.alpha/2.0)
    }
}
0x141E
  • 12,613
  • 2
  • 41
  • 54
  • Thank you for your answer! This seemed like a really straightforward solution, but unfortunately I could not get it to work (which might very well be due to my limited understanding of both swift and sprite kit). Although the code executed alright, in my tests, directly manipulating the alpha of the light's shadowColor in this way did not seem to affect the actual rendering of the shadow at all. – Gamma Sep 30 '14 at 23:25
  • Took me a bit to get around to it, but I just tried your implementation, and it works nicely! – Gamma Oct 12 '14 at 17:39