Unity | Coroutine Speeding Up After Each Executions - c#

My coroutine to fade the player out after they die,
// Fade out ragdoll
IEnumerator RagdollFade()
{
yield return new WaitForSeconds(3f);
while (startingColour.a > 0.0f)
{
headSR.color = new Color(headSR.color.r, headSR.color.g, headSR.color.b, headSR.color.a - (Time.deltaTime / 1.5f));
bodySR.color = new Color(bodySR.color.r, bodySR.color.g, bodySR.color.b, bodySR.color.a - (Time.deltaTime / 1.5f));
leftArmSR.color = new Color(leftArmSR.color.r, leftArmSR.color.g, leftArmSR.color.b, leftArmSR.color.a - (Time.deltaTime / 1.5f));
rightArmSR.color = new Color(rightArmSR.color.r, rightArmSR.color.g, rightArmSR.color.b, rightArmSR.color.a - (Time.deltaTime / 1.5f));
leftLegSR.color = new Color(leftLegSR.color.r, leftLegSR.color.g, leftLegSR.color.b, leftLegSR.color.a - (Time.deltaTime / 1.5f));
rightLegSR.color = new Color(rightLegSR.color.r, rightLegSR.color.g, rightLegSR.color.b, rightLegSR.color.a - (Time.deltaTime / 1.5f));
yield return null;
}
}
speeds up after each execution.
For example, this first time the coroutine is called everything works fine and after 3 seconds the player is faded out. However, the next time it is called 3 seconds don't pass before the fade, the next time even less time, etc.
startingColour is set in the Start() function.

It seems that your startingColour.a value is always bigger then 0 so the while loop never finishes and your coroutine just runs forever. Hard to tell without seeing the rest of your code.
So if you start it a second time you now have both routines running parallel => now each frame you decrease the alphas by the double amount .. then triple .. etc. and it also doesn't wait the 3 seconds before the first called routines are already running the while loop so they continue to decrease the alphas.
You could use StopAllCoroutines or StopCoroutine in order to interrupt any still running routines when starting a new one. But that's actually more a kind of dirty workaround.
I would rather takle the actual issue and make sure your while loop returns which is currently unlikely to happen since you seem to not change startColor.a anywhere.
Or add a flag not allowing parallel routines at all like e.g.
private bool isFading;
IEnumerator RagdollFade()
{
if(isFading) yield brake;
// prevents other routines
isFading = true;
...
// reset the flag once routine is finished
isFading = false;
}
Then I would also rather suggest to have one single float value you use for fading using Color.Lerp like
private bool isFading;
// you can also use a fixed duration and not pass it as parameter
// but this way you are even more flexible
IEnumerator RagdollFade(float duration)
{
if(isFading) yield brake;
// prevents other routines
isFading = true;
yield return new WaitForSeconds(3f);
// it is more performant to gather all required information beforehand
headStartColor = headSR.color;
bodyStartColor = bodySR.color;
leftArmStartColor = leftArmSR.color;
rightArmStartColor = rightArmSR.color;
leftLegStartColor = leftLegSR.color;
rightLegStartColor = rightLegSR.color;
headTargetColor = new Color(headStartColor.r, headStartColor.g, headStartColor.b, 0f);
bodyTargetColor = new Color(bodyStartColor.r, bodyStartColor.g, bodyStartColor.b, 0f);
leftArmTargetColor = new Color(leftArmStartColor.r, leftArmStartColor.g, leftArmStartColor.b, 0f);
rightArmTargetColor = new Color(rightArmStartColor.r, rightArmStartColor.g, rightArmStartColor.b, 0f);
leftLegTargetColor = new Color(leftLegStartColor.r, leftLegStartColor.g, leftLegStartColor.b, 0f);
rightLegTargetColor = new Color(rightLegStartColor.r, rightLegStartColor.g, rightLegStartColor.b, 0f);
var passedTime = 0f;
while (passedTime < duration)
{
// get the interpolation factor from 0 to 1
var factor = passedTime / duration;
// for adding additional ease-in and ease-out
// factor = Mathf.SmoothStep(0, 1, factor);
headSR.color = Color.Lerp(headStartColor, headTargetColor, factor);
bodySR.color = Color.Lerp(bodyStartColor, bodyTargetColor, factor);
leftArmSR.color = Color.Lerp(leftArmStartColor, leftArmTargetColor, factor);
rightArmSR.color = Color.Lerp(rightArmStartColor, rightArmTargetColor, factor);
leftLegSR.color = Color.Lerp(leftLegStartColor, leftLegTargetColor, factor);
rightLegSR.color = Color.Lerp(rightLegStartColor, rightLegTargetColor, factor);
// avoid overshooting
passedTime += Mathf.Min(Time.deltatime, duration - passedTime);
yield return null;
}
// reset the flag once routine is finished
isFading = false;
}
This is more flexible and you can add ease-in and ease-out using whatever simple math you like.

