Can i use IEnumerator as Update func? - c#

I have some structs like object - 4 child object - in each child 2 more child. I want for each child object change alpha of their color.
I created IEnumarator that should change alpha but when i test this it only changes for 0.8, not to 0, and i also want to change it by time, smoothly for 2 seconds for example, but it happens quick
imageComponents = gameObject.GetComponentsInChildren<Image>();
textComponents = gameObject.GetComponentsInChildren<Text>();
IEnumerator HideAllBoosters(Transform _object)
{
foreach (Image image in imageComponents)
{
Color _color = image.GetComponent<Image>().color;
_color = new Color(_color.r, _color.g, _color.b, 0);
image.GetComponent<Image>().color = Color.Lerp(image.color, _color, 10 * Time.deltaTime);
}
foreach (Text text in textComponents)
{
Color _color = text.GetComponent<Text>().color;
_color = new Color(_color.r, _color.g, _color.b, 0);
text.GetComponent<Text>().color = Color.Lerp(text.color, _color, 10 * Time.deltaTime);
}
yield return null;
}
Idk how to do that right, mb i should change color in Update for each object but i dont sure its good for clear and easy code, so what im asking for - can i use another IEnumerator for each object which would work like an Update, smth like:
foreach (Image image in imageComponents)
{
StartCourutine(changeAlpha(image));
}

