I am trying to lerp from a set of 2 values smoothly over 2 seconds, and then do the inverse over 2 seconds. I've been trying to use Mathf.Lerp or Smoothstep however it seems the value is changing just once and then not reaching the rest of the IEnumerator method. Is my problem with the IEnumerator implementation or within the Lerp?
void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.CompareTag("Player")) {
StartCoroutine(DistortScreenFX());
puh.gm.score += 500;
SoundManager.PlaySound("lightspeed1");
Destroy(gameObject);
}
if (other.gameObject.CompareTag("Asteroid")) {
Physics2D.IgnoreCollision(other.gameObject.GetComponent<Collider2D>(), GetComponent<Collider2D>());
}
if (other.gameObject.CompareTag("Ability")) {
Physics2D.IgnoreCollision(other.gameObject.GetComponent<Collider2D>(), GetComponent<Collider2D>());
}
}
IEnumerator DistortScreenFX() {
ChromaticAberration abbFX; //ramp then reset to 0
LensDistortion lensFX; //ramp then reset to 0
pp.profile.TryGetSettings(out abbFX);
pp.profile.TryGetSettings(out lensFX);
var startTime = Time.realtimeSinceStartup;
float duration = 2.0f;
float t = 0;
while (Time.realtimeSinceStartup < startTime + duration) {
t = (Time.realtimeSinceStartup - startTime) / duration;
abbFX.intensity.value = Mathf.SmoothStep(0.0f, 1.0f, t);
lensFX.intensity.value = Mathf.SmoothStep(-25.0f, -100.0f, t);
yield return null;
}
startTime = Time.realtimeSinceStartup;
while (Time.realtimeSinceStartup < startTime + duration) {
t = (Time.realtimeSinceStartup - startTime) / duration;
abbFX.intensity.value = Mathf.SmoothStep(1.0f, 0.0f, t);
lensFX.intensity.value = Mathf.SmoothStep(-100.0f, -25.0f, t);
yield return null;
}
}
You are destroying the game object running the coroutine.
Place the Destroy(gameObject); at the end of the coroutine(after the last yield return null).
If you are running this function using StartCoroutine(DistortScreenFX()) then most likely the function is throwing an exception which is causing it to terminate. Check the logs and see if there is an error.
If you are not using StartCoroutine to call it, then you are calling it wrong.
Related
I'm not sure what's going on but one of my coroutines is completely crashing unity. This is the coroutine in question.
IEnumerator attack()
{
currentState = state.attack;
pathFinder.enabled = false;
Vector3 origin = transform.position;
Vector3 attackPos = target.position;
float percent = 0;
float attackSpeed = 3;
while(percent <= 1)
{
percent += Time.deltaTime * attackSpeed;
float interpolation = (-Mathf.Pow(percent, 2) + percent) * 4;
transform.position = Vector3.Lerp(origin, attackPos, interpolation);
yield return null;
}
currentState = state.chase;
pathFinder.enabled = true;
}
A lot of code here might seem bad. I'm following a tutorial, probably heard that one a lot here, and on their end, it seems to be working fine but right when I press play it freezes unity forcing me to close it. Here's the entire script if that would give a better understanding of what could be going on
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
[RequireComponent(typeof(NavMeshAgent))]
public class Enemy : LivingEntity
{
public enum state { idle, chase, attack};
private state currentState;
private NavMeshAgent pathFinder;
private Transform target;
private float attackThreshhold = .5f;
private float timeBetweenAttacks = 1;
private float attackTimer;
private float myCollisionRadius;
private float targetCollisionRadius;
// Start is called before the first frame update
protected override void Start()
{
base.Start();
currentState = state.chase;
pathFinder = GetComponent<NavMeshAgent>();
target = GameObject.FindGameObjectWithTag("Player").transform;
myCollisionRadius = GetComponent<CapsuleCollider>().radius;
targetCollisionRadius = GetComponent<CapsuleCollider>().radius;
StartCoroutine(updatePath());
}
// Update is called once per frame
void Update()
{
if(Time.time > attackTimer)
{
float sqrDistanceToTarget = (target.position - transform.position).sqrMagnitude;
if (sqrDistanceToTarget < Mathf.Pow(attackThreshhold + myCollisionRadius + targetCollisionRadius, 2)) {
attackTimer = Time.time + timeBetweenAttacks;
StartCoroutine(attack());
}
}
}
IEnumerator attack()
{
currentState = state.attack;
pathFinder.enabled = false;
Vector3 origin = transform.position;
Vector3 attackPos = target.position;
float percent = 0;
float attackSpeed = 3;
while(percent <= 1)
{
percent += Time.deltaTime * attackSpeed;
float interpolation = (-Mathf.Pow(percent, 2) + percent) * 4;
transform.position = Vector3.Lerp(origin, attackPos, interpolation);
yield return null;
}
currentState = state.chase;
pathFinder.enabled = true;
}
IEnumerator updatePath()
{
float refreshRate = .25f;
while(target != null)
{
if(currentState == state.chase)
{
Vector3 dirToTarget = (target.position - transform.position).normalized;
Vector3 targetPosition = target.position - dirToTarget * (myCollisionRadius + targetCollisionRadius/2);
if (!dead) pathFinder.SetDestination(targetPosition);
yield return new WaitForSeconds(refreshRate);
}
}
}
}
Your problem is in the other routine in updatePath.
while(target != null)
{
if(currentState == state.chase)
{
Vector3 dirToTarget = (target.position - transform.position).normalized;
Vector3 targetPosition = target.position - dirToTarget * (myCollisionRadius + targetCollisionRadius/2);
if (!dead) pathFinder.SetDestination(targetPosition);
yield return new WaitForSeconds(refreshRate);
}
}
Problem: In the case your are not in the state state.chase you never yield => you have an infinite while loop!
It should probably rather be
while(target != null)
{
if(currentState == state.chase)
{
var dirToTarget = (target.position - transform.position).normalized;
var targetPosition = target.position - dirToTarget * (myCollisionRadius + targetCollisionRadius / 2);
if (!dead) pathFinder.SetDestination(targetPosition);
yield return new WaitForSeconds(refreshRate);
}
// In other cases wait one frame
else
{
yield return null;
}
}
Because at some point percent goes negative. It also never goes above 1. I would say your math is incorrect. Try removing the negative infront of Mathf.Pow
Math function has nothing to do with crash. Unity crashes because, #SOPMOD is running while loop infinetly. That causing a overflow. Your code tells it should break while loop when percent value is smaller or equal to 1 but you are yielding it null everytime inside the while loop. yield return null tells to compiler, coroutine will run in next frame. So, coroutine start again and percent value is set to 0 again. While loop run again and coroutine again will yield null. Infinite coroutine loop is crashing your app. Move
float percent = 0;
decleration to outside of function. Move it to class level and you will be fine. Because if it is in the class level, it will hold previous value and it will able to reach >1.
Here is How Coroutine yields work
Not sure if it's got anything to this two methods but when the objectToThrow is closer to the target the speed is slower on ThrowObject and ThrowBack the speed is the same but slower when it's closer by distance to the target and if the target is far the speed of the objectToMove movement will be faster.
I want the speed to be the same if it's closer or far or at least ot be able to set the speed for closer targets and for far targets.
IEnumerator ThrowObject(Transform objectToMove, float duration, InteractableItem primaryTarget)
{
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
Vector3 currentPos = objectToMove.position;
float time = Vector3.Distance(currentPos, primaryTarget.transform.position) / (duration - counter) * Time.deltaTime;
objectToMove.position = Vector3.MoveTowards(currentPos, primaryTarget.transform.position, time);
yield return null;
}
primaryTarget.gameObject.SetActive(false);
var naviParent = GameObject.Find("Navi Parent");
StartCoroutine(ThrowBack(objectToMove, naviParent.transform.position , duration, primaryTarget));
}
And :
IEnumerator ThrowBack(Transform objectToMove,Vector3 originalPosition , float duration, InteractableItem primaryTarget)
{
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
Vector3 currentPos = objectToMove.position;
float time = Vector3.Distance(currentPos, originalPosition) / (duration - counter) * Time.deltaTime;
objectToMove.position = Vector3.MoveTowards(currentPos, originalPosition, time);
yield return null;
}
objectToMove.localPosition = new Vector3(0, 0, 0);
if (primaryTarget.name == "kid_from_space_helmet")
{
var helmetParent = GameObject.Find("Helmet Parent");
GameObject Helmet = helmetParent.transform.Find("kid_from_space_helmet").gameObject;
Helmet.SetActive(true);
}
IsAlreadyThrown = false;
primaryTarget.interactableMode = InteractableItem.InteractableMode.Description;
primaryTarget.distance = 0;
}
I'm not quite sure why you're using Vector3.MoveTowards(), and I definitely don't understand what you're doing with time. I don't have much — any — experience with it, as I favor linear interpolation, or lerping.
But I've encountered similar trouble before, so it's probably a problem with your time. You're modifying it each iteration because you're basing it on the distance difference, whereas the example in the docs uses a constant speed.
That said, instead of pointing out what's wrong with your code, it'd be easier to write down a solution I use in these cases that should work.
private IEnumerator Throw(Transform objectToMove, float duration, Vector3 targetPosition)
{
float counter = 0f;
var initialPosition = objectToMove.transform.position;
while (counter < duration)
{
counter += Time.deltaTime;
var currentPosition = Vector3.Lerp(initialPosition, targetPosition, counter / duration);
objectToMove.position = currentPosition;
yield return null;
}
// ...
}
From what I've been able to understand, MoveTowards() is preferable when you'd like to move an object at a given speed, whereas Lerp() is better when it's the duration you want to control.
Additionally, for less coupling and cleaner code, I'd suggest that you chain this one coroutine twice and add the necessary code once the 2nd ("back") throw is done, like so:
private IEnumerator ThrowBackAndForth(Transform objectToMove, float duration, InteractableItem primaryTarget)
{
Vector3 originalPosition = objectToMove.transform.position;
Vector3 targetPosition = primaryTarget.transform.position;
// Throw "forward".
yield return StartCoroutine(Throw(objectToMove, duration, targetPosition));
// Throw "back".
yield return StartCoroutine(Throw(objectToMove, duration, originalPosition));
// And then do whatever you were doing after the while loop concluded in ThrowBack().
}
Of course, you know your code best, so customize as needed.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rotate : MonoBehaviour
{
public GameObject[] objectsToRotate;
public float duration = 5f;
public static bool desiredAngle = false;
private Vector3 lastFwd;
private bool startRot = true;
private void OnMouseDown()
{
if (startRot == true)
{
startRot = false;
StartCoroutine(StartRotationOfObjects());
}
}
private IEnumerator StartRotationOfObjects()
{
for (int i = 0; i < objectsToRotate.Length; i++)
{
// Random wait period before rotation starts
if (i == 0)
{
yield return new WaitForSeconds(0);
}
else
{
yield return new WaitForSeconds(Random.Range(0, 2f));
}
StartCoroutine(Rotates(objectsToRotate[i].transform, duration));
}
startRot = true;
}
private IEnumerator Rotates(Transform objectToRotate, float duration)
{
Quaternion startRot = objectToRotate.rotation;
float t = 0.0f;
lastFwd = objectToRotate.transform.forward;
while (t < duration)
{
t += Time.deltaTime;
objectToRotate.rotation = startRot * Quaternion.AngleAxis(t / duration * 360f, Vector3.up);
var curFwd = objectToRotate.transform.forward;
// measure the angle rotated since last frame:
var ang = Vector3.Angle(curFwd, lastFwd);
if (myApproximation(ang, 179f, 1f) == true)
{
desiredAngle = true;
}
yield return null;
}
objectToRotate.rotation = startRot;
desiredAngle = false;
}
private bool myApproximation(float a, float b, float tolerance)
{
return (Mathf.Abs(a - b) < tolerance);
}
}
I want to disable the OnMouseDown code so I will not be able to execute the Coroutine nonstop times. And after all the objects finished rotating then to enable the OnMouseDown again. I'm using the startRot flag for that but still it's true all the time and I can keep start the coroutine inside the OnMouseDown nonstop.
Below is a simplified version of your code.
private void OnMouseDown()
{
if (startRot == true)
{
startRot = false;
StartCoroutine(StartRotationOfObjects());
}
}
private IEnumerator StartRotationOfObjects()
{
for (int i = 0; i < objectsToRotate.Length; i++)
{
if (i == 0)
{
yield return new WaitForSeconds(0);
}
else
{
yield return new WaitForSeconds(Random.Range(0, 2f));
}
StartCoroutine(Rotates(objectsToRotate[i].transform, duration));
}
startRot = true;
}
private IEnumerator Rotates(Transform objectToRotate, float duration)
{
while (condition)
{
yield return null;
}
}
First, you have the OnMouseDown checking if true and it is on first run so it enters, sets it to false and start the coroutine.
Moving on with the loop. The first if check is useless since wait for 0 seconds. But it actually impacts the experience. So on first run, it enters the statement and basically does not wait. It then moves on to start the Rotates coroutine.
Entering the coroutine, it will reach a yield statement, place the coroutine in a list of coroutines to run and return. Back in the loop, it runs again, this time it will wait for to 2 seconds and run the next coroutine and so on until last one of the objectsToRotate collection.
Loop is done, startRot is set back to true. This means, even though, your coroutines may not be done, you can trigger new ones.
Your solution, either you want to wait for one rotation to be done before starting a new one with:
if (i == 0)
{
yield return new WaitForSeconds(0);
}
else
{
yield return new WaitForSeconds(Random.Range(0, 2f));
}
yield StartCoroutine(Rotates(objectsToRotate[i].transform, duration));
or you need to keep track of any coroutine running:
private int index = 0;
private IEnumerator StartRotationOfObjects()
{
for (int i = 0; i < objectsToRotate.Length; i++)
{
if (i == 0)
{
yield return new WaitForSeconds(0);
}
else
{
yield return new WaitForSeconds(Random.Range(0, 2f));
}
StartCoroutine(Rotates(objectsToRotate[i].transform, duration);
}
while(index > 0){ yield return null; }
startRot = true;
}
private IEnumerator Rotates(Transform objectToRotate, float duration)
{
index++;
while (condition)
{
yield return null;
}
index--;
}
Now anytime you start a coroutine, index is increased, at the end of the coroutine, it is decreased, in the main coroutine, you yield until index is back to 0.
i want to Rotate my object 0 to 90 degree with 1 speed --> then wait for 3 Sec ---> Then Rotate again 90 to 0 degree with 1 speed --> Then wait for 3 sec
i need above process in loop
i m success with it but it working only 1 time in Start Function
In update function its not working
below my Start Function code
IEnumerator Start()
{
StartCoroutine( RotateMe1(Vector3.forward * 90f, 1f));
yield return new WaitForSeconds(3);
StartCoroutine(RotateMe2(Vector3.forward * 0f, 1f));
yield return new WaitForSeconds(3);
}
IEnumerator RotateMe1(Vector3 byAngles1, float inTime1)
{
var fromAngle1 = transform.rotation;
var toAngle1 = Quaternion.Euler(transform.eulerAngles + byAngles1);
for (var t = 0f; t < 1; t += Time.deltaTime / inTime1)
{
transform.rotation = Quaternion.Lerp(fromAngle1, toAngle1, t);
yield return null;
}
}
IEnumerator RotateMe2(Vector3 byAngles2, float inTime2)
{
var fromAngle2 = transform.rotation;
var toAngle2 = Quaternion.Euler(transform.eulerAngles + byAngles2);
for (var t = 0f; t < 1; t += Time.deltaTime / inTime2)
{
transform.rotation = Quaternion.Lerp(fromAngle2, toAngle2, t);
yield return null;
}
}
Below my Update Function code
void Update()
{
StartCoroutine(RotateMe1(Vector3.forward * 90f, 1f));
StartCoroutine(Wait());
StartCoroutine(RotateMe2(Vector3.forward * 0f, 1f));
StartCoroutine(Wait());
}
IEnumerator RotateMe1(Vector3 byAngles1, float inTime1)
{
var fromAngle1 = transform.rotation;
var toAngle1 = Quaternion.Euler(transform.eulerAngles + byAngles1);
for (var t = 0f; t < 1; t += Time.deltaTime / inTime1)
{
transform.rotation = Quaternion.Lerp(fromAngle1, toAngle1, t);
yield return null;
}
}
IEnumerator RotateMe2(Vector3 byAngles2, float inTime2)
{
var fromAngle2 = transform.rotation;
var toAngle2 = Quaternion.Euler(transform.eulerAngles + byAngles2);
for (var t = 0f; t < 1; t += Time.deltaTime / inTime2)
{
transform.rotation = Quaternion.Lerp(fromAngle2, toAngle2, t);
yield return null;
}
}
IEnumerator Wait()
{
yield return new WaitForSeconds(3);
}
Here i recorded Video for issue of Update Function
i attached above script on AXE in attached above Video
PLease help me
What's happening is that you are starting cooruines every frame without waiting for the one to finish. This results to hundreds or even thousands of coroutines trying to modify an Object rotation at the-same time. Sorry, you can't wait in the Update function. You can try with a boolean variable but coroutines are used to accomplish this kind of stuff.
If you want it to rotate forever, put it in a while loop.
void Start()
{
StartCoroutine(rotateForever());
}
IEnumerator rotateForever()
{
while (true)
{
StartCoroutine(RotateMe1(Vector3.forward * 90f, 1f));
yield return new WaitForSeconds(3);
StartCoroutine(RotateMe2(Vector3.forward * 0f, 1f));
yield return new WaitForSeconds(3);
}
}
You can optimize the rotateForever function:
IEnumerator rotateForever()
{
WaitForSeconds waitTime = new WaitForSeconds(3);
while (true)
{
StartCoroutine(RotateMe1(Vector3.forward * 90f, 1f));
yield return waitTime;
StartCoroutine(RotateMe2(Vector3.forward * 0f, 1f));
yield return waitTime;
}
}
You can use a single coroutine to do the job, use it instead.
Basically, this.
void Start()
{
StartCoroutine(RotateMe(Vector3.forward * 90f, 1f));
}
IEnumerator RotateMe(Vector3 byAngles1, float inTime1)
{
while (true)
{
var fromAngle1 = transform.rotation;
var toAngle1 = Quaternion.Euler(transform.eulerAngles + byAngles1);
for (var t = 0f; t < 1; t += Time.deltaTime / inTime1)
{
transform.rotation = Quaternion.Lerp(fromAngle1, toAngle1, t);
yield return null;
}
yield return new WaitForSeconds(3);
byAngles1 *= -1;
}
}
I have been working on a object movement along a path Which i have been geting from Navmesh Unity3d
I am using coroutine in which i controled it with while loop as i can show
public void DrawPath(NavMeshPath pathParameter, GameObject go)
{
Debug.Log("path Parameter" + pathParameter.corners.Length);
if (agent == null || agent.path == null)
{
Debug.Log("Returning");
return;
}
line.material = matToApplyOnLineRenderer;
line.SetWidth(1f, 1f);
line.SetVertexCount(pathParameter.corners.Length);
allPoints = new Vector3[pathParameter.corners.Length];
for (int i = 0; i < pathParameter.corners.Length; i++)
{
allPoints[i] = pathParameter.corners[i];
line.SetPosition(i, pathParameter.corners[i]);
}
StartCoroutine(AnimateArrow(pathParameter));
//StartCoroutine(AnimateArrowHigh(pathParameter));
}
#endregion
#region AnimateArrows
void RunAgain()
{
StartCoroutine(AnimateArrow(Navpath));
}
IEnumerator AnimateArrow(NavMeshPath path)
{
Vector3 start;
Vector3 end;
while (true)
{
if (index > 0)
{
if (index != path.corners.Length - 1)
{
start = allPoints[index];
index += 1;
end = allPoints[index];
StopCoroutine("MoveObject");
StartCoroutine(MoveObject(arrow.transform, start, end, 3.0f));
yield return null;
}
else
{
index = 0;
RunAgain();
}
}
else if (index == 0)
{
start = allPoints[index];
arrow.transform.position = allPoints[index];
index += 1;
end = allPoints[index];
StopCoroutine("MoveObject");
StartCoroutine(MoveObject(arrow.transform, start, end, 3.0f));
yield return null;
}
}
}
IEnumerator MoveObject(Transform arrow, Vector3 startPos, Vector3 endPos, float time)
{
float i = 0.0f;
float rate = 1.0f / time;
journeyLength = Vector3.Distance(startPos, endPos);
float distCovered = (Time.time - startTime) * speed;
float fracJourney = distCovered / journeyLength;
while (i < 1.0f)
{
// Debug.Log("fracJourney In While" + fracJourney);
arrow.position = Vector3.LerpUnclamped(startPos, endPos, fracJourney);
yield return endPos;
}
Debug.LogError("Outside While");
}
But the problem is i have to move object on a constant speed but my object is gaining speed at every loop as i have to make movement in a loop so it tends to move until user wants to end it by input
guys plz help i dont understand what i am doing wrong in Coroutines that the speed of my objects is rising i wat it to stay constant but somehow its not working that way
thanks
As an alternative, you could utilize Unity's AnimationCurve class to map out all kinds of super smooth animation types easily:
You can define the curves in the inspector, or in code
public AnimationCurve Linear
{
get
{
return new AnimationCurve(new Keyframe(0, 0, 1, 1), new Keyframe(1, 1, 1, 1));
}
}
And you can define useage in a coroutine as such:
Vector2.Lerp (startPos, targetPos, aCurve.Evaluate(percentCompleted));
Where "percentCompleted" is your timer/TotalTimeToComplete.
A full example of lerping can be seen from this function:
IEnumerator CoTween(RectTransform aRect, float aTime, Vector2 aDistance, AnimationCurve aCurve, System.Action aCallback = null)
{
float startTime = Time.time;
Vector2 startPos = aRect.anchoredPosition;
Vector2 targetPos = aRect.anchoredPosition + aDistance;
float percentCompleted = 0;
while(Vector2.Distance(aRect.anchoredPosition,targetPos) > .5f && percentCompleted < 1){
percentCompleted = (Time.time - startTime) / aTime;
aRect.anchoredPosition = Vector2.Lerp (startPos, targetPos, aCurve.Evaluate(percentCompleted));
yield return new WaitForEndOfFrame();
if (aRect == null)
{
DeregisterObject(aRect);
yield break;
}
}
DeregisterObject(aRect);
mCallbacks.Add(aCallback);
yield break;
}
Check out this Tween library for more code examples: https://github.com/James9074/Unity-Juice-UI/blob/master/Juice.cs
while (i < 1.0f) will run forever because i is 0.0f and 0.0f is always < 1.0f and there is no place inside your while loop, where you increement i so that it will >= 1.0f. You need a way to exit that while loop. It should have looked like something below:
while (i < 1.0f){
i++ or i= Time.detaTime..... so that this loop will exist at some point.
}
Also your moving function is bad. The function below should do what you are trying to do:
bool isMoving = false;
IEnumerator MoveObject(Transform arrow, Vector3 startPos, Vector3 endPos, float time = 3)
{
//Make sure there is only one instance of this function running
if (isMoving)
{
yield break; ///exit if this is still running
}
isMoving = true;
float counter = 0;
while (counter < time)
{
counter += Time.deltaTime;
arrow.position = Vector3.Lerp(startPos, endPos, counter / time);
yield return null;
}
isMoving = false;
}
Also, in your AnimateArrow(NavMeshPath path) function, replace these three lines of code:
StopCoroutine("MoveObject");
StartCoroutine(MoveObject(arrow.transform, start, end, 3.0f));
yield return null;
with
yield return StartCoroutine(MoveObject(arrow.transform, start, end, 3.0f));
Doing this will wait the MoveObject function to finish before returning and running again in the while loop. You have to replace these inside if (index != path.corners.Length - 1) and else if (index == 0)
Maybe you could multiply the velocity by, say, 0.95f. This will make it accelerate, then stay at a constant speed, and then when you want it to stop it will gradually decelerate. Increasing the 0.95f will cause it to accelerate/decelerate faster.