Swift : 动画 SKSpriteNode 渐变

Posted

技术标签:

【中文标题】Swift : 动画 SKSpriteNode 渐变【英文标题】:Swift : Animate SKSpriteNode Gradient 【发布时间】:2016-03-27 10:31:11 【问题描述】:

我想做的事情对我来说似乎很简单,但鉴于我对 ios 还很陌生,我无法找到实现它的方法。

我几乎希望我的背景有一个渐变,它可以通过非常缓慢地淡入和淡出来改变颜色。

我在 github 上找到了这个并用它创建了两个不同颜色的渐变节点,唯一的问题是我似乎无法为其 alpha 设置动画或以任何方式淡入和淡出。 https://github.com/braindrizzlestudio/BDGradientNode 这是我所做的:

//Blueish
    let color1 = UIColor(red: 0.965, green: 0.929, blue: 0.667, alpha: 1)
    let color2 = UIColor(red: 0.565, green: 0.71, blue: 0.588, alpha: 1)
    let color3 = UIColor(red: 0.259, green: 0.541, blue: 0.529, alpha: 1)
    let colors = [color1, color2, color3]

    //Redish
    let color4 = UIColor(red: 1, green: 0.518, blue: 0.769, alpha: 1)
    let color5 = UIColor(red: 0.859, green: 0.22, blue: 0.541, alpha: 1)
    let color6 = UIColor(red: 0.737, green: 0, blue: 0.314, alpha: 1)
    let colors2 = [color4, color5, color6]

    let blending : Float = 0.3

    let location1 : CGFloat = 0.5
    let locations : [CGFloat] = [location1]

    let startPoint = CGPoint(x: 0.3, y: 0.0)
    let endPoint = CGPoint(x: 0.6, y: 0.8)

    let size = CGSize(width: self.size.width, height: self.size.height)

    let texture = SKTexture(imageNamed: "White Background")


    myGradientNode = BDGradientNode(linearGradientWithTexture: texture, colors: colors, locations: nil, startPoint: startPoint, endPoint: endPoint, blending: blending, keepTextureShape: true, size: size)

    myGradientNode2 = BDGradientNode(linearGradientWithTexture: texture, colors: colors2, locations: nil, startPoint: startPoint, endPoint: endPoint, blending: blending, keepTextureShape: true, size: size)
    myGradientNode2?.blending = 0

    self.addChild(myGradientNode2!)
    self.addChild(myGradientNode!)

我觉得有一个更简单的方法,也许不使用这个。 您现在在很多游戏应用程序中都看到了这一点,如果有人能帮助我实现这个令人惊叹的设计,那将非常有帮助!

【问题讨论】:

【参考方案1】:

这是一个可能的完整解决方案(在 Swift 2.2 中),您可以根据自己的喜好对其进行调整(包括对您的应用有意义的错误处理)。我将以相反的顺序排列代码,从能够编写的目标开始:

// assuming:
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)

if let bg = HorizontalGradientNode(
    size: scene.size,
    fadingFrom: [.AliceBlue, .BurlyWood],
    to: [.AliceBlue, .CornflowerBlue],
    wait: 1,
    duration: 2)

    scene.addChild(bg)

这将在场景中添加一个节点,其中包含两个子渐变精灵节点,其中最上面的一个将永远淡入淡出。 HorizontalGradientNode.init 采用两个 CGColor 数组。它们可以分别包含两种或更多颜色!命名为CGColors(AliceBlueCornflowerBlue等)可以通过以下方式构造:

public extension CGColor 

    public static func withHex(hex: Int, alpha: CGFloat = 1) -> CGColor 
        let x = max(0, min(hex, 0xffffff))
        let r = CGFloat((x & 0xff0000) >> 16)   / 0xff
        let g = CGFloat((x & 0x00ff00) >> 8)    / 0xff
        let b = CGFloat( x & 0x0000ff)          / 0xff
        return CGColorCreateGenericRGB(r, g, b, alpha)
    

    public static var AliceBlue: CGColor  return .withHex(0xF0F8FF) 
    public static var BurlyWood: CGColor  return .withHex(0xDEB887) 
    public static var CornflowerBlue: CGColor  return .withHex(0x6495ED) 
    // see named colours at: http://www.w3schools.com/colors/colors_names.asp

HorizontalGradientNode 的最简单版本类似于:

