Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fading a shadow together with the SKSpriteNode that casts it

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?

like image 872
Gamma Avatar asked Sep 30 '22 19:09

Gamma


2 Answers

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]))
like image 73
Mike S Avatar answered Oct 05 '22 08:10

Mike S


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)
    }
}
like image 23
0x141E Avatar answered Oct 05 '22 08:10

0x141E