3

Not exactly sure how to phrase the title but I want to generate a random color every second on an object. I also want this color to not be similar to the old color the object already has. If the current random color is the-same as the color the Object already has, re-generate the random color again. That object is simply a Text component.

This is the function I use to generate the color:

public Color genRandomColor()
{
    float r, g, b;
    r = UnityEngine.Random.Range(0f, 1f);
    g = UnityEngine.Random.Range(0f, 1f);
    b = UnityEngine.Random.Range(0f, 1f);

    return new Color(r, g, b);
}

For comparing if the color are similar, I ported and used the function from this answer to C#.

public double ColourDistance(Color32 c1, Color32 c2)
{
    double rmean = (c1.r + c2.r) / 2;
    int r = c1.r - c2.r;
    int g = c1.g - c2.g;
    int b = c1.b - c2.b;
    double weightR = 2 + rmean / 256;
    double weightG = 4.0;
    double weightB = 2 + (255 - rmean) / 256;
    return Math.Sqrt(weightR * r * r + weightG * g * g + weightB * b * b);
}

I put the random color generator in while loop inside a coroutine function to run over and over again until the generated color is not similar. I use yield return null; to wait for a frame each time I generate a random color so that it does not freeze the program.

const float threshold = 400f;
bool keepRunning = true;

while (keepRunning)
{
    Text text = obj.GetComponent<Text>();

    //Generate new color but make sure it's not similar to the old one
    Color randColor = genRandomColor();
    while ((ColourDistance(text.color, randColor) <= threshold))
    {
        Debug.Log("Color not original. Generating a new Color next frame");
        randColor = genRandomColor();
        yield return null;
    }

    text.color = randColor;
    yield return new WaitForSeconds(1f);
}

Everything seems to be working with one minor problem.

The problem is that it takes 1 to 3 seconds to re-generate a color that is not a similar to the old one sometimes. I removed yield return null; to prevent it from waiting each frame and it seems to work now but I risk freezing the whole game since I am now using the random function to control the while loop. Already, I've noticed tiny freezes and that's not good.

What's a better way to generate a random color that is not similar to the object's new color without freezing the game or waiting for seconds?

Programmer
  • 121,791
  • 22
  • 236
  • 328
  • why not generate an array of colors in advance and just pick random values from it? That should save you some run time... – Zohar Peled Dec 03 '17 at 09:55
  • I thought about that but I don't want the color to be predetermined or to be limited to few colors. I just want random colors to be as different as possible. – Programmer Dec 03 '17 at 09:57
  • The-same thing Zohar said.I can't use Unity's API(Random) Thread in another Thread. Although, I can use the C# standard Random API but I am trying to avoid using predetermined colors stored in a collection. Like my first reply, I want the color to be as much unique as possible instead of re-using some sometimes. Your `Thread` suggestion is a great idea and I will look into that. – Programmer Dec 03 '17 at 10:03
  • I don't explain last comment well. Remember that I am not generating a random number. I would have gone this route if that's what I was doing. I am doing this for colors. If I have to pre-populates them then I will more likely see similar colors in the array or HashSet recycled over and over again. I want to reduce the chances of seeing the-same exact color again. – Programmer Dec 03 '17 at 10:27
  • @mjwills Your first comment gave me an idea to use ThreadPool to generate multiple colors each time I need them at the-same time then select the one that's not similar to the objects current color. This should give me much more unique colors then pre-populating the colors. – Programmer Dec 03 '17 at 10:28
  • Every second. But there are many Objects for each player to change the color. About 4 objects per player and there can be 8 players in the game. That's why I believe using the predetermined method is not a good idea. It will work but the randomness is now ruined. *"When you next need to generate a unique colour, do you mean it needs to be not Yellow?"* Yes. Now, if I go with your suggest, the colour might be White again which I also don't want. If the White color is slightly different this time then that's fine. A darker or brighter White is fine which is why I prefer to do it on the fly. – Programmer Dec 03 '17 at 10:39
  • 4 objects per player times 8 players in the game means 32 colors. You want to generate a new set of 32 colors each second? – Zohar Peled Dec 03 '17 at 10:44
  • Yes, if there are that amount of the players during that session otherwise less. – Programmer Dec 03 '17 at 10:46
  • and each of these 32 colors needs to be unique? what about the next second? do you need to generate colors that was not generated in the previous set or simply unique within the current set? – Zohar Peled Dec 03 '17 at 10:48
  • Each 4 colors are generated on the player's computer for that player not on one computer. Those 4 colors has to be unique atleast. If the last generated color is blue, the next generated one can also be blue **but** a brighter or darker blue. It can't be that exact blue that was generated last time. This reduces the chances of those 8 players having so many the-same similar colors. I feel like I am confusing everyone.... – Programmer Dec 03 '17 at 10:59