class HorizontalGradientNode : SKNode 

    init?(size: CGSize, fadingFrom colors1: [CGColor], to colors2: [CGColor], wait: NSTimeInterval, duration: NSTimeInterval) 
        guard
            let grad1 = CGImage.withHorizontalGradient(size: size, colors: colors1),
            let grad2 = CGImage.withHorizontalGradient(size: size, colors: colors2)
            else
        
            return nil
        
        let bg1 = SKSpriteNode(texture: SKTexture(CGImage: grad1))
        let bg2 = SKSpriteNode(texture: SKTexture(CGImage: grad2))
        bg2.alpha = 0

        super.init()
        addChild(bg1)
        addChild(bg2)

        bg2.runAction(
            .repeatActionForever(
                .sequence(
                    [
                        .waitForDuration(wait),
                        .fadeInWithDuration(duration),
                        .waitForDuration(wait),
                        .fadeOutWithDuration(duration)
                    ]
                )
            )
        )
    

    required init?(coder aDecoder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

这个CGImage.withHorizontalGradient(size:, colors:)可以如下实现:

public extension CGImage 

    public static func withHorizontalGradient(size size: CGSize, colors: [CGColor]) -> CGImage? 
        guard colors.count >= 2 else  return nil 
        let locations: [CGFloat] = colors.indices.map
            CGFloat($0) / CGFloat(colors.count - 1)
        
        guard let gradient = CGGradientCreateWithColors(nil, colors, locations) else 
            return nil
        
        let start = CGPoint(x: size.width / 2, y: size.height)
        let end = CGPoint(x: size.width / 2, y: 0)
        return CGContext.rgb(size)?
            .draw(linearGradient: gradient, start: start, end: end)
            .image
    

使用CGContext 的点语法扩展,例如:

public extension CGContext 

    public static func rgb(size: CGSize) -> CGContext? 
        let sp = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)
        let bi = CGImageAlphaInfo.PremultipliedLast.rawValue
        return CGBitmapContextCreate(nil, Int(ceil(size.width)), Int(ceil(size.height)), 8, 0, sp, bi)
    

    public var image: CGImage?  return CGBitmapContextCreateImage(self) 

    public func draw(linearGradient
        gradient: CGGradient?,
        start: CGPoint,
        end: CGPoint,
        options: CGGradientDrawingOptions = [.DrawsBeforeStartLocation, .DrawsAfterEndLocation]
        ) -> CGContext
    
        CGContextDrawLinearGradient(self, gradient, start, end, options)
        return self
    

如果您想在 Playground 中尝试此操作,您可以将所有内容粘贴进去,并以以下 sn-p(对于 OS X)为前缀,确保在 Assistant 编辑器中拥有 Playground 的时间线:

import XCPlayground
import Cocoa
import SpriteKit

let scene = SKScene(size: CGSize(width: 400, height: 400))
let view = SKView(frame: CGRect(origin: .zero, size: scene.size))
XCPlaygroundPage.currentPage.liveView = view
view.presentScene(scene)

【讨论】:

哇!!!!你是非凡的!但是,我确实收到“return CGColorCreateGenericRGB(r, g, b, alpha)”的错误,说它不可用。有什么解决方法吗?我像苹果文档所说的那样导入了 CoreGraphics,但仍然没有。 明白了!使用这个:返回 UIColor(red: r, green: g, blue: b, alpha: alpha).CGColor。非常感谢你!我真的很感激这个! 其实最后一个问题!现在它从 AliceBlue & BurlyWood 到 AliceBlue 和 CornflowerBlue。什么是创建另一组以使用和使用不同颜色的最佳方法。例如,在它从 AliceBlue & BurlyWood 到 AliceBlue 和 CornflowerBlue 之后,我希望它从 Red & Yellow 变为 Green & Blue。我会实例化另一个 Horizo​​ntalGradientNode 吗? 不客气!至于问题:请注意,这是一个Cocoa 示例(用于 OS X 游乐场),但您似乎已经弄清楚了。让我知道您是否需要任何帮助将其转换为 iOS。至于要循环使用的额外颜色集,我实际上会修改HorizontalGradientNode 以取而代之的是[[CGColor]][CGColor]...(对于任意数量的颜色集)。你想让我相应地改变答案还是你已经明白了? 是的,您介意更改答案吗?天哪,拜托!我对这一切都很陌生,你帮了很多忙!谢谢!我实际上将它集成到我的 iOS 应用程序中,它看起来/工作得很好!【参考方案2】:

