SKShapeNode - 动画变换颜色

24

我正在使用SpriteKit制作游戏。 我正在使用SKShapeNode绘制形状。现在我想要动画地改变其颜色,但SKActions对于SKShapeNode不起作用。有没有办法做到这一点,或者我必须使用不同的方法?

谢谢。

编辑:

感谢LearnCocos2D,我能够想出这个快速(并且完全不完美)的解决方案。

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]];
}];

我只需要淡化两个特定颜色,所以这不是一个通用的解决方案,但现在足够了。

谢谢

6个回答

35

已更新至Swift 4.2

这是你需要放置在扩展文件或类似位置的通用代码。

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
        }
        )
    }
}

使用方法:

redSKSpriteNodeThatBecomesBlue.run(SKAction.colorTransitionAction(fromColor: .red, toColor: .blue, duration: 5))

请注意,对于SKShapeNode或其他用途,您需要重写此行以适应您的需求:

(node as? SKSpriteNode)?.color = transColor

此解决方案最初受Paddy Collins答案的启发。


谢谢!这个完美无缺。 - BuzzCloudAU
在Swift 4中,除非在构建设置中将“内存独占访问”设置为“无执行”,否则会导致“同时访问...”错误。 - Tim
我会在晚上检查,谢谢提醒,Tim。 - OwlOCR
@Tim 看起来问题在于旧实现使用相同的数组来存储所有输入颜色组件。请检查更新版本,它使用结构体代替。根据快速测试,似乎现在运行良好。 - OwlOCR

16
使用customActionWithDuration:block:并更改fillColorstrokeColor属性。
我认为着色操作不起作用,因为SKShapeNode没有color属性。在子类或分类中将此属性添加到类中,并将其重定向到fillColorstrokeColor或两者都尝试一下可能值得一试。

@Aaron 感谢你的编辑...在iPad上添加语法高亮真是太烦人了。 - CodeSmile
是的,我希望苹果能够添加一个Markdown键盘...不过这永远不会发生。 - Aaron Brager
如果我能将选择弹出窗口(复制、粘贴等)设置为出现在选择下方而不是上方就好了。它通常会挡住我在编辑框中点击任何标记图标,这就是为令我在iPad上很少正确格式化的原因。 - CodeSmile
2
这个解决方案不会使颜色变化动画化,它只是瞬间发生。 - bpn
bpn,您可能需要将持续时间设置为3.0(秒),然后根据传递到块中的时间值,将该值设置为颜色过渡的百分比。 - bshirley

8

以下是我的实现。我认为它更易于阅读。

-(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;
}

非常有帮助。我将其转换为SKAction类别,并在方法签名中包含了一个duration参数。 - Paulw11
1
这里有一个分类版本(使用 lerp 宏):https://gist.github.com/warpling/fde6a753d6c3ef35b22b00aff4698a4b。 - Warpling

3

在mogelbuster的答案上,GOR做了补充,而GOR的答案又是在Patrick Collins的答案上做的补充。

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
    })
}

3

我想在我的多边形上持续地闪烁发光。我使用了runBlock:而不是customActionWithDuration:block:,但实际上也可以用后者无限循环。

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];

2

我发现上面GOR的回答很有帮助,他发现Paddy Collin的回答也很有帮助,所以我将其扩展为可以通过一系列颜色进行动画。

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
    })
}
}

我对GOR的原始函数进行了一些修改以适应多种颜色,主要是封装frgbatrgbalerp,这样块就不需要引用self,并且允许frgbatrgba被多个块捕获。以下是一个示例用法:

let theRainbow:[SKColor] = [.red,.orange,.yellow,.green,.cyan,.blue,.purple,.magenta]
let rainbowSequenceAction = SKAction.multipleColorTransitionAction(colors: theRainbow, duration: 10)
star.run(SKAction.repeatForever(rainbowSequenceAction))

这里的star是一个SKShapeNode


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接