2 Answers2

3

I would take a different approach:

  • Generate a random integer in [0; 2] representing red, green, blue
  • Add 0.5 to the color just determined in the first step but subtract -1 if greater than 1
  • Generate 2 independent random float numbers in the range [0; 1] that are taken for the remaining two color components

Example: Assume we have C1 = (R1; G1; B1) = (0.71; 0.22; 0.83)

  • Assume step 1 produces index 0 i.e. red
  • So we take R1 + 0.5 = 0.71 + 0.5f = 0.21f
  • We create G2 and B2 as new green and blue components and get (0.21f; G2; B2)

Even if G2 and B2 are identical to their predecessors the new color will be clearly distinct as R2 is shifted

Update code

public static class RandomColorGenerator
{
    public static Color GetNextPseudoRandomColor(Color current)
    {
        int keep = new System.Random().Next(0, 2);
        float red = UnityEngine.Random.Range(0f, 1f);
        float green = UnityEngine.Random.Range(0f, 1f);
        float blue = UnityEngine.Random.Range(0f, 1f);
        Color c = new Color(red, green, blue);
        float fixedComp = c[keep] + 0.5f;
        c[keep] = fixedComp - Mathf.Floor(fixedComp);
        return c;
    }
}

Test:

public class RandomColorTest
{
    [Test]
    public void TestColorGeneration()
    {
        Color c = Color.magenta;
        for (int i = 0; i < 100; i++)
        {
            Vector3 pos = new Vector3(i / 20f, 0f, 0f);
            c = RandomColorGenerator.GetNextPseudoRandomColor(c); 
            Debug.Log(i + " = " + c);
            Debug.DrawRay(pos, Vector3.up, c);
        }
    }
}

Result in scene view around (0; 0; 0) after running editor test enter image description here

Kay
  • 12,918
  • 4
  • 55
  • 77
2

Here's another idea:

Rather than generating the RBG components, instead generate a HSV combination (and convert). As far as the human eye concerns, a difference in hue of about 15 degrees (~0.05 on the 0-1 scale) is sufficient to be considered a different color. This is the only value that's important.

For example, here's an image with red along the top ("0 degrees"). Along the bottom are 4 colors: 7, 15, 30, and 60 degrees along the "hue" slider.

Color distinction

The 7 degree difference can be seen but looks too close. The 15 degree shift is definitely a different color (even if I'd call both it and the next one "orange", they're at least different oranges).

You can pick whatever threshold you want and generating new colors until the new color is more than your threshold in degrees away from the old. For value and saturation, you can pretty much generate any value from 0 to 1, as even if the values are exactly the same as the previous color, the minimum enforced hue difference will result in a color that is sufficiently different.

Then, converting from HSV to RGB is pretty easy.

I've done this sort of thing in two different languages, one of which was for a project where I specifically wanted to generate two or three "different colors" while also insuring that the brightness was sufficiently high (i.e. I did not want "black" to be possible), so HSV was the only sensible solution: I could generate random hues, set value and saturation up towards max (e.g. random range [0.8,1.0]). The other project I was merging two images into a single texture: I wanted to keep the same hue and saturation of the first image but adjust the value based on the second.

You may also want to bias the Saturation and Value numbers, depending on your goals, if either Saturation or Value is too low (under 0.5) hues will start blurring together and be too similar (the colors will be all muddy). Alternatively, you could generate a single float between 0 and 1 and set the Saturation to that number and Value to one minus that number.