Nonoku - Creating a Background

So, I was getting bored of looking at a grey background and, as a break from cleaning up, refactoring, and adding the fiddly (nut necessary) UI code, I decided to run up a quick background.

The original version of Nonoku had a vertical gradient fill, light to dark, with subtle diagonal striping. I liked this so decided to reproduce it. However, it made sense to me to implement this in code. Since I’m using SpriteKit already, rendering to an SKEffectNode which rasterises the nodes made sense (the background is not animated so I can basically update once and be done).

There’s no native way, as far as I’m aware, to draw a gradient on a node, so the first thing would be to create an SKTexture. Again, since this is a one time deal, I’m not too worried. I’ve done something similar before so I already knew I could use a CIFilter to generate this. From that, I can get a CIImage. That can be passed to a CIContext to create a CGImage which can finally be passed to the SKTexture which I’ll pass into an SKSprite.

From reviewing the CIFilter documentation, I can get a list of available filters as follows:

class func filterNames(inCategory category: String?) -> [String]

Even better, under the list of category constants, there is kCICategoryGradient. For this kind of quick exploratory code, I usually start a Swift Playground to spike out the things I don’t know.

So, skipping a step, I have this:

print("\(CIFilter.filterNames(inCategory: kCICategoryGradient))")
let filter = CIFilter(name: "CILinearGradient")
print("\(filter?.attributes)")

The list of filterNames has a couple of likely contenders, CILinearGradient and CISmoothLinearGradient. They take the same attributes (two colours, and two vectors) so I ended up trying both. In my use case I couldn’t see any difference between the two so decided to stick with CILinearGradient. If it ever comes up as an issue, it’s a very simple change to make.

CIFilter.attributes() gives me a list of the supported attributes, and a brief description of them. That's enough to define how I'm going to use it so I can leave the playground and come back to my code.

Since I want to create a new SKTexture with this gradient it makes sense to do it as an extension on SKTexture. I need the size, the start colour, and the end colour. I could add additional logic here but, following YAGNI (You Ain’t Gonna Need It), I am only interested in a vertical gradient so that’s all I’ll support.

extension SKTexture {
    convenience init?(size: CGSize, color1: SKColor, color2: SKColor) {
        guard let filter = CIFilter(name: "CILinearGradient") else {
            return nil
        }
        let startVector = CIVector(x: 0, y: size.height)
        let endVector = CIVector(x: 0, y: 0)
        filter.setValue(startVector, forKey: "inputPoint0")
        filter.setValue(endVector, forKey: "inputPoint1")
        filter.setValue(CIColor(color: color1), forKey: "inputColor0")
        filter.setValue(CIColor(color: color2), forKey: "inputColor1")

        let context = CIContext(options: nil)
        let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
        guard let filterImage = filter.outputImage,
            let image = context.createCGImage(filterImage, from: rect) else {
            return nil
        }

        self.init(cgImage: image)
    }
}

Interestingly, whilst I can just get a ciColor from a UIColor (SKColor aliases UIColor), I can’t use it in the CIFilter due to the following issue.

*** -CIColor not defined for the UIColor UIExtendedSRGBColorSpace 0.666667 0.223529 0.223529 1; need to first convert colorspace.

Instead, I had to create a new instance of CIColor with the UIColor. I’m not sure if there are any issues associated with this, but it’s working fine for me so far.

From there, I just created an SKSpriteNode with the generated texture. The lines were just stroked SKShapeNodes with a CGPath which just have a defined start and end point.

So here’s how it looks:

It’s still early so I’m not sure if this is the final version but it does make the game easier to play as it’s much easier to tell where the empty spaces are. Also, with this approach I can easily generate backgrounds with different colours so I have a bunch of options.

It’s still early so I’m not sure if this is the final version but it does make the game easier to play as it’s much easier to tell where the empty spaces are. Also, with this approach I can easily generate backgrounds with different colours so I have a bunch of options.