Try call StopCoroutine() before starting a new Coroutine. Possibly you have a few coroutines working at the same time.

Ok, I've solved the problem, and it's completely my bad.
I realized that ANOTHER coroutine I had was interfering with the current one, so that's why StopCoroutine() and adding a fade check wasn't working.
Sorry guys for not including that in my question post, you would probably have been able to help me out more effectively.
So for anyone who encounters strange routine behavior in the future, make sure two coroutines are not interfering with each other.

Related

Vector3.Lerp inside a Coroutine is not working as it should on Unity3D

I am having a strange issue when using a Vector3.Lerp inside a coroutine and it makes no sense because I have many Coroutines in my game and they are all working fine.
WHAT I AM TRYING TO ACHIEVE
I am simply trying to move an object from a starting height to a final height with a coroutine called InVolo() and a simple Vector3.Lerp to change the position of the object.
MY CODE
float elapsedTime = 0f;
while (elapsedTime < TempoSalita)
{
transform.position = Vector3.Lerp(transform.position,
new Vector3(transform.position.x, maxHight, transform.position.z), (elapsedTime / TempoSalita));
elapsedTime += Time.deltaTime;
yield return null;
}
transform.position = new Vector3(transform.position.x, maxHight, transform.position.z);
yield return null;
The maxHight is simply 4f and TempoSalita is 2f. The coroutine is started in an OnTriggerEnter and it is working fine, the object is reaching the y of 4f and it exits the while in 2 seconds.
THE PROBLEM
Basically, elapsedTime / TempoSalita is becoming 1 after 2 seconds, as it should be, but the object reaches the ending position after like 0.3 seconds, when elapsed/TempoSalita is 0.2 and it makes no sense to me. Vector3.Lerp should go from startpos to endpos in the time of the t value to go from 0 to 1. But it goes to the final position in 0.2 seconds and I don't have a clue why.
WHAT I TRIED
I have tried to Debug.Log the elapsedTime and it is changing fine, tried to use only Mathf.Lerp between the y values and it does the same thing. There is nothing else in that script that can affect it, this is how the Coroutine starts:
void OnTriggerEnter(Collider other)
{
if(other.CompareTag("Saltino"))
{
StartCoroutine(InVolo());
}
}
It starts only one time. Do you know what can cause this strange problem?
It could be the condition you are using which seems a bit suspect to me.
Generally I would write this a bit differently, something like..
var t = 0f;
var start = transform.position;
var target = new Vector3(transform.position.x, maxHight, transform.position.z);
while (t < 1)
{
t += Time.deltaTime / TempoSalita;
if (t > 1) t = 1;
transform.position = Vector3.Lerp(start, target, t);
yield return null;
}
First you have your start and target values created before altering the position. The t value will increase until it reaches (or passes) 1. Since we do not want t to go beyond 1 we do a check before applying the lerp. Our t value is calculated outside of the lerp to make it clear and easy to read/modify.
The above will run for time specified and does not require the extra lines at the end since t will eventually be exactly 1.

How to transition smoothly from AddForce() to MovePosition() in Unity?

