Unity - how to lerp while a certain animation state is on? - c#

Ok, so I have an animationnthe speed of which is controlled by user's tapping m meaning I cant just lerp for a set time but have to have it depend on if this is true:
cameraAnim.GetCurrentAnimatorStateInfo (0).IsName ("stillOpening")
By the end of this animation (no matter how long it took, fast or slow) I need this float in my material to have lerped to its final value:
skybox.SetFloat ("_Exponent1",Mathf.Lerp(skybox.GetFloat("_Exponent1"), topSkyBoxOpen, ratio));
Meaning it has to be equal to topSkyBoxOpen at the end of "stillOpening". I don't know how to coordinate the timing.
I have tried this in the Update():
void openSkyLerp()
{
float ratio = 0;
float duration = 0.5f; // this is the one that will control how long it takes
// value is in second
float multiplier = 1 / duration;
while (cameraAnim.GetCurrentAnimatorStateInfo (0).IsName ("stillOpening")) {
ratio += Time.deltaTime * multiplier;
skybox.SetFloat ("_Exponent1",Mathf.Lerp(skybox.GetFloat("_Exponent1"), topSkyBoxOpen, ratio));
}
}
But nothing happens at all - I read this might be because its trying to have it all lerp in 1 frame. Is this possible? How can I lerp WHILE an animation is playing regardless of its speed?

Meaning it has to be equal to topSkyBoxOpen at the end of
"stillOpening". I don't know how to coordinate the timing.
The problem is that you are not even waiting for a frame. So, even if your equation is correct, all those cannot happen smoothly in a single frame. You wait for a frame with yield return null; and that requires coroutine.
Answered a very similar but not the-same question few hours ago.
If the stillOpening variable is the destination value, get the current _Exponent1 value before going into the while loop. Have a counter variable that increments each frame while counter is less than topSkyBoxOpen. You can then use Mathf.Lerp(currentVal, topSkyBoxOpen, counter / duration); in the SetFloat function.
bool running = false;
void openSkyLerp()
{
if (running)
{
return;
}
running = true;
StartCoroutine(OpenSky(5));
}
IEnumerator OpenSky(float duration)
{
float currentVal = skybox.GetFloat("_Exponent1");
float counter = 0;
while (counter < topSkyBoxOpen)
{
//Exit if not still opening
if (!cameraAnim.GetCurrentAnimatorStateInfo(0).IsName("stillOpening"))
{
yield break;
}
counter = counter + Time.deltaTime;
float val = Mathf.Lerp(currentVal, topSkyBoxOpen, counter / duration);
skybox.SetFloat("_Exponent1", val);
yield return null; //Wait for a frame
}
running = false;
}

Related

