Color cycle script - NEED HELP

I am new to Unity, to c# and these forums and tools, so please forgive me if I breach a code of conduct or something…

So, I’m trying to create a script that will cycle through the color scale on a material. This is what I came up with…

void Update()
{

Material material = Renderer.material;

        //Color assignments
        float red = 1.0f;
        float green = 0.0f;
        float blue = 0.0f;

        //The idea here is to change the value of the floats every frame and check for 1.0 values then change the value being affected accordingly.
        if (red == 1.0f)
        {
            if (blue > 0.0f)
            {
                blue = blue - 0.1f;
            }
            if (blue == 0.0f)
            {
                green = green + 0.1f;
            }
        }

        if (green == 1.0f)
        {
            if (red > 0.0f)
            {
                red = red - 0.1f;
            }
            if (red == 0.0f)
            {
                blue = blue + 0.1f;
            }
        }

        if (blue == 1.0f)
        {
            if (green > 0.0f)
            {
                green = green - 0.1f;
            }
            if (green == 0.0f)
            {
                red = red + 0.1f;
            }
        }

        //Assigns material a new color every frame
        material.color = new Color(red, green, blue, 1.0f);
}

So before I tried this, I had –

material.color = new Color(Random.Range(0.0f, 1.0f), Random.Range(0.0f, 1.0f), Random.Range(0.0f, 1.0f), 1.0f);

–which gave me this strobing technicolor effect. What I wanted was to slow it down so that the colors kind of fade into each other… Any help would be great.

Welcome! For simple color cycling have you considered perhaps just doing it with an animation? Pretty powerful stuff if you just want a canned loop of slowly changing colors.

There are also color changers in packages like DOTween and ITween and suchlike free on the asset store. That way you can just use their code, you don’t have to debug your own.

As for the speed of the above code, the reason it is so fast is that every frame (eg, every Update() call) you are advancing 0.1, and it only goes from 0.0 to 1.0f.

This also means on faster frame rates your color cycles faster, probably not what you want.

Instead, adjust your color by some amount that you want it to change per second, and multiply that by Time.deltaTime to make it framerate-independent, something like:

const float RedCyclesPerSecond = 2.5f;

red += RedCyclesPerSecond * Time.deltaTime;
if (red > 1.0f)
{
  red = 0.0f;
}

Also, generally never compare floating point numbers for equality due to floating point imprecision. I’ll let you google that term up.

If you want a cool up/down function you can use something like Mathf.PingPong() for a steady ramp up and steady ramp down, or else Mathf.Sin() for smoothly changing “ocean wave” color changes.

2 Likes

So, the bottom code in my OP –

material.color = new Color(Random.Range(0.0f, 1.0f), Random.Range(0.0f, 1.0f), Random.Range(0.0f, 1.0f), 1.0f);

– is the one that produced the lightning fast strobing effect, presumably because like you stated it calls for a random value for each R,G, and B with a constant opacity every frame. That’s when I built the upper script –

void Update()
{
Material material = Renderer.material;
        //Color assignments
        float red = 1.0f;
        float green = 0.0f;
        float blue = 0.0f;
        //The idea here is to change the value of the floats every frame and check for 1.0 values then change the value being affected accordingly.
        if (red == 1.0f)
        {
            if (blue > 0.0f)
            {
                blue = blue - 0.1f;
            }
            if (blue == 0.0f)
            {
                green = green + 0.1f;
            }
        }
        if (green == 1.0f)
        {
            if (red > 0.0f)
            {
                red = red - 0.1f;
            }
            if (red == 0.0f)
            {
                blue = blue + 0.1f;
            }
        }
        if (blue == 1.0f)
        {
            if (green > 0.0f)
            {
                green = green - 0.1f;
            }
            if (green == 0.0f)
            {
                red = red + 0.1f;
            }
        }
        //Assigns material a new color every frame
        material.color = new Color(red, green, blue, 1.0f);
}

– which does nothing. It loads as a red Cube, but colors never shift. I’m guessing that’s from the floating point imprecision causing my if statements to never actually be true? I had tried to multiply each field by Time.deltaTime in an earlier attempt, but it never changed colors.

As far as the other functions go -Mathf.PingPong() & Mathf.Sin()- I will look into them now. Thank you for the timely reply.

Your current script has a couple of common beginner mistakes. First, when dealing with animation, you will want to scale any changes by Time.deltaTime, as Update runs once a frame, which can be of basically any duration (30fps, 60fps, etc.). You don’t want to tie your animation speed to the current frame rate. In general form, this pattern looks something like new_value = old_value + change_amount_per_second * Time.deltaTime;