I have a character that dashes using AddForce(), after dashing, i want him to auto walk in set speed using MoovePosition().
This project was made using the 3D engine and it work flawlessly. But I've changed it to 2D and after the AddForce, the character abruptly changes to the speed that MovePosition() set, it looks bad.
I thought about checking the velocity after the dash and when it reaches the same velocity as when auto-walk is happening, it calls for MovePosition. But it doesn't seem a good approach.. I'd love to receive some tips on this issue.
if(moveFactor!= 0)
{
Vector2 appliedForce = transform.position + transform.up * moveFactor;
Debug.Log("appliedForce: " + appliedForce.magnitude);
playerRigidbody.AddForce(appliedForce);
autoWalk = true;
moveFactor = 0;
}
if (autoWalk) playerRigidbody.MovePosition(walkForce);
moveFactor is a float value that a swipe detector script returns. The code is in FixedUpdate as well.
It would be helpful to see the problem instead of trying to reconstruct the picture from your description, but I'll try my best.
The Problem
The same frame you start the dash, you set autoWalk to true. As soon as the if statement is exited, the if (autoWalk) is evaluated and you start manually moving the RigidBody using MovePosition() — before the force has even been added.
I'm not sure why that ever worked in 3D, though.
Solution #1
Instead of using MovePosition(), move with AddForce(), with the default ForceMode2D; or with playerRigidbody.velocity, where-in you set it once, not every frame.
Solution #2
Instead of using AddForce(), for the dash, use the same old MovePosition() you use inside if (autoWalk).
Just increase the magnitude of walkForce several times for the duration of the dash.
Solution #3
The problem with using AddForce() for it is that you don't know when it's "finished", so you don't know when to transition to MovePosition().
If the above solutions don't work for you, implement the dash manually. You can do this with Vector2.Lerp() (or Vector3) inside a coroutine.
if (moveFactor != 0)
{
Vector2 endPosition = transform.position + transform.up * moveFactor;
StartCoroutine(Dash(endPosition));
moveFactor = 0;
}
Where Dash() is:
public float dashDuration = 1f;
private IEnumerator Dash(Vector2 endPosition)
{
autoWalk = false;
// Record the original position before you begin moving the object.
float startingPosition = transform.position;
float elapsed = 0f;
while (elapsed < dashDuration )
{
elapsed += Time.deltaTime;
Vector2 nextPosition = Vector2.Lerp(startingPosition, endPosition, elapsed / dashDuration );
playerRigidbody.MovePosition(nextPosition);
// Note: if the RigidBody is kinematic (isKinematic == true), this will simply teleport it to the given position.
// Otherwise, it will comply with its interpolation settings.
// You could directly modify playerRigidbody.position or transform.position (though the former is faster) for the same teleportation.
yield return null;
}
autoWalk = true;
}
appliedForce might not immediately work as endPosition — modify it so it does, in that case. dashDuration is in seconds.
Make sure Dash() is started only once, until it is finished. You can do this by saving the Coroutine returned by StartCoroutine() in a private field member and comparing it to null before you start, but you shouldn't have to. If the code inside the if (moveFactor != 0) statement is executed something like every frame, change your code accordingly.
I've not tested this, so I'm not sure it will check for collisions. If it doesn't but you need it to, you can add that funcionality by raycasting (or box-casting, sphere-casting, or whatever kind of Collider you use):
private IEnumerator Dash(Vector2 endPosition)
{
float startingPosition = transform.position;
float elapsed = 0f;
while (elapsed < dashDuration )
{
elapsed += Time.deltaTime;
Vector2 nextPosition = Vector2.Lerp(startingPosition, endPosition, elapsed / dashDuration );
// Add the raycast here and only execute the MovePosition() if the returned RaycastHit2D's collider property is null.
// (The raycast's should extend to nextPosition.)
playerRigidbody.MovePosition(nextPosition);
yield return null;
}
}

Setting a shader property (Propertyblock) twice (Fading out a color with Lerp and resetting it to the original color)