How can I use Mathf.PingPong to scale and rotate an object between max and min?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RotateAndScale : MonoBehaviour
{
public Transform target; // Target to scale
public Vector3 minScale; // Minimum scale value
public Vector3 maxScale; // Maximum scale value
public Vector3 maxRotate;
public Vector3 minRotate;
public float speed;
private float t = 0.0f;
void Update()
{
//if (Input.GetKey(KeyCode.C))
//{
t += speed * Time.deltaTime;
target.localScale = Vector3.Lerp(target.localScale, maxScale, t);
target.localRotation = Quaternion.Lerp(target.localRotation,
Quaternion.Euler(maxRotate.x, maxRotate.y, maxRotate.z), t);
// }
}
}
This will scale and rotate the object to max once. but I want it to get to max and then go back to min and then to max and min nonstop.
Then after it I want to use a key for example C and when pressing once on C it will get to max another press on C it will back to min.
but first I'm not sure how to do it with the pingpong nonstop and then how to do it with the key ?
I added a new global flag to interrupt the Coroutine so if it's true when pressing C in the middle it will change in real time directly to the other direction min or max and if the interrupt flag is false it will use the C key as before. But it's not working it does nothing still I need to wait for it to finish to change to min/max even if interrupt flag is true.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RotateAndScale : MonoBehaviour
{
public Transform target; // Target to scale
public Vector3 minScale; // Minimum scale value
public Vector3 maxScale; // Maximum scale value
public Vector3 maxRotate;
public Vector3 minRotate;
public float speed;
public bool interruptCoroutine = false;
// Flag for blocking input until one routine is done
private bool isMoving;
// Flag for deciding in which direction to go next
// Via the Inspector set this to the direction it shall initially go towards
[SerializeField] bool towardsMin;
private float t = 0.0f;
void Update()
{
if(interruptCoroutine == true)
{
if (Input.GetKeyDown(KeyCode.C))
{
StopAllCoroutines();
StartCoroutine(DoMove());
}
}
else
{
if (!isMoving && Input.GetKeyDown(KeyCode.C))
{
StartCoroutine(DoMove());
}
}
}
// Once started via a StartCoroutine call this will be executed
// every frame until the next yield command
IEnumerator DoMove()
{
if (interruptCoroutine == false)
{
// Just in case block concurrent routines
if (isMoving) yield break;
// Block input
isMoving = true;
}
// Decide if going to Max or min
var targetRot = Quaternion.Euler(towardsMin ? minRotate : maxRotate);
var targetScale = towardsMin ? minScale : maxScale;
// Store Start values
var startRot = target.localRotation;
var startScale = target.localScale;
var duration = 1 / speed;
var timePassed = 0f;
while (timePassed < duration)
{
t = timePassed / duration;
// Optional: add ease-in and ease-out
//t = Mathf.SmoothStep(0, 1, t);
target.localScale = Vector3.Lerp(startScale, targetScale, t);
target.localRotation = Quaternion.Lerp(startRot, targetRot, t);
// Increase the time passed since last frame
timePassed += Time.deltaTime;
// This tells Unity to "pause" the routine here,
// render this frame and continue from here
// in the next frame
yield return null;
}
// Just to be sure to end with exact values apply them hard once
target.localScale = targetScale;
target.localRotation = targetRot;
// Invert direction
towardsMin = !towardsMin;
if (interruptCoroutine == false)
{
// Done -> unlock input
isMoving = false;
}
}
}
First of all a general note: This is not how you want to use Lerp.
Currently you always start a new interpolation with a growing factor but from the current rotation/scale towards the max values.
So in the next frame the current values will already have moved so the next interpolation uses a different start value etc.
There are basically two use cases how to interpolate:
Interpolate the current value against a goal using a constant factor. This results in a smoothed movement when you have e.g. a camera movement expecting a lot of jitter. This is not what you want to do since this becomes slower and slower in the end probably never reaching the target.
Interpolate between constant start and end point with a factor growing from 0 to 1. This results in a smooth movement within a controlled time. This is more like what you want.
Mathf.PingPong uses a time input and goes forth and back between 0 and a given maximum parameter.
In your case you want to go up to 1 using the speed value as multiplier for the time input.
Like this
void Update()
{
t = Mathf.PingPong(speed * Time.time, 1);
target.localScale = Vector3.Lerp(target.localScale, maxScale, t);
target.localRotation = Quaternion.Lerp(target.localRotation,
Quaternion.Euler(maxRotate.x, maxRotate.y, maxRotate.z), t);
}
Then for the one-time key press it gets a bit more complex. I would assume for now you always want to wait until one press "animation" is done before accepting the next key press for going back.
I would use a Coroutine for this. They work like kind of temporary little Update routines but are easier to control and maintain:
// Flag for blocking input until one routine is done
bool isMoving;
// Flag for deciding in which direction to go next
// Via the Inspector set this to the direction it shall initially go towards
[SerializeField] bool towardsMin;
void Update()
{
if(!isMoving && Input.GetKeyDown(KeyCode.C))
{
StartCoroutine(DoMove());
}
}
// Once started via a StartCoroutine call this will be executed
// every frame until the next yield command
IEnumerator DoMove()
{
// Just in case block concurrent routines
if(isMoving) yield break;
// Block input
isMoving = true;
// Decide if going to Max or min
var targetRot = Quaternion.Euler(towardsMin ? minRotate : maxRotate);
var targetScale = towardsMin ? minScale : maxScale;
// Store Start values
var startRot = target.localRotation;
var startScale = target.localScale;
var duration = 1 / speed;
var timePassed = 0f;
while(timePassed < duration)
{
t = timePassed / duration;
// Optional: add ease-in and ease-out
//t = Mathf.SmoothStep(0, 1, t);
target.localScale = Vector3.Lerp(startScale, targetScale, t);
target.localRotation = Quaternion.Lerp(startRotation, targetRotation, t);
// Increase the time passed since last frame
timePassed += Time.deltaTime;
// This tells Unity to "pause" the routine here,
// render this frame and continue from here
// in the next frame
yield return null;
}
// Just to be sure to end with exact values apply them hard once
target.localScale = targetScale;
target.localRotation = targetRotation;
// Invert direction
towardsMin = !towardsMin;
// Done -> unlock input
isMoving = false;
}
If you rather want to interrupt the current routine and immediately go back instead use
// Flag for deciding in which direction to go next
// Via the Inspector set this to the direction it shall initially go towards
// Since the routine inverts this you have to set the initial value to exactly the opposite
// of what you want to be the first direction!
[SerializeField] bool towardsMin;
void Update()
{
if(Input.GetKeyDown(KeyCode.C))
{
StopAllCoroutines(DoMove());
StartCoroutine(DoMove());
}
}
// Once started via a StartCoroutine call this will be executed
// every frame until the next yield command
IEnumerator DoMove()
{
// Invert direction for the next time
towardsMin = !towardsMin;
// Decide if going to Max or min
var targetRot = Quaternion.Euler(towardsMin ? minRotate : maxRotate);
var targetScale = towardsMin ? minScale : maxScale;
// Store Start values
var startRot = target.localRotation;
var startScale = target.localScale;
var duration = 1 / speed;
var timePassed = 0f;
while(timePassed < duration)
{
t = timePassed / duration;
// Optional: add ease-in and ease-out
//t = Mathf.SmoothStep(0, 1, t);
target.localScale = Vector3.Lerp(startScale, targetScale, t);
target.localRotation = Quaternion.Lerp(startRotation, targetRotation, t);
// Increase the time passed since last frame
timePassed += Time.deltaTime;
// This tells Unity to "pause" the routine here,
// render this frame and continue from here
// in the next frame
yield return null;
}
// Just to be sure to end with exact values apply them hard once
target.localScale = targetScale;
target.localRotation = targetRotation;
}
In short: Get rid of the isMoving and all occurrences entirely!
You will then need to invert the towardsMin flag before the while loop! Thus, you will also need to invert the flag in the Inspector so if you want it first to be moving towards max set this to true => Inverted by the coroutine => first time moving towards max values.
Typed on smartphone but I hope the idea gets clear

