Change lerp values smoothly over time - c#

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.

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);

Can i use IEnumerator as Update func?

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.

Unity - How to instantiate prefab at slider`s specific value

I`m making a 2d game based on levels in which, each level, you have three checkpoints in the score tracking bar. The player must reach the lowest checkpoint to be able to pass to the next level, but will get a bonus if he reaches the 2nd and 3rd checkpoints.
I tought on using a Slider as the scoring bar. My question is:
Is there a way to store a specific value of the Slider's bar in the Start method and Instantiate a marker prefab at that position? Here's an example:
The Max Value of the Slider at Level 1 is 100.
I want to Instantiate the first marker, with some padding in the y, in the 50`s position of the slider, the second in the 75 position and the third in the 100 position.
My current logic is that I need to, somehow, get the value I want and find his Transform, but I can`t find a way to code this, I have no idea how to get the position I want.
Here are some images to illustrate what i`m trying to do:
i would get the width attribute of the slider, then divide that by sliderMax, the result will be the the width of a single % on the slider. you can then add or subract multiple of this to get a percentages place on the bar.
example: slider.x=50
slider.width=200;
increment = slider.width/100; //this will result in two, giving you two pixels per percent.
so your 50 percent placement would be: sliderx+(increment*50);
keep in mind this is all pseudo code, designed to give you an idea of how to acheive your desired result
I found the solution!
Based on the insights i managed to do this:
void SpawnCheckPoint() {
mySlider.maxValue = gameLevel[currentLevel].maxValue; //Set the slider's Max Value to the max value of the level.
float sliderMaxValue = mySlider.maxValue;
float sliderWidth = slider.GetComponent<RectTransform>().sizeDelta.x; //Get the width of the Slider.
float zeroValue = slider.transform.position.x - (sliderWidth / 2); //Get the leftmost corner of the slider.
//Loop to Instantiate the 3 checkpoints.
for (int i = 0; i < gameLevel[currentLevel].values.Length; i++) {
float valueToIncrement = (gameLevel[currentLevel].values[i] / sliderMaxValue); //Get the % of the checkpoint based on the max value of the level.
float newPos = (sliderWidth * valueToIncrement); //New position in screen
//Instantiate the object as a child of the Slider
GameObject checkpoint = Instantiate(checkPoint, new Vector3(zeroValue + newPos - xPadding, slider.transform.position.y - yPadding, slider.transform.position.z),
Quaternion.identity, GameObject.FindGameObjectWithTag("Slider").transform);
}
}
It's probably not the best way to do what i want but it's working just fine.
Thank you all who tried to help me, your insights were very useful.

Using a large float value as a timer in Unity3d

I am using a float to calculate and display the time the player has left (in seconds) to finish the game.
In my game time sometimes goes faster, so the time left value needs to go big.
Here's my problem: I use the following code in Update:
Update () {
timeleft -= Time.deltaTime;
Debug.Log (timeleft);
counter.text = "Time Left = " +timeleft.ToString("F0");
}
At game start, the time left value is set to a really high number (32 million, about a year in seconds).
The rate at which the time drops varies in the game, so I am using a float with time.deltaTime.
Unity stores this big initial value as 3.2E+07.
As soon as that happens, my counter.text doesn't work properly anymore, as that just waits until the next scientific notation value comes up. So it looks like the timer text is stuck, while in the back it is in fact still counting down. Hope I'm making sense here. For the avoidance of doubt: the counter display works fine for values below 1 million.
How do I fix this problem? How do I convert timeleft.ToString so that it displays the correct value?
As per the comments below, someone suggested to use a decimal instead. That won't work with deltaTime as that needs a float or I misunderstood where and how to use the decimal.
I tried to create a new int and use Mathf.RoundToInt on the float, but that doesnt work: the timer stays stuck at the larger value.
The short moral here is that floating-point arithmetic doesn't conform to ordinary human intuitions about numbers.
You write:
Unity stores this big initial value as 3.2E+07. As soon as that
happens, my counter.text doesn't work properly anymore, as that just
waits until the next scientific notation value comes up. So it looks
like the timer text is stuck, while in the back it is in fact still
counting down.
This is incorrect: it's not just the text that's not changing, the number itself isn't changing for small values of Time.deltaTime. The problem is that the precision of float reduces as the value gets larger. Moreover, since the underlying storage format is binary floating-point, the 'gap' between the next-largest and next-smallest number is difficult to intuit.
You can test this yourself with the following code:
float f = 3.2e7f;
string s1 = f.ToString();
Console.WriteLine(s1);
string s2 = f.ToString("F0");
Console.WriteLine(s2);
string s3 = (f - 7).ToString("F0");
var f2 = f;
for (int i = 0; i < 3000000; i++)
{
f2 = f2 - 1f;
}
var diff = f - f2;
Debug.Log(diff);
if (f2 == f)
{
Debug.Log("Floating point numbers are strange.");
}
The value of f2 never changes, despite being supposedly decremented 3 million times. This is because 3.2e7f - 1f is exactly equal to 3.2e7f.
The solution, as pointed out in the comments, is to use decimal, which uses a base-10 format and conforms much better to human intuitions. This will count down correctly.
decimal timeLeft = 3.2e7M;
Update () {
timeleft -= (decimal)Time.deltaTime;
Debug.Log (timeleft);
counter.text = "Time Left = " +timeleft.ToString("F0");
}

Bounding Box Collision On Certain Sides

I have a player sprite and a spikes sprite. The spikes are facing down and about at head level with the player. I have set it so that if the right side of the player's rectangle goes into the spikes's rectangle, it stops moving. However, I want to set it up like this-
if (playerRect.Right == spikesRect.Left - 1)
{
speedRight = 0;
}
However, this does not work. The player can continue to go past it. The only way I can get it to work is if I do this-
if (playerRect.Right > spikesRect.Left)
{
speedRight = 0;
}
To clarify, the spikesRect.Left value is 350. I want it so that if playerRect.Right is equal to 349, to stop moving to the right. Thanks for any help, it's appreciated.
If you just want a basic collision, use:
if(playerRect.Intersects(spikesRect))
{
//handle collision
}
I recommend having a velocity and a direction variable instead of different variables for different direction's speeds, it means that you only have to change one variable if you want your character to change speed or direction.
The main problem is is that as your player is moving it is not always going to land on spot 349. as its moving it might move from 348 to 350. therefore it will never trigger when it's 349. What you could however do is.
int i = spikesRect.Left - playerRect.Right;
if ( i < 0 )
playerRect.X += i; //playerRect.X can be replaced by a position variable
When it reaches spot 351. 350 - 351 = -1 so since it is less than 0 it will be added to playerRect.X making playerRect.X moved back to where playerRect.Right is 350. That way it won't look as if your player is penetrating the spike.
I think that the issue is caused by some changes you make to speedRight or to playerRect somewhere in your game. So if you're using VisualStudio just set a breakpoint and check the speedRight and playerRect values.
Although there are a few changes you should make to your game. Firstly you should create a public field into the Player class of type Texture as for the Spike class, then create another field of type Vector2 which indicates the speed and the direction :
public class Player{
public Vector2 DirectionSpeed;
public Texture2D Texture;
//...
public Player(){
//initialize your fields
}
}
Then you can handle the collision by using the method Intersects():
if(player.Texture.Bounds.Intersects(spike.Bounds)){
player.DirectionSpeed = Vector.Zero;
}
If you are trying to handle the collisions with more than one object of type Spike, you have to create a List<Spike> and then iterate with a cicle the entire list:
foreach(Spike s in listSpikes){
if(player.Texture.Bounds.Intersects(s.Bounds)){
player.DirectionSpeed = Vector.Zero;
}
}
EDIT:
Moreover if speedRight doesn't equal 1 or a submultiple of spikesRect.Left - 1 it's obvious that by increasing the position with it playerRect.Right exceeds spikesRect.Left - 1. A solution could be:
if (playerRect.Right > spikesRect.Left - 1)
{
playerRect.Location = spikesRect.Left - playerRect.Width - 1;
speedRight = 0;
}

Categories