I am trying to make "wet footprints", but am encountering some issues with fading out the color and resetting it to it's starting color when the step is reused. I have a gameObject array with a couple of footstep gameobject and they are placed on the position of the players foot when it touches the ground. It is currently pooling those steps, which is all working fine. Since I want to keep it as lightweight possible I am using a decal shader with a propertyblock and I need to fade out the _MainColor property when the object is placed, and when that same object is used again on a different position it needs to be reset. The problem is however the fading isn't working in it's current setup. What currently happens is the color is reset to black when it is reused and it's suppose to do the fade out again, but instead it just instantly transparent again.
void Update()
{
if (lastPos != transform.position)
{
stepPositioner = true;
lastPos = transform.position;
//Debug.Log("This Go changed position: ", this);
}
if (stepPositioner)
{
StartCoroutine(FadeTimer());
}
}
IEnumerator FadeTimer()
{
_renderer.GetPropertyBlock(_propertyBlock);
_propertyBlock.SetColor("_MainColor", startColor);
_renderer.SetPropertyBlock(_propertyBlock);
yield return new WaitForSeconds(1);
_renderer.GetPropertyBlock(_propertyBlock);
_propertyBlock.SetColor("_MainColor", Color.Lerp(startColor, endColor, Time.time * .5f));
_renderer.SetPropertyBlock(_propertyBlock);
stepPositioner = false;
}
That's not how Lerp works.
Linearly interpolates between colors a and b by t.
t is clamped between 0 and 1. When t is 0 returns a. When t is 1 returns b.
You are calling it only once with a pritty small factor of about 0.5/60 so around 0.008333 resulting in almost the start value.
Additionally you wait one second before resetting the stepPositiontimet so for one second you Update method starts a few hundred concurrent Coroutines!
For fading out the color over one second use something like
IEnumerator FadeTimer(float duration)
{
// This has to be done right away!
// Otherwise you get concurrent routines!
stepPositioner = false;
_renderer.GetPropertyBlock(_propertyBlock);
_propertyBlock.SetColor("_MainColor", startColor);
_renderer.SetPropertyBlock(_propertyBlock);
var timePassed = 0f;
while (timePassed < duration)
{
_renderer.GetPropertyBlock(_propertyBlock);
_propertyBlock.SetColor("_MainColor", Color.Lerp(startColor, endColor, timePassed / duration));
_renderer.SetPropertyBlock(_propertyBlock);
// Add the time since last frame
timePassed += Time.deltaTime;
// Tells Unity to "pause" the routine, render this frame
// and continue from here in the next frame
yield return null;
}
// Just to be sure set the final value in the end
_renderer.GetPropertyBlock(_propertyBlock);
_propertyBlock.SetColor("_MainColor", endColor, );
_renderer.SetPropertyBlock(_propertyBlock);
}
Just added the duration as parameter to make it clearer and more flexible. So just call it as
StarCoroutine(FadeTimer(2));
e.g. to fade within 2 seconds
Note: Typed on smartphone but I hope the idea gets clear

Unity: flash at frequency with variable on/off ratio