Use MoveTowards with duration instead of speed

I want to move GameObject from position A to B with Vector3.MoveTowards within x seconds and in a coroutine function. I know how to do this with Vector3.Lerp but this time would prefer to do it with Vector3.MoveTowards since both functions behave differently.
With Vector3.Lerp, this is done like this:
IEnumerator moveToX(Transform fromPosition, Vector3 toPosition, float duration)
{
float counter = 0;
//Get the current position of the object to be moved
Vector3 startPos = fromPosition.position;
while (counter < duration)
{
counter += Time.deltaTime;
fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
}
I tried to do the the-same thing with Vector3.MoveTowards, but it's not working properly. The problem is that the move finishes before the x time or duration. Also, it doesn't move smoothly. It jumps to the middle of both positions than to the end of position B.
This is the function that uses Vector3.MoveTowards with the issue above:
IEnumerator MoveTowards(Transform objectToMove, Vector3 toPosition, float duration)
{
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
Vector3 currentPos = objectToMove.position;
float time = Vector3.Distance(currentPos, toPosition) / duration;
objectToMove.position = Vector3.MoveTowards(currentPos, toPosition,
time);
Debug.Log(counter + " / " + duration);
yield return null;
}
}
How do you move GameObject from position A to B with Vector3.MoveTowards within x seconds and in a coroutine function?
Please do not suggest Vector3.Lerp as that's not what I want to use.
EDIT:
Replacing
float time = Vector3.Distance(currentPos, toPosition) / duration;
with
float time = Vector3.Distance(startPos, toPosition) / (duration * 60f);
works but introduces a problem when focus is shifted from Unity to another application. Doing that causes the movement to not finish. Calculating it every frame instead of once before starting the timer seems more reasonable.
MatrixTai's answer fixed both problems.
As I have long time not touching Unity... but I do believe you mess up in the calculation.
First of all, Vector3.MoveTowards(currentPos, toPosition, time) is talking about
Walking from currentPos to toPosition with each frame moving
certain distance time.
Thus using time as name is confusing, but fine lets keep it.
However, you will notice in the statement, time is something move each frame. But Vector3.Distance(currentPos, toPosition) / duration is velocity (m/s), not (m/frame). To make it (m/frame), simply times Time.deltatime which is (s/frame).
Secondly,
When in coroutine, which means the function is iterated each frame. In the line float time = Vector3.Distance(currentPos, toPosition) / duration * Time.deltatime;, what you will notice is that the time keeps on decreasing when going next frame as distance become smaller and smaller.
To be more concrete, we can do some math. Typically this should be done with calculus, but lets simplify it by considering just 2 points. When object postion d = 0, and object postion d ~= 9.9, assume endpoint at 10.
At point 1, the object has time = (10-0)/duration, full speed.
At point 2, the object has time = (10-9.9)/duration, 1/10 of full speed.
Unless you want it to move slower every frame, you cannot hold the value of duration unchanged. As after each frame, you want the velocity to be kept, duration should thus decreases with distance.
To make that physics work, minus the duration for the time passed.
So the final solution is
float time = Vector3.Distance(currentPos, toPosition) / (duration-counter) * Time.deltaTime;
Here is the complete function:
IEnumerator MoveTowards(Transform objectToMove, Vector3 toPosition, float duration)
{
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
Vector3 currentPos = objectToMove.position;
float time = Vector3.Distance(currentPos, toPosition) / (duration - counter) * Time.deltaTime;
objectToMove.position = Vector3.MoveTowards(currentPos, toPosition, time);
Debug.Log(counter + " / " + duration);
yield return null;
}
}

