Reputation:
I am making a game similar to the stack game on iOS (https://apps.apple.com/us/app/stack/id1080487957).
In the screenshot, each box is in a color slightly different from the box below. The background is also changing, in case of conflict of background and boxes.
I am wondering how to generate this sequence of color palette for the boxes, and a corresponding background color that helps to make the box stand out.
What I have tried is very naive:
UIColor(
red: ((0 + boxCount) % 255)/255,
green: ((100 + boxCount) % 255)/255,
blue: ((200 + boxCount) % 255)/255)
)
This doesn't look good, and I think the reasons are:
But what's good about this is that it avoids grayscale color such as completely black, so that the boxes are always colorful.
Is there any better way for this problem?
EDIT:
To clarify, the color is not just lighter or darker. It can actually change color (e.g. the first screenshot changes from sky blue to greenish-yellow).
Upvotes: 0
Views: 527
Reputation: 347314
A very long time ago, in a different programming language, I spent way too much time trying to come up with a blending algorithm which "worked" the way I expected it to.
Sooo, I spent a lot of time fiddling with it, until I came up with an approach which worked for me. So I've dragged the concept over to UIKit, for example.
Now, this is a little basic. I'd considered writing an extension to make it easier to get the RGB colors, in a more "structured" way, as well as adding the blending algorithm as an extensions to UIColor
, but you get the basic idea.
I've also included a basic darken
and lighten
algorithm, based on the same concept as the blend. In fact, if you have the color and know the number of samples (ie total number of bricks), you could easily just darken or lighten the base color by that factor, but where's the fun in that :P
Put the code in a playground and see what you get
import UIKit
public extension UIColor {
func darken(by: Double) -> UIColor {
let rgb = cgColor.components
let red = max(0, (rgb?[0])! - CGFloat(1.0 * by))
let green = max(0, (rgb?[1])! - CGFloat(1.0 * by))
let blue = max(0, (rgb?[2])! - CGFloat(1.0 * by))
let alpha = cgColor.alpha
return UIColor(red: red, green: green, blue: blue, alpha: alpha)
}
func brighten(by: Double) -> UIColor {
let rgb = cgColor.components
let red = min(1.0, (rgb?[0])! + CGFloat(1.0 * by))
let green = min(1.0, (rgb?[1])! + CGFloat(1.0 * by))
let blue = min(1.0, (rgb?[2])! + CGFloat(1.0 * by))
let alpha = cgColor.alpha
return UIColor(red: red, green: green, blue: blue, alpha: alpha)
}
}
func blend(from lhs: UIColor, to rhs: UIColor, by weight: Double) -> UIColor {
var lhsRed: CGFloat = 0
var lhsGreen: CGFloat = 0
var lhsBlue: CGFloat = 0
var lhsAlpha: CGFloat = 0
lhs.getRed(&lhsRed, green: &lhsGreen, blue: &lhsBlue, alpha: &lhsAlpha)
var rhsRed: CGFloat = 0
var rhsGreen: CGFloat = 0
var rhsBlue: CGFloat = 0
var rhsAlpha: CGFloat = 0
rhs.getRed(&rhsRed, green: &rhsGreen, blue: &rhsBlue, alpha: &rhsAlpha)
let inverseWeight = 1.0 - weight
let red = min(1.0, max(0.0, lhsRed * weight + rhsRed * inverseWeight))
let green = min(1.0, max(0.0, lhsGreen * weight + rhsGreen * inverseWeight))
let blue = min(1.0, max(0.0, lhsBlue * weight + rhsBlue * inverseWeight))
let alpha = min(1.0, max(0.0, lhsAlpha * weight + rhsAlpha * inverseWeight))
return UIColor(red: red, green: green, blue: blue, alpha: alpha)
}
let from = UIColor.red.darken(by: 0.5)
let to = UIColor.red.brighten(by: 0.5)
blend(from: from, to: to, by: 0.1)
blend(from: from, to: to, by: 0.2)
blend(from: from, to: to, by: 0.3)
blend(from: from, to: to, by: 0.4)
blend(from: from, to: to, by: 0.5)
blend(from: from, to: to, by: 0.6)
blend(from: from, to: to, by: 0.7)
blend(from: from, to: to, by: 0.8)
blend(from: from, to: to, by: 0.9)
For example, in the first screenshot in the link, it changes from sky blue on the bottom to greenish-yellow on the top)
Sure, so change the from color to .blue
and the end color to .yellow
or .green
and see what you get
I'd also suggesting looking a things like SceneKit gradients as this might change your approach to the problem
Any idea how to make it an infinite sequence? (given input of boxCount which is number of boxes already placed in the tower). I am thinking about having an "anchorColors" array, then gradually blending from ith anchorColor to i+1th anchorColor using your blend function. What do you think? Oh, the boxCount will be incrementing as we keep placing the box on top of the tower. And the color of a given box will never change.
Much of this will come down to your own choices, designs and long term intentions.
If you have a unknown number of elements, you could start with a series of "base" colors. Then you could allocate n
number of deviations between them, giving your a different gradient over a very wide range of colors.
For example, if you had 64 base colors, with 128 segments between each base colour you get something like 8192 colors ... So each time you use up a series of segments, you'd move onto the next base color pair (the old end color would become the new start color and the next color would become the end color)
Upvotes: 4