ios - SKShapeNode-Animar el cambio de color
objective-c sprite-kit (6)
Estoy trabajando en un juego con SpriteKit. Estoy dibujando una forma con SKShapeNode. Ahora quiero animar su cambio de color, pero SKActions no funciona para SKShapeNode. ¿Hay alguna manera de hacer esto o tengo que usar un enfoque diferente?
Gracias.
EDITAR:
Gracias a LearnCocos2D pude encontrar esta solución rápida (y totalmente no perfecta).
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]];
}];
Solo necesitaba fundir dos colores específicos, por lo que no es una solución universal, pero es suficiente por ahora.
Gracias
Agregando a la respuesta de mogelbuster, que se agrega a la respuesta de GOR, que se agrega a la respuesta de 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
})
}
Aquí está mi implementación. Creo que un poco más fácil de leer.
-(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;
}
Encontré útil la respuesta de GOR anterior, quien encontró útil la respuesta anterior de Paddy Collin, así que la extendí para animar a través de una variedad de colores.
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
})
}
}
Cambié algunas cosas sobre la función original de GOR para acomodar múltiples colores, principalmente encapsulando frgba
, trgba
y lerp
para que el bloque no necesite una referencia a self
, y para permitir que frgba
y trgba
tengan múltiples instancias capturadas por múltiples bloques. Aquí hay un ejemplo de uso:
let theRainbow:[SKColor] = [.red,.orange,.yellow,.green,.cyan,.blue,.purple,.magenta]
let rainbowSequenceAction = SKAction.multipleColorTransitionAction(colors: theRainbow, duration: 10)
star.run(SKAction.repeatForever(rainbowSequenceAction))
Donde la star
es un SKShapeNode
.
Quería un brillo continuo en mi polígono. Utilicé runBlock:
lugar de customActionWithDuration:block:
pero podría haberse ejecutado infinitamente con ese.
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];
Use customActionWithDuration:block:
y cambie las propiedades fillColor
o strokeColor
.
Supongo que las acciones de colorear no funcionarán porque SKShapeNode no tiene propiedad de color
. Vale la pena intentar agregar esta propiedad a la clase en una subclase o categoría y redirigirla a fillColor
o strokeColor
o ambos.
Actualizado para Swift 4.2
Este es el código común que necesita colocar en un archivo de extensión o algo por el estilo.
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
}
)
}
}
Uso:
redSKSpriteNodeThatBecomesBlue.run(SKAction.colorTransitionAction(fromColor: .red, toColor: .blue, duration: 5))
Tenga en cuenta que para SKShapeNode u otros fines, debe volver a escribir esta línea para satisfacer sus necesidades:
(node as? SKSpriteNode)?.color = transColor
Esta solución está muy inspirada originalmente por la respuesta de Paddy Collins.