Increase and decrease light intensity overtime

I want to create some fireflies in Unity. I want to Increase light intensity then wait some seconds and then decrease it in Unity. When they get spawned, I want them increasing their light intensity, wait some seconds and then fade out. How can I create this "process" in a clean way?
private Light pointLight; // The light component of the firefly
private float minLuminosity = 0; // min intensity
private float maxLuminosity = 1; // max intensity
private float luminositySteps = 0.005f; // factor when increasing / decreasing
private float shineDuration = 3; // wait 3 seconds when faded in
private void Start()
{
pointLight = GetComponent<Light>();
pointLight.intensity = Random.Range(minLuminosity, maxLuminosity); // start with a random intensity
StartCoroutine(ChangeIntensity()); // start the process
}
private IEnumerator ChangeIntensity()
{
pointLight.intensity += luminositySteps; // increase the firefly intensity / fade in
yield return new WaitWhile(() => pointLight.intensity >= maxLuminosity); // wait for the maximum intensity
yield return new WaitForSeconds(shineDuration); // wait 3 seconds
pointLight.intensity -= luminositySteps;
yield return new WaitWhile(() => pointLight.intensity <= maxLuminosity); // wait for the minimum intensity
StartCoroutine(ChangeIntensity()); // do it again
}
So obviously the coroutine stops forever at the first WaitWhile() How can I create such a code chain? When fading in or out, I just mean changing the light intensity.
Even though this has been solved, the current solutions are just decrementing the variable and also creates new object (WaitForSeconds) every frame.
The proper way of doing this in Unity is using Mathf.Lerp and Time.deltaTime. This type of operation is what Mathf.Lerp is made for which is to go from your minLuminosity to maxLuminosity. You can read more about this in my other question for fading out/in GameObject using its alpha component here.
I took the fadeInAndOut function from that answer and ported it to work with the Light component. Here is a simple light fade in/out function:
IEnumerator fadeInAndOut(Light lightToFade, bool fadeIn, float duration)
{
float minLuminosity = 0; // min intensity
float maxLuminosity = 1; // max intensity
float counter = 0f;
//Set Values depending on if fadeIn or fadeOut
float a, b;
if (fadeIn)
{
a = minLuminosity;
b = maxLuminosity;
}
else
{
a = maxLuminosity;
b = minLuminosity;
}
float currentIntensity = lightToFade.intensity;
while (counter < duration)
{
counter += Time.deltaTime;
lightToFade.intensity = Mathf.Lerp(a, b, counter / duration);
yield return null;
}
}
Now, to create the exact effect you want which is to increase light intensity then wait some seconds and then decrease it, create another coroutine function that calls the function above and waits for it to finish. You can do that by yielding the fadeInAndOut function. Notice how WaitForSeconds is declared outside the while loop so that it does not create new Object each time.
//Fade in and out forever
IEnumerator fadeInAndOutRepeat(Light lightToFade, float duration, float waitTime)
{
WaitForSeconds waitForXSec = new WaitForSeconds(waitTime);
while (true)
{
//Fade out
yield return fadeInAndOut(lightToFade, false, duration);
//Wait
yield return waitForXSec;
//Fade-in
yield return fadeInAndOut(lightToFade, true, duration);
}
}
USAGE:
public Light lightToFade;
public float eachFadeTime = 2f;
public float fadeWaitTime = 5f;
void Start()
{
StartCoroutine(fadeInAndOutRepeat(lightToFade, eachFadeTime, fadeWaitTime));
}
The problem in your code was that you applied your luminosity change only once, thus your WaitWhile condition would never be reached. I would change both WaitWhile into simple while loops, and then use WaitForEndOfFrame:
private IEnumerator ChangeIntensity()
{
while(true)
{
while(pointLight.intensity <= maxLuminosity)
{
pointLight.intensity += luminositySteps; // increase the firefly intensity / fade in
yield return new WaitForEndOfFrame();
}
yield return new WaitForSeconds(shineDuration); // wait 3 seconds
while(pointLight.intensity > minLuminosity)
{
pointLight.intensity -= luminositySteps;
yield return new WaitForEndOfFrame();
}
}
}
So I got it by using the following while loop
private IEnumerator ChangeIntensity()
{
while (true)
{
pointLight.intensity += isIncreasing ? luminositySteps : -luminositySteps;
if (pointLight.intensity <= minLuminosity)
isIncreasing = true;
if (pointLight.intensity >= maxLuminosity)
{
isIncreasing = false;
yield return new WaitForSeconds(shineDuration);
}
yield return null;
}
}