The error is in passing 10 * Time.deltaTime as the t value for Lerp(Vector3 a, Vector3 b, float t);
If you look at the documentation for Lerp():
When t = 0 returns a. When t = 1 returns b. When t = 0.5 returns the point midway between a and b.
This means that in order for your alpha to have a nice fade, the t value of Lerp() should be a value that goes from 1 to 0 (or 0 to 1 if you want to fade in) over a certain time. Right now you are passing in 10 * Time.deltaTime this will always be the same value, based on the framerate. (in this case that would be about 0.8).
To fix this issue you need t to be a value that slowly increases/decreases (depending on wether you want to fade in or out) between 0 and 1 . One way of doing this is by encasing your logic in a while loop.
float speed = 0.01f;
float time = 0;
while ( time < 1)
{
time += speed;
//Rest of code
}
This will increment the time value by speed(in this case 0.01) every time the loop runs, which in this case is for 100 iterations (0.01 * 100 = 1).
We can now apply this time value as the t value in the Lerp() method to make it a smooth transition
image.color = Color.Lerp(image.color, _color, time);
If you want your fade to take more or less time, simply increase or decrease the value in speed.
the total implementation would look something like this (notice that I've also done some optimizations that I will cover later)
public float speed = 0.01f; //The speed at which the fade happens
private Image[] imageComponents;
private Text[] textComponents;
void Start()
{
imageComponents = gameObject.GetComponentsInChildren<Image>(); //Cache the images so we don't have to find them every time
textComponents = gameObject.GetComponentsInChildren<Text>(); //Cache the texts
StartCoroutine(HideAllBoosters());//Start the Coroutine
}
IEnumerator HideAllBoosters()
{
float t = 0; //Start value for our loop
while (t < 1) // 1 indicates the max value of t at which the loop stops. In this case after 100 iterations since the speed is 0.01
{
t += speed;
foreach (Image image in imageComponents)
{
Color _color = image.color;
_color = new Color(_color.r, _color.g, _color.b, 0);
image.color = Color.Lerp(image.color, _color, t); //t dictates how far in the interpolation we are.
}
foreach (Text text in textComponents)
{
Color _color = text.color;
_color = new Color(_color.r, _color.g, _color.b, 0);
text.color = Color.Lerp(text.color, _color, t);
}
yield return null;
}
}
The optimizations I've done:
The arrays you loop through imageComponents and textComponents are already of type Image and Text. This means that when you loop through them in a foreach() loop Image image and Text text are already of their respective types, and will already hold a reference to their component. What this means is that your .GetComponent<Image>() and .GetComponent<Text>() calls are unnecessary.
In example:
Color _color = image.GetComponent<Image>().color;
is the same as
Color _color = image.color;
I have also removed the Transform _object parameter that HideAllBoosters required, as this didn't seem to be used in the method at all. It may be that you do use this value somewhere later in the function that is outside the scope of this question. If that is the case you need to include it ofcourse.

Related

Synchronising rhythm game notes by changing the maxValue in Mathf.Lerp

enter image description here
I can't describe the problem thoroughly because I don't know rhythm game terms.
In the picture
The white cube Note represents the object which the player needs to hit on time.
The green rectangle Line is the representation of the timing. When the Note perfectly lines up with the Line is when the player presses a button to hit the note.
Now the problem is, I cannot seem to find a way to make the Note perfectly line up with the Line using Mathf.Lerp while it still reaches the end.
void GenerateBeat()
{
if (timeItems.Count == 0)
return;
if (timeItems.Peek() <= currentBeatPosition + BeatLookForwardValue)
{
Debug.Log("Item instantiated, Spawned At : " + currentBeatPosition + " Will reach 0 at : " + timeItems.Peek());
GameObject obj = Instantiate(tempSpawnObject);
obj.transform.SetParent(parentObject.transform);
existingBeats.Add(obj);
existingBeatsTime.Add(currentBeatPosition);
timeItems.Dequeue();
}
}
void MoveBeat()
{
// if (timeItems.Count == 0)
// return;
for (int i = 0; i < existingBeats.Count; i++)
{
NoteBeatline beatline = existingBeats[i].GetComponent<NoteBeatline>();
Vector2 StartingPos = new Vector2(0, 800f);
Vector2 EndPos = new Vector2(0, 0);
float offset = (endTime[i] - existingBeatsTime[i]) / 3;
float time = Mathf.InverseLerp(existingBeatsTime[i], endTime[i] + offset, currentBeatPosition);
beatline.Image.rectTransform.anchoredPosition = Vector2.Lerp(StartingPos, EndPos, time);
}
}
[Edit]
Sorry for the lack of clarification.
I'm trying to synchronise Notes by adding extra value to the endTime because existingBeatsTime (keeps track of the time at which the object is spawned) can vary depending on how great BeatLookFOrwardValue is.currentBeatPosition represents how long in beat time has passed since the start of the song, so I cannot manipulate the value.
the endTime value is always a constant, set by me
and items in timeItems are identical to the items in endTime.
the whole background colour of dark cyan is currently 800 units
and the y position of the green horizontal line is 600
so that the Notes can perfectly line up with the green line
when
float time = Mathf.InverseLerp(existingBeatsTime[i], endTime[i] + offset, currentBeatPosition);
is 0.75f.
I did more research and experiment after I posted this,
and found out
float offset = (endTime[i] - existingBeatsTime[i]) / 3;
somehow synchronises the Notes, but I don't understand how.
Please feel free to comment on my explanation.
This is my first time posting a question on StackOverflow and explaining my coding problem to someone else.
I will try to provide more information if it is still lack.
"Currently, Notes reach the end when currentBeatPosition == endTime.
But I want to make them reach the Line when currentBeatPosition == endTime"
So, they are lerping across the correct distance? They are just doing it a bit too fast yeah? So to fix it you can increase the total time over which they are lerping. So they reach the line at endTime and reach the end a little bit later.
get the ratio, distance from start to end / distance from start to line
Multiply the total time they are lerping over by this ratio.
I've finally fixed the synchronisaton problem.
I found out that there's Mathf.LerpUnclamped() which can extrapolate the return value. So I had to change
beatline.Image.rectTransform.anchoredPosition = Vector2.Lerp(StartingPos, EndPos, time);
to
beatline.Image.rectTransform.anchoredPosition = Vector2.LerpUnclamped(StartingPos, EndPos, time);
this. Since it can extrapolate I was able to set the endPos to the position of the green line.
Additionally, I also had to make my own Mathf.InverseLerp() which will be the unclamped version of Mathf.InverseLerp().
public static float InverseLerpUnclamped(float startValue, float maxValue, float inbetweenValue)
{
return (inbetweenValue - startValue) / (maxValue - startValue);
}
Finally, I could simply get it working by these two lines of code.
float time = Tools.InverseLerpUnclamped(existingBeatsTime[i], endTime[i], currentBeatPosition);
beatline.Image.rectTransform.anchoredPosition = Vector2.LerpUnclamped(StartingPos, EndPos, time);

How to find out when one object is under another

I have two objects and one above the other, I want to use the condition to check whether one object is under the other, but for some reason nothing works.
I think this is because the interval between object movements is too large and the code simply does not have time to work on time
[image]1
if (this.transform.position.x == CarPlayer.transform.position.x)
{
print("isPosition");
}
Create a Tolerance Rectangle.
Like
const int tolerance = 3;
var r = new Rectangle(transform.Position.X-tolerance, transform.Position.Y-tolerance, 2*tolerance, 2*tolerance);
if (r.Contains(CarPlayer.transform.position.x))
{
print("isPosition");
}
You can adapt tolerance to your needs.
I'd just use the Vector3.Distance() method which returns a float value of the distance between the two GameObject's transforms. If they are on top of one another, the returned value should be 0.
This way you can easily establish the maximum distance to trigger the "isPosition" condition.
float maxDistance = 0.1f;
if (Vector3.Distance(objA.transform.position, objB.transform.position) <= maxDistance)
{
print("isPosition");
}
if ((int) this.transform.position.x == (int)CarPlayer.transform.position.x)
{
print("isPosition");
RandomPref = rndPref.Next(0, 2);
Instantiate(PrefabsOil[RandomPref], PositionOilDisgarge.position, PositionOilDisgarge.rotation);
}

Change lerp values smoothly over time

I am currently working on a dynamic skybox. My skybox is composed of 3 separate scripts:
CustomList ,CustomListEditor., TOD.CS
CustomList.cs has class that stores variables for what each variable will be at a specific time. Example: time, cloud color horizon color ect.
CustomListEditor.cs is a custom inspector to set values and Add/Remove them to a list of Times of Day (TOD).
TOD.cs is where am calculating time passing and lerping variables from one TOD to another.
The problem I am currently having: I am unable to evenly lerp each TOD. basically the problem I am having is that my lerp is not running smoothly between each Time of Day and is instead having portions that run slower and some that run faster. I Acknowledge this is a Math problem and I am not entirely sure how to go about getting the correct equation to make this work properly.
if anyone could help that would be amazing. Here is an i drew up of time and the separate time of days. keep in mind the TOD's could be placed anywhere in time so the number values are not definite in the example.
<!-- language: lang-c# -->
public float TODspeed = 0.02
private float currentValue = 0.00f
public int TODindex = 0;
public Color horizon;
void Start()
{
GetTarget = new SerializedObject(this.GetComponent<CustomList>());
ThisList = GetTarget.FindProperty("MyList"); // Find the List in our script and create a refrence of it
SerializedProperty MyListRef = ThisList.GetArrayElementAtIndex(TODindex);
SerializedProperty myHorizon = MyListRef.FindPropertyRelative("horizon");
horizon = myHorizon.colorValue;
}
void Update()
{
//Grab serialized properties from my List
//MyListRef is getting a reference of the current TOD
SerializedProperty MyListRef = ThisList.GetArrayElementAtIndex(TODindex);
//NextListRef is getting a reference of the next TOD that we will be lerping to.
SerializedProperty NextListRef = ThisList.GetArrayElementAtIndex(TODindex + 1);
SerializedProperty myTime = NextListRef.FindPropertyRelative("time");
//mixTime is supposed to be my equation for the speed of the times of day. I presume that this code is incorrect and I have no idea how to fix it.
float mixTime = TODspeed * (myTime.floatValue - MyListRef.FindPropertyRelative("time").floatValue);
//This is where I lerp my TOD variables, so long as CurrentValue ,which is the game time, is less than the next TOD's time value.
if (currentValue < myTime.floatValue)
{
currentValue += (Time.deltaTime*TODspeed);
horizon = Color.Lerp(horizon, nextHorizon.colorValue, mixTime);
this.GetComponent<CustomList>().atmosphereGradient.SetColor("_BottomColor", horizon);
}
// if game time is greater than my next TOD's time variable, It will compare the TODIndex to what would be the last TOD in the script. If it is smaller than the last TOD it will incriment , If it is bigger or equal to it, it will restart to time of days.
if (currentValue >= myTime.floatValue)
{
int compareValue = ThisList.arraySize - 2;
if (TODindex < compareValue)
{
TODindex++;
}
else if (TODindex >= compareValue)
{
TODindex = 0;
currentValue = 0.00f;
}
}
}
Your problem is in the line
horizon = Color.Lerp(horizon, nextHorizon.colorValue, mixTime);
you allways interpolate between the current value and the target value => the difference between those is everytime smaller => the "fading" gets slower an slower in time.
What you want to do instead is a constant fading between the original and the target value. I don't see where horizon is declared but you should instead store the original Color outside of Update e.g. as startColor and than change your line to
horizon = Color.Lerp(startColor, nextHorizon.colorValue, mixTime);
Note: I don't completely understand the rest of your code but as I understood your problem was mostly the lerping effect so I assumed that the rest works fine.

Best way to generate random Color and exclude old one

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?
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
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.
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.

Normalize a vector with one fixed value

I have this problem where I want to keep a list of values and normalize them so that the sum of all values is 1.
I found this question, but there is one difference. I would like to have 1 value that is fixed. Because, there are 4 GUI sliders and I drag one. The other 3 should update accordingly so that the total sum is 1.
// Let's keep 0.6 fixed and try to normalize the rest
List<float> categories = new List<float>() { 0.6f,0.3f,0.25f,0.25f };
// find the max value of the rest of the values that is not the fixed number
float maxval = categories.Where(s => s != 0.6f).Max();
if (maxval != 0) // check if not all other
{
// calculate the ratio
float ratio = (1 - 0.6f) / maxval;
foreach (float s in categories)
{
if (s != 0.6f)
{
s *= ratio;
}
}
}
else // all values are 0, so just add the rest value
{
// do something else that is correct
}
Unfortunately this example will return:
{ 0.6f, 0.4f, 0.33f, 0.33f } => sum is 1.66f
And there is something that I'm misunderstanding. Can someone give me a clue on how to get the rest of the values normalized to the rest sum (0.4f) so that the total sum of all values is 1?
You are calculating it wrongly.
If you have a 4 elements, and 1 of those is fixed, and the total sum needs to be 1, you would go like:
ratio = (1-fixedElement) / sumOfAllOtherElements;
EachNonFixedElement *= ratio;
You are calculating the ratio just with the max value, but you need to calculate it with the sum of all non-fixed elements.
Among other problems, this loop doesn't do what you expect:
foreach (float s in categories) {
if (s != 0.6f) {
s *= ratio;
}
}
s is a local copy of one of the values from categories. You modify the copy, and then it goes out of scope and your new value disappears. You never modified categories.
C# foreach cannot be used to modify a collection.
Furthermore, to avoid accumulating large rounding error, you should store to a new collection and not replace the values in categories, at least until the drag operation ends.

Categories