Additionally, you are attempting to compare floats using the == operator. In most circumstances, you want to avoid this, since if a float is almost a number, it still won’t be equal. For example, 1.999997f does not equal 2f. The better way to do this is to use greater than or less than checks to see if the float has exceeded some maximum or minimum value.

It appears that you’re trying to cycle through hues. The best way to do this would be to use HSV (hue, saturation, value) color format, as it only requires that you animate the hue value.

You can easily do this using Unity’s HSVToRGB utility. Set your brightness/value and saturation to something you like, and cycle hue from 0 to 1:

float currentHue = 0;

// expose to the inspector for easy tweaking
public float saturation = 0.5f;
public float brightness = 0.5f;

public float cycleSpeed = 1f; // in cycles per second, where 1 cycle is equivalent all the way around the color wheel once

void Update () {
    currentHue += cycleSpeed + Time.deltaTime;
  
    // we don't want to check if currentHue == 1f because of the issue mentioned above
    if( currentHue > 1f ) {
        // we subtract 1 instead of setting to zero to preserve the tiny value that overshoots 1
        // for eg. if currentHue is 1.014, this will make it 0.014. This makes more of a difference
        //   than you might think!
        currentHue -= 1f;
    }

    material.color = Color.HSVToRGB( currentHue, saturation, brightness );
}
1 Like

Yours seems like a much cleaner method to do this. I am going to try it, though I did resolve my problem with the help of a few other community members. What I wound up with was this –

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Cube : MonoBehaviour
{
    public MeshRenderer Renderer;
    private Material material;

    private float shiftSpeed = 0.2f;
    private float red = 1.0f;
    private float green = 0.0f;
    private float blue = 0.0f;


    void Start()
    {    
        //Randomizes starting position and size within set parameters
        float startX = Random.Range(3.0f, 5.5f);
        float startY = Random.Range(4.0f, 6.5f);
        float startZ = Random.Range(0.5f, 2.0f);
        float startSize = Random.Range(0.7f, 1.9f);
        transform.position = new Vector3(startX, startY, startZ);
        transform.localScale = Vector3.one * startSize;
    }
  
    void Update()
    {
        //Roatation of cube
        transform.Rotate(10.0f * Time.deltaTime, 0.0f, 0.0f);
        //Not really sure what this does
        material = Renderer.material;

        //Color assignments

        Color matColor = new Color(red, green, blue, 1.0f);

        //The idea here is to change the value of the floats every frame and check for 1.0 values then change the value being affected accordingly.
        if (red >= 1.0f)
        {
            if (blue > 0.0f)
            {
                blue = blue - shiftSpeed * Time.deltaTime;
            }
            if (blue <= 0.0f)
            {
                green = green + shiftSpeed * Time.deltaTime;
            }
        }

        if (green >= 1.0f)
        {
            if (red > 0.0f)
            {
                red = red - shiftSpeed * Time.deltaTime;
            }
            if (red <= 0.0f)
            {
                blue = blue + shiftSpeed * Time.deltaTime;
            }
        }

        if (blue >= 1.0f)
        {
            if (green > 0.0f)
            {
                green = green - shiftSpeed * Time.deltaTime;
            }
            if (green <= 0.0f)
            {
                red = red + shiftSpeed * Time.deltaTime;
            }
        }

        //Assigns material a new color every frame
        material.color = matColor;
    }
}

– and it does transition nice and smoothly now. I definitely appreciate all of the tips and tricks, you guys were so quick and helpful. Thank you all so much! I will be looking into the Mathf functions I was recommended to look into, as well as the HSVtoRGB utility.

You’re starting those color channel variables at zero each frame.

Make those variables class variables, NOT declared inside the Update()

1 Like
//Not really sure what this does
        material = Renderer.material;

That sets the material you’ve declared to be the one currently on the Renderer, basically getting a reference to it, so changes will be used by the Renderer. Otherwise it’s just a generic Material. Also, move that line into Start()—you only need to run it once, so having it in Update() is wasteful.

Glad you got the color patterns going. The Mathf.Sin function can work wonders as well, especially when you include duration and amplitude variables to tweak. You can also add and multiply several sin functions together for more complex (but still periodic) effects. Here’s a basic example, so durationParam tells how fast the periods should go, and ampParam scales the result:

float colorVal = Mathf.Sin(durationParam * Time.time) * ampParam; // ampParam should be (0-1)
colorVal = (colorVal + 1) * 0.5f; // get range to (0-1)

Sin/cos always give you a (-1, 1) range of values, and you want (0, 1) for color, so adjust accordingly: add 1, then multiply by 0.5f.

AnimationCurves are another great (and graphical) way to work with values over time.

2 Likes