Adri
Adri

Reputation: 243

How to make a slow transition of color in sprite kit?

Hello fellow developers,

I'm trying to change the color of a circle slowly in SpriteKit, making a transition from the original color to the new one with some time between them, transitioning from one color to the other visually. The problem that I have is that my code doesn't make that change slowly, it simply changes like if I directly assigned the new color. Here's my actual code:

func changeColor(c: UIColor) {
        var r:CGFloat = 0
        var g:CGFloat = 0
        var b:CGFloat = 0
        var a:CGFloat = 0
        c.getRed(&r, green: &g, blue: &b, alpha: &a)

        var r1:CGFloat = 0
        var g1:CGFloat = 0
        var b1:CGFloat = 0
        var a1:CGFloat = 0
        circle.fillColor.getRed(&r1, green: &g1, blue: &b1, alpha: &a1);
        let wait = SKAction.waitForDuration(0.4)
        r = (r - r1)/10
        g = (g - g1)/10
        b = (b - b1)/10

        for(var i = 0; i < 10; i++) {
            let cN = UIColor(red: (r1 + r*CGFloat(i)), green: (g1 + g*CGFloat(i)), blue: (b1 + b*CGFloat(i)), alpha: 1.0)
            circle.fillColor = cN
            circle.strokeColor = cN
            runAction(wait)
        }
    }

I really belive that the problem is in "runAction(wait)", but I'm not sure. I'd like to know if there's a default function that does what I want, but I highly doubt it.

Upvotes: 0

Views: 566

Answers (1)

rickster
rickster

Reputation: 126127

The problem is that you're trying to do all of this "live" in a for loop. All the code in your function runs in between frames of animation, so that won't work.

To make this clearer, let's ignore the wait part for a moment. When you run a function that looks like this:

func changeColor() { // unrolled loop and pseudocode colors for clarity
    circle.fillColor = blue
    circle.fillColor = red
    circle.fillColor = green
}

...SpriteKit does change the circle's color three times—immediately—but it isn't drawing the results. Whatever color you set it to most recently appears only after all your code finishes running and SpriteKit draws the screen.

When you use SKActions, what you're doing is lining up instructions for SpriteKit to follow over time, which includes instructions for tasks that take more than one animation frame. But the non-SKAction calls in your code are still the same as they were before—they make immediate changes, the end result of which you see when the next frame draws.

So, when your code looks like this:

func changeColor() { // unrolled loop and pseudocode colors for clarity
    circle.fillColor = blue
    circle.runAction(SKAction.waitForDuration(0.4))
    circle.fillColor = red
}

... the order of events is like this:

  1. set color to blue
  2. add a wait action to the list of planned actions
  3. set color to red
  4. draw the next frame (color is red)
  5. execute the list of planned actions (wait for 0.4 sec before executing next action in sequence)

So, the first thing to do is to get your color changes to stop being between-frames changes and add them to the over-time task list — that is, change color via an SKAction. There's a colorizeWithColor action, but I'm not sure whether it applies to shape nodes (with their separate fill/stroke colors). Try it, and if that fails you can try a runBlock action that directly sets fillColor/strokeColor.

Back to pseudocode to talk about the sequencing problem, though...

func changeColor() { // unrolled loop and pseudocode color actions
    circle.runAction(mySetBlueColorAction)
    circle.runAction(SKAction.waitForDuration(0.4))
    circle.runAction(mySetRedColorAction)
}

If you run this you'll notice there's still a problem here. Just calling runAction adds separate actions that run (roughly) in parallel. So your circle is still changing colors twice, but both changes are happening at the same time. (Which one wins? I'm not sure there's a determinate answer.) And at the same time as your circle is getting confused about its color, it's also starting to wait 0.4 seconds for... what exactly?

A waitForDuration action makes sense only in the context of a sequence action—for it to be meaningful, there needs to be something after in in sequence to wait for.

So, if what you want to happen sounds like this sequence:

  1. Set the color
  2. Wait a bit
  3. Set the color to something else

... then what you need is a sequence action describing what you want to happen in order:

let sequence = SKAction.sequence([
    mySetBlueColorAction,
    SKAction.waitForDuration(0.4),
    mySetRedColorAction,
    SKAction.waitForDuration(0.4),
    mySetGreenColorAction,
    // etc.
])

Upvotes: 4

Related Questions