- Home /
Unity 2020.1 List index out of range and colors not changing smoothly
What I'm trying to do is to constantly shift the color of an object through the colors of the rainbow smoothly. I have a list of the colors it needs to loop through in hex and a converter which converts hex to a color. First of all, the code gives me an ArgumentOutOfRangeException [Parameter name: Index] twice every time update is run, and I have no clue where they come from. (Line 45 near the bottom is my best guess, but at least on the first run listloc = 0 and there are 6 objects in the list, so I don't understand why this would throw an error on either one). Second, the material's color is only changing to every color in the list (except the last red) every 1/6 of a second (60fps) with no smoothness between colors (Color.Lerp is supposed to give a color inbetween the two values depending on inputs, see documentation for more info). I have no clue why either of these is occuring. See source code below.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RainbowScript : MonoBehaviour
{
public int count = 0;
public int listloc = 0;
public List<string> colors = new List<string>() { "FF0000", "FF6600", "FFFF00", "008000", "0000FF", "4B0082", "FF0000"};
public Material mt;
public float HexToDecNorm(string hex)
{
int dec = System.Convert.ToInt32(hex, 16);
return dec / 255f;
}
public Color HexToColor(string hexString)
{
float red = HexToDecNorm(hexString.Substring(0,2));
float green = HexToDecNorm(hexString.Substring(2, 2));
float blue = HexToDecNorm(hexString.Substring(4, 2));
return new Color(red, green, blue);
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
count++;
if (count == 11)
{
count = 0;
listloc++;
if (listloc == 6)
{
listloc = 0;
}
print(listloc);
}
mt.color = Color.Lerp(HexToColor(colors[listloc]), HexToColor(colors[listloc + 1]), count / 10);
// mt.color is the material's color
}
}
Answer by DenisIsDenis · Jul 14, 2021 at 03:29 AM
I don’t know why you don’t just use a list of colors, it’s much easier. It is also not clear why you are using the count
variable. In your script for changing colors, you only give 10 frames, which is very little for a smooth operation. It is also desirable to use Time.deltaTime
and its derivatives in the Lerp
function (provided that the function is in Update()
).
I give an example of a working script for smoothly changing the color of an object:
using System.Collections.Generic;
using UnityEngine;
public class ChangeColor : MonoBehaviour
{
List<Color> colors = new List<Color>();
Color nextColor = new Color(1.0f, 0.0f, 0.0f, 1.0f); // red
int indexOfColor = 0;
Material mt;
void Start()
{
colors.Add(new Color(1.0f, 0.0f, 0.0f, 1.0f)); // red
colors.Add(new Color(1.0f, 0.5f, 0.0f, 1.0f)); // orange
colors.Add(new Color(1.0f, 1.0f, 0.0f, 1.0f)); // yellow
colors.Add(new Color(0.0f, 1.0f, 0.0f, 1.0f)); // green
colors.Add(new Color(0.0f, 1.0f, 1.0f, 1.0f)); // blue
colors.Add(new Color(0.0f, 0.0f, 1.0f, 1.0f)); // indigo
colors.Add(new Color(1.0f, 0.0f, 1.0f, 1.0f)); // violet
mt = GetComponent<Renderer>().material;
}
void Update()
{
mt.color = Color.Lerp(mt.color, nextColor, Time.deltaTime * 6);
if(IsColorsEqual(mt.color, nextColor, 0.01f))
{
indexOfColor = indexOfColor >= colors.Count - 1 ? 0 : indexOfColor + 1;
nextColor = colors[indexOfColor];
}
}
bool IsColorsEqual(Color first, Color second, float accuracy)
{
var redEqual = Mathf.Abs(first.r - second.r) < accuracy;
var greenEqual = Mathf.Abs(first.g - second.g) < accuracy;
var blueEqual = Mathf.Abs(first.b - second.b) < accuracy;
var alphaEqual = Mathf.Abs(first.a - second.a) < accuracy;
return redEqual && greenEqual && blueEqual && alphaEqual;
}
}
@DenisIsDenis Thank you for the answer. I'm new to C# and Unity in general and didn't know how a color object was stored/created so I looked up a way to convert hexadecimal to a color object (probably should've just looked at color documentation, but didn't think about it at the time). I don't quite understand how Time.deltaTime
works in this context and how you would control how long or short the gradient is. Truth is, I didn't know that it existed before this. Two more questions: Why don't you declare lines 6-9 public and why do you change mt
on line 21 instead of dragging it in the Unity editor to the variable slot in the script component? Could you answer these and explain them a little bit more? I understand the rest of the code and want to get more knowledge on Unity and C#.
Time.deltaTime is the time the current frame lasts..it's used to make some parts of a script frame rate independent (since it's kinda a reciprocal to the frame rate).....and hence is generally necessary for uses like lerping.....also....declaring an variable locally within the script is just a standard practice if it's a component of the current gameObject....so that we can ensure that the code works even if we accidentally deleted the reference in the editor....hope this helps
As rage_co already mentioned, using Time.deltaTime
allows animations, movement, etc. to play the same on all devices. But the main thing in the Update()
function is to use Time.deltaTime
, and in the FixedUpdate()
function Time.fixedDeltaTime
(also Time.unscaledDeltaTime
and Time.fixedUnscaledDeltaTime
, respectively).
You can change the rate of change of the gradient by multiplying Time.deltaTime
by a certain number, in my case it is 6. The smaller the number, the slower the animation (it does not work with negative numbers), and vice versa. Here's this line of code:
mt.color = Color.Lerp (mt.color, nextColor, Time.deltaTime * 6); // your number
All four global variables can be made public if there is a need to configure these variables in the inspector. This should be done, for example, if you need a reference to a component of another object. I assigned the variable mt
in the script, because I mean that the object on which the script is located will change its color. But if I needed to change the color of a completely different object, then I would make the mt
variable public.
(By the way, you can get any component from any object using the GetComponent<[ComponentYouNeed]>()
command as in the example in my answer)
And in conclusion, I will show a script that seemed more logical to me:
using UnityEngine;
public class ChangeColor : MonoBehaviour
{
public Gradient gradient;
float time;
Material mt;
void Start()
{
mt = GetComponent<Renderer>().material;
}
void Update()
{
time += Time.deltaTime * 0.5f;
if (time >= 1) { time--; }
mt.color = gradient.Evaluate(time);
}
}
In the inspector, you need to assign the gradient as you need. I configured like this:
This color change method is easier to customize. Time.deltaTime * 0.5f
means that one color change cycle will be completed in 2 seconds, but simply Time.deltaTime
in one second.