问题
I'm working on a game with SpriteKit. I'm drawing a shape with SKShapeNode. Now I want to animate its colour change but the SKActions is not working for SKShapeNode. Is there any way to do this or I have to use a different approach?
Thank you.
EDIT:
Thanks to LearnCocos2D I was able to come up with this quick (and totally not perfect) solution.
int groundChangeInterval = 5;
SKColor *originalColor = [SKColor colorWithRed:0.92 green:0.87 blue:0.38 alpha:1.0];
SKColor *finalColor = [SKColor colorWithRed:0.29 green:0.89 blue:0.31 alpha:1.0];
CGFloat red1 = 0.0, green1 = 0.0, blue1 = 0.0, alpha1 = 0.0;
[originalColor getRed:&red1 green:&green1 blue:&blue1 alpha:&alpha1];
CGFloat red2 = 0.0, green2 = 0.0, blue2 = 0.0, alpha2 = 0.0;
[finalColor getRed:&red2 green:&green2 blue:&blue2 alpha:&alpha2];
SKAction *changeGroundColor = [SKAction customActionWithDuration:groundChangeInterval actionBlock:^(SKNode *node, CGFloat elapsedTime) {
CGFloat step = elapsedTime/groundChangeInterval;
CGFloat red3 = 0.0, green3 = 0.0, blue3 = 0.0;
red3 = red1-(red1-red2)*step;
green3 = green1-(green1-green2)*step;
blue3 = blue1-(blue1-blue2)*step;
[(SKShapeNode*)node setFillColor:[SKColor colorWithRed:red3 green:green3 blue:blue3 alpha:1.0]];
[(SKShapeNode*)node setStrokeColor:[SKColor colorWithRed:red3 green:green3 blue:blue3 alpha:1.0]];
}];
I only needed to fade two specific colours so it is not a universal solution but it is enough for now.
Thanks
回答1:
Use customActionWithDuration:block:
and change the fillColor
or strokeColor
properties.
I suppose the colorize actions won't work because SKShapeNode has no color
property. It's worth a try to add this property to the class in a subclass or category and redirect it to fillColor
or strokeColor
or both.
回答2:
Updated for Swift 4.2
This the common code that you need to place in and extension file or something of the sort.
func lerp(a : CGFloat, b : CGFloat, fraction : CGFloat) -> CGFloat
{
return (b-a) * fraction + a
}
struct ColorComponents {
var red = CGFloat(0)
var green = CGFloat(0)
var blue = CGFloat(0)
var alpha = CGFloat(0)
}
extension UIColor {
func toComponents() -> ColorComponents {
var components = ColorComponents()
getRed(&components.red, green: &components.green, blue: &components.blue, alpha: &components.alpha)
return components
}
}
extension SKAction {
static func colorTransitionAction(fromColor : UIColor, toColor : UIColor, duration : Double = 0.4) -> SKAction
{
return SKAction.customAction(withDuration: duration, actionBlock: { (node : SKNode!, elapsedTime : CGFloat) -> Void in
let fraction = CGFloat(elapsedTime / CGFloat(duration))
let startColorComponents = fromColor.toComponents()
let endColorComponents = toColor.toComponents()
let transColor = UIColor(red: lerp(a: startColorComponents.red, b: endColorComponents.red, fraction: fraction),
green: lerp(a: startColorComponents.green, b: endColorComponents.green, fraction: fraction),
blue: lerp(a: startColorComponents.blue, b: endColorComponents.blue, fraction: fraction),
alpha: lerp(a: startColorComponents.alpha, b: endColorComponents.alpha, fraction: fraction))
(node as? SKSpriteNode)?.color = transColor
}
)
}
}
Usage:
redSKSpriteNodeThatBecomesBlue.run(SKAction.colorTransitionAction(fromColor: .red, toColor: .blue, duration: 5))
Please note that for SKShapeNode or other purposes you need to rewrite this line to suit your need:
(node as? SKSpriteNode)?.color = transColor
This solution is heavily inspired originally by Paddy Collins answer.
回答3:
Here is my implementation. I think a little easier to read.
-(SKAction*)getColorFadeActionFrom:(SKColor*)col1 toColor:(SKColor*)col2 {
// get the Color components of col1 and col2
CGFloat r1 = 0.0, g1 = 0.0, b1 = 0.0, a1 =0.0;
CGFloat r2 = 0.0, g2 = 0.0, b2 = 0.0, a2 =0.0;
[col1 getRed:&r1 green:&g1 blue:&b1 alpha:&a1];
[col2 getRed:&r2 green:&g2 blue:&b2 alpha:&a2];
// return a color fading on the fill color
CGFloat timeToRun = 0.3;
return [SKAction customActionWithDuration:timeToRun actionBlock:^(SKNode *node, CGFloat elapsedTime) {
CGFloat fraction = elapsedTime / timeToRun;
SKColor *col3 = [SKColor colorWithRed:lerp(r1,r2,fraction)
green:lerp(g1,g2,fraction)
blue:lerp(b1,b2,fraction)
alpha:lerp(a1,a2,fraction)];
[(SKShapeNode*)node setFillColor:col3];
[(SKShapeNode*)node setStrokeColor:col3];
}];
}
double lerp(double a, double b, double fraction) {
return (b-a)*fraction + a;
}
回答4:
I wanted a continuous throbbing glow on my polygon. I used runBlock:
instead of customActionWithDuration:block:
but could have just run infinitely with that one.
SKShapeNode *shape = [SKShapeNode shapeNodeWithPoints:points count:indices.count];
shape.fillColor = [[SKColor yellowColor] colorWithAlphaComponent:0.2];
shape.strokeColor = [SKColor clearColor];
[self addChild:shape];
shape.userData = @{@"minAlpha": @0, @"maxAlpha": @0.5, @"deltaAlpha": @.025}.mutableCopy;
SKAction *a = [SKAction runBlock:^{
SKColor *color = shape.fillColor;
CGFloat delta = [shape.userData[@"deltaAlpha"] floatValue];
CGFloat w, alpha; [color getWhite:&w alpha:&alpha];
shape.fillColor = [color colorWithAlphaComponent:alpha + delta];
if ((delta < 0 && alpha <= [shape.userData[@"minAlpha"] floatValue]) ||
(delta > 0 && alpha >= [shape.userData[@"maxAlpha"] floatValue])) {
shape.userData[@"deltaAlpha"] = @(-delta);
}
}];
SKAction *slice = [SKAction sequence:@[a, [SKAction waitForDuration:0.05]]];
SKAction *glow = [SKAction repeatActionForever:slice];
[shape runAction:glow];
回答5:
I found GOR's answer above helpful, who found Paddy Collin's answer above helpful, so I extended it to animate through an array of colors.
extension SKAction {
func multipleColorTransitionAction(colors:[SKColor], duration:Double) -> SKAction {
guard colors.count > 1 else { return SKAction.colorize(withColorBlendFactor: 1, duration: 0) }
var colorActions:[SKAction] = []
for i in 1..<colors.count {
colorActions.append( colorTransitionAction(fromColor: colors[i-1] , toColor: colors[i], duration: duration/Double(colors.count)) )
}
colorActions.append(colorTransitionAction(fromColor: colors.last!, toColor: colors.first!, duration: duration/Double(colors.count)))
return SKAction.sequence(colorActions)
}
func colorTransitionAction(fromColor : SKColor, toColor : SKColor, duration : Double = 0.4) -> SKAction {
func lerp(_ a : CGFloat, b : CGFloat, fraction : CGFloat) -> CGFloat { return (b-a) * fraction + a }
var frgba:[CGFloat] = [0,0,0,0]
var trgba:[CGFloat] = [0,0,0,0]
fromColor.getRed(&frgba[0], green: &frgba[1], blue: &frgba[2], alpha: &frgba[3])
toColor.getRed(&trgba[0], green: &trgba[1], blue: &trgba[2], alpha: &trgba[3])
return SKAction.customAction(withDuration: duration, actionBlock: { (node : SKNode!, elapsedTime : CGFloat) -> Void in
let fraction = CGFloat(elapsedTime / CGFloat(duration))
let transColor = UIColor(red: lerp(frgba[0], b: trgba[0], fraction: fraction),
green: lerp(frgba[1], b: trgba[1], fraction: fraction),
blue: lerp(frgba[2], b: trgba[2], fraction: fraction),
alpha: lerp(frgba[3], b: trgba[3], fraction: fraction))
(node as! SKShapeNode).fillColor = transColor
})
}
}
I changed a few things about GOR's original function to accommodate for multiple colors, mainly encapsulating frgba
, trgba
, and lerp
so the block doesn't need a reference to self
, and to allow frgba
and trgba
to have multiple instances captured by multiple blocks. Here is an example usage:
let theRainbow:[SKColor] = [.red,.orange,.yellow,.green,.cyan,.blue,.purple,.magenta]
let rainbowSequenceAction = SKAction.multipleColorTransitionAction(colors: theRainbow, duration: 10)
star.run(SKAction.repeatForever(rainbowSequenceAction))
Where star
is an SKShapeNode
.
回答6:
Adding to mogelbuster's answer, which is adding to GOR's answer, which is adding to Patrick Collins' answer.
Swift 4
func shapeColorChangeAction(from fromColor: UIColor, to toColor: UIColor, withDuration duration: TimeInterval) -> SKAction {
func components(for color: UIColor) -> [CGFloat] {
var comp = color.cgColor.components!
// converts [white, alpha] to [red, green, blue, alpha]
if comp.count < 4 {
comp.insert(comp[0], at: 0)
comp.insert(comp[0], at: 0)
}
return comp
}
func lerp(a: CGFloat, b: CGFloat, fraction: CGFloat) -> CGFloat {
return (b-a) * fraction + a
}
let fromComp = components(for: fromColor)
let toComp = components(for: toColor)
let durationCGFloat = CGFloat(duration)
return SKAction.customAction(withDuration: duration, actionBlock: { (node, elapsedTime) -> Void in
let fraction = elapsedTime / durationCGFloat
let transColor = UIColor(red: lerp(a: fromComp[0], b: toComp[0], fraction: fraction),
green: lerp(a: fromComp[1], b: toComp[1], fraction: fraction),
blue: lerp(a: fromComp[2], b: toComp[2], fraction: fraction),
alpha: lerp(a: fromComp[3], b: toComp[3], fraction: fraction))
(node as! SKShapeNode).fillColor = transColor
})
}
来源:https://stackoverflow.com/questions/20872556/skshapenode-animate-color-change