Home > Mobile >  Gradient progress bar with rounded corners SpriteKit Swift
Gradient progress bar with rounded corners SpriteKit Swift

Time:02-28

I'm trying to build a gradient progress bar with rounded corners in SpriteKit, but I'm completely stuck at this point. I've tried different combinations of SKCropNode, SKShapeNodes etc. but I can't seem to get it to work. Any help is appreciated, kind regards!

CodePudding user response:

It's about SKCropNode its maskNode property. From the docs:

SKCropNode is a container node that you use to crop other nodes in the scene. You add other nodes to a crop node and set the crop node's maskNode property. For example, here are some ways you might specify a mask:

An untextured sprite that limits content to a rectangular portion of the scene.

A textured sprite that works as a precise per-pixel mask.

A collection of child nodes that form a unique shape.

You can animate the shape or contents of the mask to implement interesting effects such as hiding or revealing.

So, a simple example would be like this:

    class GameScene: SKScene {
        
        override func sceneDidLoad() {
            
            super.sceneDidLoad()
            createProgressBar()
        }
        
        private func createProgressBar(){
            
            let barFrame = CGRect(x: 0, y: 0, width: 300, height: 15)
            
            if let cgImage = createImage(frame: barFrame) {
                
                let texture = SKTexture(cgImage: cgImage)
                
                let sprite = SKSpriteNode(texture: texture)
                let cropNode = SKCropNode()
                let mask = SKSpriteNode(color: .gray, size: barFrame.size)
                
                cropNode.addChild(sprite)
                cropNode.maskNode = mask
                
                sprite.anchorPoint = CGPoint(x: 0.0, y: 0.5)
                mask.anchorPoint = CGPoint(x: 0.0, y: 0.5)
                
                var counter:Double = 0
                let action = SKAction.run {[weak self, sprite] in
                    guard let `self` = self, counter < 100 else {
                    sprite?.removeAction(forKey: "loop")
                      return
                
                  }
                    
                    counter  = 1
                    let newWidth = self.getWidth(percents: counter, spriteWidth: barFrame.width)
                    print("Bar width \(newWidth), percentage \(counter)")
                    
                    mask.size = CGSize(width: newWidth, height: barFrame.height)
                }
                let wait = SKAction.wait(forDuration: 0.05)
                let sequence = SKAction.sequence([wait, action])
                
                let loop = SKAction.repeatForever(sequence)
                
                
                
                addChild(cropNode)
                cropNode.position = CGPoint(x: self.frame.width / 2.0, y: self.frame.height / 2.0)
                
                sprite.run(loop, withKey: "loop")
                
                
            }
        }
        
        private func getWidth(percents:Double, spriteWidth:Double)->Double{
            
            let onePercent = spriteWidth / 100.0
            
            return onePercent * percents
        }
        
        
        private func createImage(frame barFrame:CGRect) -> CGImage?{
            
            if let ciFilter = CIFilter(name: "CILinearGradient"){
                
                let ciContext = CIContext()
                
                ciFilter.setDefaults()
                
                let startColor  = CIColor(red: 0.75, green: 0.35, blue: 0.45, alpha: 1)
                let endColor    = CIColor(red: 0.45, green: 0.35, blue: 0.75, alpha: 1)
                
                let startVector = CIVector(x: 0, y: 0)
                let endVector   = CIVector(x: barFrame.width, y: 0)
                
                ciFilter.setValue(startColor,   forKey: "inputColor0")
                ciFilter.setValue(endColor,     forKey: "inputColor1")
                ciFilter.setValue(startVector,  forKey: "inputPoint0")
                ciFilter.setValue(endVector,    forKey: "inputPoint1")
                
                if let outputImage = ciFilter.outputImage {
                    let  cgImage = ciContext.createCGImage(outputImage, from: CGRect(x: 0, y: 0, width: barFrame.width, height: barFrame.height))
                    return  cgImage
                    
                }
            }
            
            return nil
      

  }
}

Now cause this is just an example I won't go all the way to implement this right, but You can maybe make a class of it with designable and inspectable properties, optimize code, make it reusable etc. But the general idea is shown here.

You use SKCropNode to add progress bar in it, and use maskNode property to reveal progress bar as percentage increases. Also I gave a method to create texture programatically, but You can use just a .png file instead.

Crop node is here used only cause of a gradient (cause we don't wan't to scale image, but rather to show it part by part). Obviously, crop node is not needed if a progress bar had only one color.

Here is final result:

progress-bar

  • Related