I want to be able to flash stuff at a certain frequency. For an example, let's say 2Hz. I also want to be able to specify a ratio, where I can have the thing displayed for let's say 2/3 of the cycle and have it hidden for 1/3, so the ratio would be 2:1. It's a wild bunch of flashing, so I Need to stay flexible in the way I do it. There might be some flashing with a ratio of 3:5 and a frequency of 2Hz, and some other flashing at 4Hz with ratio 1:1, and so on.
Also, I need to be able to flash in sync. So if one object is flashing already and I start flashing another one, they need to be in sync (or rather their cycles need to be in sync, the flashing may vary as the ratio may be different). But if at the same frequency, they need to "turn on" at the same time, even if their ratios are different. Also, they all need to turn on at the same time the slowest turns on.
My current approach: I have a GameObject FlashCycle, that essentially in it's update method calculates a progress for the 3 frequency's I have (2Hz, 4Hz and 8Hz).
float time = Time.time;
twoHerzProgress = (time % twoHerzInSeconds) / twoHerzInSeconds;
fourHerzProgress = (time % fourHerzInSeconds) / fourHerzInSeconds;
eightHerzProgress = (time % eightHerzInSeconds) / eightHerzInSeconds;
I have tried different times, but that didn't really matter so let's just stick to that one if you don't think it's a bad idea!
Now, whenever I want to flash an object, in it's own Update() I do this:
switch (flashRate.herz)
{
case FlashRateInterval.twoHerz:
show = flashCycle.oneHerzProgress <= onTimePercentage;
case FlashRateInterval.fourHerz:
show =flashCycle.twoHerzProgress <= onTimePercentage;
case FlashRateInterval.eightHerz:
show =flashCycle.fourHerzProgress <= onTimePercentage;
default:
show =true;
}
and then just continue and have the object displayed if show == true.
Unfortunately this doesn't flash the objects at a nice smooth and regular interval. I measured the 2Hz interval and got differences in the ratio of up to 48ms, and though it seems like not much it really makes a difference on the screen.
So the question boils down to: How can I get quick, reqular flashes while maintaining the flexibility (ratio and frequency wise) and have a syncronized flash?
Thanks for your help!
You could use Coroutines and WaitForSeconds to achieve that
// onRatio and offRatio are "optional" parameters
// If not provided, they will simply have their default value 1
IEnumerator Flash(float frequency ,float onRatio = 1, float offRatio = 1)
{
float cycleDuration = 1.0f / frequency;
float onDuration = (onRatio/ (onRatio + offRatio)) * cycleDuration;
float offDuration = (offRatio/ (onRatio + offRatio)) * cycleDuration;
while(true)
{
show = true;
yield return new WatForSeconds(onDuration);
show = false;
yield return new WatForSeconds(offDuration);
}
}
so you can call it either with a frequency e.g. 8Hz
StartCoroutine(Flash(8.0f));
this is actually equal to any call where you set onRatio = offRatio e.g.
StartCoroutine(Flash(8.0f, onRatio = 1, offRatio = 1));
StartCoroutine(Flash(8.0f, onRatio = 2, offRatio = 2));
....
or with a frequency and ratios e.g. 1(on):2(off) with 8Hz
StartCoroutine(Flash(8.0f, onRatio = 1, offRatio = 2));
With this setup the Coroutine runs "forever" in the while(true)-loop. So, don't forget before you start a new Coroutine with different parameters to first stop all routines with
StopAllCoroutines();
Now if you want to change that dynamically in an Update method, you would have to add some controll flags and additional variables in roder to make sure a new Coroutine is only called when something changed:
FlashRateInterval currentInterval;
float currentOnRatio = -1;
float currentOffRatio = -1;
void Update()
{
// if nothing changed do nothing
if(flashRate.herz == currentInterval
//todo && Mathf.Approximately(<yourOnRatio>, currentOnRatio)
//todo && Mathf.Approximately(<yourOffRatio>, currentOffRatio)
) return;
StopAllCoroutines();
currentInterval = flashRate.herz;
//todo currentOnRatio = <yourOnRatio>;
//todo currentOffRatio = <yourOffRatio>;
switch (flashRate.herz)
{
case FlashRateInterval.twoHerz:
StartCoroutine(2.0f);
//todo StartCoroutine(2.0f, onRatio = <yournRatio>, offRatio = <yourOffRatio>);
case FlashRateInterval.fourHerz:
StartCoroutine(4.0f);
//todo StartCoroutine(4.0f, onRatio = <yournRatio>, offRatio = <yourOffRatio>);
case FlashRateInterval.eightHerz:
StartCoroutine(8.0f);
//todo StartCoroutine(8.0f, onRatio = <yournRatio>, offRatio = <yourOffRatio>);
default:
show =true;
}
}
Notes:
I dont know your FlashRateInterval but if you need to use it for some reason you could make it like
public enum FlashRateInterval
{
AllwaysOn,
twoHerz = 2,
fourHerz = 4,
eightHerz = 8
}
in order to directly use the correct values.
I would call a frequency variable flashRate.herz. You also wouldn't call a size value cube.meters. I'ld recommend to rename it to flashRate.frequency.
To archieve that syncing you would somehow need access to all Behaviours and compare their values (so I'ld say some static List<YourBehavior>) and than e.g. in the Coroutine wait until all bools are e.g. set to true before continuing with your own one. For that you would need an additional bool since it is possible that show is true permanently on one component.
public bool isBlinking;
IEnumerator Flash(float frequency ,float onRatio = 1, float offRatio = 1)
{
//todo: You'll have to set this false when not blinking -> in Update
isBlinking = true;
float cycleDuration = 1.0f / frequency;
float onDuration = (onRatio/ (onRatio + offRatio)) * cycleDuration;
float offDuration = (offRatio/ (onRatio + offRatio)) * cycleDuration;
// SYNC AT START
show = false;
// wait until all show get false
foreach(var component in FindObjectsOfType<YOUR_COMPONENT>())
{
// skip checking this component
if(component == this) continue;
// if the component is not running a coroutine skip
if(!component.isBlinking) continue;
// Now wait until show gets false
while(component.show)
{
// WaitUntilEndOfFrame makes it possible
// for us to check the value again already before
// the next frame
yield return new WaitForEndOfFrame;
}
}
// => this line is reached when all show are false
// Now lets just do the same but this time wating for true
// wait until all show get false
foreach(var component in FindObjectsOfType<YOUR_COMPONENT>())
{
// skip checking this component
if(component == this) continue;
// if the component is not running a coroutine skip
if(!component.isBlinking) continue;
// Now wait until show gets false
while(!component.show)
{
// WaitUntilEndOfFrame makes it possible
// for us to check the value again already before
// the next frame
yield return new WaitForEndOfFrame;
}
}
// this line is reached when all show are getting true again => begin of loop
while(true)
{
.........
Instead of using FindObjectsOfType<YOUR_COMPONENT>() which is kind of slow you could also do something like
public static List<YOUR_COMPONENT> Components = new List<YOUR_COMPONENT>();
private void Awake()
{
if(!Components.Contains(this)){
Components.Add(this);
}
}
so you also get currently disabled components and objects
You got some diferences because you are doing everything in an Update() cycle with <= condition. On slower/faster machines you will have more/less differences because the frame's duration will never be equal to your frequency.
Try doing everything in a Corotine: unity coroutine docs
//bad code below but i think its more understandable like this
IEnumerator Flash()
{
while(true)
{
BlinkOn();
Sync();//sync here another cicle if you want to sync when on starts
yield return new WaitForSeconds(yourDuration);// yourDuration*multiplier/something+0.5f....ecc
BlinkOff()
Sync();//sync here another cicle if you want to sync when of starts
yield return new WaitForSeconds(yourDuration);
}
}

Box2dx: Cancel force on a body?

I'm doing pathfinding where I use force to push body to waypoints. However, once they get close enough to the waypoint, I want to cancel out the force. How can I do this? Do I need to maintain separately all the forces I've applied to the body in question?
I'm using Box2dx (C#/XNA).
Here is my attempt, but it doesn't work at all:
internal PathProgressionStatus MoveAlongPath(PositionUpdater posUpdater)
{
Vector2 nextGoal = posUpdater.Goals.Peek();
Vector2 currPos = posUpdater.Model.Body.Position;
float distanceToNextGoal = Vector2.Distance(currPos, nextGoal);
bool isAtGoal = distanceToNextGoal < PROXIMITY_THRESHOLD;
Vector2 forceToApply = new Vector2();
double angleToGoal = Math.Atan2(nextGoal.Y - currPos.Y, nextGoal.X - currPos.X);
forceToApply.X = (float)Math.Cos(angleToGoal) * posUpdater.Speed;
forceToApply.Y = (float)Math.Sin(angleToGoal) * posUpdater.Speed;
float rotation = (float)(angleToGoal + Math.PI / 2);
posUpdater.Model.Body.Rotation = rotation;
if (!isAtGoal)
{
posUpdater.Model.Body.ApplyForce(forceToApply, posUpdater.Model.Body.Position);
posUpdater.forcedTowardsGoal = true;
}
if (isAtGoal)
{
// how can the body be stopped?
posUpdater.forcedTowardsGoal = false;
//posUpdater.Model.Body.SetLinearVelocity(new Vector2(0, 0));
//posUpdater.Model.Body.ApplyForce(-forceToApply, posUpdater.Model.Body.GetPosition());
posUpdater.Goals.Dequeue();
if (posUpdater.Goals.Count == 0)
{
return PathProgressionStatus.COMPLETE;
}
}
UPDATE
If I do keep track of how much force I've applied, it fails to account for other forces that may act on it.
I could use reflection and set _force to zero directly, but that feels dirty.
If you just want to stop the body dead (cancelling out all forces acting on it) you can just put it to sleep:
posUpdater.Model.Body.SetAwake( false )
This clears the body's linear and angular forces and velocities. Sleeping bodies also don't collide with other sleeping bodies and generally take up less CPU time, as effectively their physics is switched off.
If you actually want the body to take part in physics after it's stopped dead, just wake it immediately:
posUpdater.Model.Body.SetAwake( false )
posUpdater.Model.Body.SetAwake( true)
This may lead to "unnatural" looking results if the body was in the middle of colliding with other things of course.

Categories