Changing a shader blend on pointer click over time

I have a shader in Unity that is set up to blend between two textures. This is set up and works fine.
Blend has a range from 0-1 and works well on offline mode.
_Blend1("Blend between _MainTex and Texture2", Range(0, 1)) = 0
I have set up an OnClick pointer that works fine. It essentially toggles a value to true and activates it in the Update. I have had some success on toggling the values between 0 (The first texture and 1 (The second texture).
public void OnColorChangeClick()
{
if (newSwitchOne)
{
switchOn = true;
}
else if (newSwitchTwo)
{
switchOff = true;
}
}
In the update when switchOn is true I have a while loop that runs and increments a count for the blend.
void Update()
{
rend.material.SetFloat("_Blend1", up);
while (switchOn == true) {
for (int i = 0; i < 10; i++)
{
//StartCoroutine(Wait());
up = Time.deltaTime * (up + 0.1f);
}
switchOn = false;
print(switchOn);
}
}
The trouble I am having is that the value increments are not working alongside time.deltaTime. I am seeing that Time.deltatime may not work in a while loop. So I have also tried a CoRoutine with a WaitForSeconds. None of this is giving me the incrementation that I desire.
I have looked into Lerping this - but I'm not aware how to Lerp such a value - I have been looking, perhaps in the wrong places.
Can someone point me in the right direction?
Time.deltaTime returns the time it took to complete the last frame so it will be roughly constant as you loop through (and a very small value) - which is keeping your up value to remain tiny. You should be able to lerp the value like so.
float t = 0;
void Update()
{
if (switchOn == true)
{
t += Time.deltaTime;
rend.material.SetFloat("_Blend1", Mathf.Lerp(0, 1f, t/3f)); //3 is time to complete lerp
}
if (t > 3) // lerp has completed. reset function and timer.
{
t = 0;
switchOn = false;
}
}
Update() already runs "over time." You don't need a for loop or a coroutine. You just need to track a variable that changes inside the Update() method body.
up = Time.deltaTime * (up + 0.1f);
Oh wait, you already do.
void Update()
{
rend.material.SetFloat("_Blend1", up);
if(switchOn == true) {
up = Time.deltaTime * (up + 0.1f);
if(up >= 1) {
switchOn = false;
}
print(switchOn);
}
}
Ta da.
However, you still need code to bring the value back down again (either animating it back to 0 or just setting it to 0) but your original code didn't have it either, so I'm not including it.

Scrolling Speed of GameObject increases unnecessarily

In Unity, I am facing a small problem. I start some spawns at a certain speed (let's say 2) and by time, the speed increases (let's say by 0.5). When the player loses and the Game Over screen is loaded, the speed continues to increase in the background, so when the player tries again, the speed isn't 2 (initial/normal) but it's 3 or 4 or (depending on how much time has passed).
How can I fix this? Should I destroy or disable the execution of the scripts when Gameover screen is loaded?
Code:
public class ObjectsScroller : MonoBehaviour {
public float speed = 2; // Speed of the rings
int MaxSpeed = 5; // Max speed of the rings
int timeSecond = 1; // 1s
float timer;
int sixSeconds = 6; // 6 seconds without subtraction
int countdown = 6; // Countdown with subtraction
// Use this for initialization
void Start ()
{
speed = 2;
timeSecond = 1;
timer = Time.time;
countdown = 6;
}
void Update()
{
timer = Time.time;
transform.Translate(Vector2.up * speed * Time.deltaTime);
if(timer >= timeSecond) { // Checks if a second passed
countdown -= 1; // countdown decreases by one
timeSecond += 1; // Increases so the loop runs again
}
if (countdown <= 0) { // Checks if countdown is less than 0
speed += .5f; // Speed increases by 1
transform.Translate(Vector2.up * speed * Time.deltaTime);
countdown = sixSeconds; // Countdown is equal to 8 Seconds so the loop runs again
}
if (speed >= 5) { // If speed is bigger or equal to 5 then make it 5 only
speed = MaxSpeed;
}
}
}

Categories

Resources