在我最初回答@OriginalAlchemist 之后的cmets 中询问HorizontalGradientNode 是否可以修改为循环超过两组颜色。这是一种方法(假设我的第一个答案中的CGImage.withHorizontalGradient(size:colors:) 可用):

class HorizontalGradientNode : SKNode 

    init(size: CGSize, gradientColorSets: [[CGColor]], wait: NSTimeInterval, duration: NSTimeInterval) 
        super.init()

        var i = 0
        let setNextGradientTexture = SKAction.customActionWithDuration(0)  (node, _) in
            guard let sprite = node as? SKSpriteNode else  return 
            let colors = gradientColorSets[i]
            i = (i + 1) % gradientColorSets.count
            guard let image = CGImage.withHorizontalGradient(size: size, colors: colors) else  return 
            sprite.texture = SKTexture(CGImage: image)
            sprite.size = sprite.texture!.size()
        

        let sprite1 = SKSpriteNode()
        let sprite2 = SKSpriteNode()

        sprite1.runAction(setNextGradientTexture)

        sprite2.alpha = 0
        sprite2.runAction(
            .repeatActionForever(
                .sequence(
                    [
                         setNextGradientTexture,
                        .waitForDuration(wait),
                        .fadeInWithDuration(duration),
                        .runBlock( sprite1.runAction(setNextGradientTexture) ),
                        .waitForDuration(wait),
                        .fadeOutWithDuration(duration)
                    ]
                )
            )
        )

        addChild(sprite1)
        addChild(sprite2)
    

    required init?(coder aDecoder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

用法:

// assuming
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)

let gradientColorSets: [[CGColor]] = [
    [.AliceBlue, .BurlyWood], // note that you can have more than two colours per set
    [.BurlyWood, .CornflowerBlue],
    [.CornflowerBlue, .ForestGreen],
    [.ForestGreen, .AliceBlue]
]

let bg = HorizontalGradientNode(size: scene.size, gradientColorSets: gradientColorSets, wait: 1, duration: 2)
scene.addChild(bg)

@OriginalAlchemist 还要求提供名为CGColors 的 iOS 兼容版本:

public extension CGColor 

    public static func withHex(hex: Int, alpha: CGFloat = 1) -> CGColor 
        let x = max(0, min(hex, 0xffffff)) // allows force unwrapping of the return value
        let r = CGFloat((x & 0xff0000) >> 16)   / 0xff
        let g = CGFloat((x & 0x00ff00) >> 8)    / 0xff
        let b = CGFloat( x & 0x0000ff)          / 0xff
        let cp = CGColorSpaceCreateDeviceRGB()
        return CGColorCreate(cp, [r, g, b, alpha])!
    

    public static var AliceBlue: CGColor  return .withHex(0xF0F8FF) 
    public static var BurlyWood: CGColor  return .withHex(0xDEB887) 
    public static var CornflowerBlue: CGColor  return .withHex(0x6495ED) 
    public static var ForestGreen: CGColor  return .withHex(0x228B22) 
    // see named colours at: http://www.w3schools.com/colors/colors_names.asp

【讨论】:

哇,你真是个了不起的人。非常感谢。这很有帮助。这是我的电子邮件:nadalalyafaie@gmail.com。您可以给我发电子邮件并保持联系,我会记住这一点,如果有机会出现,我知道该去找谁!!此外,当且仅当您有时间并且感觉非常慷慨时,我在这里问了另一个问题并希望得到一些帮助:***.com/questions/36234960/…。再次非常感谢!哇!

以上是关于Swift : 动画 SKSpriteNode 渐变的主要内容,如果未能解决你的问题,请参考以下文章

Swift - 使用纹理数组更改 SKSpriteNode 纹理

如何使用动画回收 SKSpriteNode

将 SKSpriteNode 移动 Y - Swift 2 + SpriteKit

如何通过 Sprite Kit 中的 CGPoints 数组制作动画 - Swift

过滤 SKSpriteNode 的最近邻居和所述节点的大小的困难(swift,spritekit)

获取 SKPhysicsBody 以在纹理动画时更新(Swift)