Theming SpriteKit

Dirty singletons

When I first started working on TenPair I knew that I should also do something about game UI themes support. But I did not have clear idea how to approach it at that time. This resulted in following monstrosity:

 background.fillColor = TenPairTheme.currentTheme.consumedTileColor!

Well… At least colors were not hardcoded… But it wasn’t also much better. Dirty singletons everywhere… And I never got to how theme changes should be handled.

With the latest update to TenPair I wished to add at least one additional color theme. And as it was period for rethinking SpriteKitUI, it was time to address theming.

SpriteKit appearance?

UIKit has a nice way to set elements appearance app wide. For example setting navigation bar color:

UINavigationBar.appearance().barTintColor = .blue
UINavigationBar.appearance().tintColor = .white

Set once and forget. I wished to use similar pattern.

Theming

First step for identifying elements that can be themed was to introduce theming protocol

public protocol Themed {
    static func appearance() -> Appearance
    
    func set(color: SKColor, for attribute: Appearance.Attribute)
    func set(value: String, for attribute: Appearance.Attribute)
}

Appearance defines a bag of properties for every UI element based on full class name. Appearance.Attribute is user defined property key that will be used when theme is applied.

Sample usage for defining app wide View colors:

View.appearance().set(color: SKColor.white, for: .background)
View.appearance().set(color: lightBlue, for: .foreground)

During theming phase elements will receive defined properties with attribute keys:

open class View: SKSpriteNode, Themed
    open func set(color: SKColor, for attribute: Appearance.Attribute) {
        switch attribute {
        case Appearance.Attribute.background:
            backgroundColor = color
        default:
            break // no op
        }
    }
}

When ever colors need to be changed, you will need to perform appearance calls with new values and call applyTheme() on game object

Theme demo

Conclusion

Overall this allowed clear separation of theme definition form game logic and UI. If I only had more theme ideas…

Tags: ios, spritekit