How do you choose colors for a color palette?

For context, I have a game where a player can build whatever on their plot of land. I would like to support a color wheel, however, I don’t want to include one due to how little it can be serialized when it’s saved to a datastore.

More on the serialization issue, and why saving colors suck.

When saving a Color3.FromRGB value, you’ll start with this:
Color3.fromRGB(153, 167, 135)

This can’t be saved in datastores currently, and you’d have to serialize it. It’d look something like this:
C = "153:167:135"

You can serialize this further, but you’re never going to get it smaller than 153:167:135, without some sort of compressor, which means it’s stuck at a minimum of 11 characters.

However, using preset colors, I can make it as short as this:
C = 53

Then when the datastore script is loading the color for the part, it can use a global module script that can convert this ID to a real color. Like so:

C=53[53] = Color3.fromRGB(153, 167, 135)

Changing 11 characters to only 2-3 characters, is a huge gain when saving data, allowing you to save more parts and objects. With 3 characters per object color, you can have up to 999 preset colors, and even more if you use the IDs weirdly, like 007 and 7.

Every game is different when serializing data, some don’t even have to, but with plots of land and building, you have to save position, rotation, color and more for every object, so serialization is usually a must have if you want them to be able to build their hearts out with little to no limit.

The issue is I don’t know how to include every color that players could possibly want. Like getting a slightly dark green versus a slightly desaturated green, and then doing this for every color plus monochromatics.

I tried going to color palette websites but at some point I started getting too many blues or too many reds that started to look identical.

Is there any way to generate a color palette that has all colors that I could possibly need, but with tons of different shades and values?

Actually, you can serialize it even further. You’re using strings to store it, but those are actually numbers. If you want to continue with the strings route, you can shorten it by using hex codes pretty easily with Color3.fromHex and color3value:ToHex()
You don’t need commas this way either.

But I think your best bet for optimum storage space is to use string.pack:

local pack_string = "BBB" -- represents 3 numbers, all between 0 and 255.
local color = Color3.fromRGB(255, 127, 101)
local compressed = pack_string:pack(
    math.floor(color.r*255+0.5), -- get the 0-255 integer to save
-- encoded into an ascii string that is only 3 letters long.
print(compressed) --> weird symbols
local unpacked_color = Color3.fromRGB(pack_string:unpack(compressed))

This is really interesting, I was looking into string manipulation for a solution like this but string.pack is under explained in the docs.

My only concern is because it’s compressing into a value that in my eyes is unreadable, that if string.pack had its functionality changed (doubtful, but I like to future-proof my code), I would have tons of parts with broken colors.

This is the same reason why I didn’t want to use a string compressor, like LZW, to save more data, as I wouldn’t know how to uncompress it if it broke. I like to code, but I may just draw the line at unreadable compression.

I think what I’ll do is stick with the :ToHex() solution, atleast that way I know what I’m saving while being able to shave off 5 characters from the original 11. The extra 3 characters compared to your secondary solution might hurt but that’s nothing compared to the pro of giving a player complete control of their objects color while never having to worry about it breaking.

Thanks for this + your summary of how it works! :slight_smile:

1 Like

So just FYI, I agree with your readability reason but I would like to point out that string.pack is super simple in this case and you wouldn’t be screwed over if it broke.

Here’s a list of ASCII letters/characters in Extended ASCII format.

Each letter or special symbol on this chart can be represented by a single byte, that is a number ranging from 000-255 or from hexadecimal 00-FF. So when the RGB color is (125, 101, 63), the corresponding string would be }e?. This is as compressed as you can make it without losing data, because a 3-byte color is represented as a 3-byte string. This is half the size of the hex codes, which are 6-byte strings.

For sustainability, you can replace pack and unpack with char and byte:

local r, g, b = 125, 101, 63
local compressed = string.char(r) .. string.char(g) .. string.char(b)
--> }e?
local r = compressed:sub(1,1):byte()
local g = compressed:sub(2,2):byte()
local b = compressed:sub(3,3):byte()

Like I said though, I agree that readability is important and you do what you want to do. Happy to help!

1 Like