In this game Level, I want to lift a structure (a fence) off the ground piece by piece. I am using SmoothDamp in a Coroutine for this. But when the Coroutine is triggered the objects sometimes overshoot the target and certain objects are show a lot of jitteriness until the coroutine is stopped. What could be the problem?
I tried this without TransformPoint(), didn’t fix it.
Code:
[SerializeField] private GameObject[] fences;
[SerializeField] private Vector3[] fences2ndPos;
public bool openFence = false;
[SerializeField] private float newPosY, waitForSeconds, smoothing;
void Start()
{
fences2ndPos = new Vector3[fences.Length];
for (int i = 0; i < fences.Length; i++)
{
Vector3 fencePos = fences[i].transform.TransformPoint(0, 0, 0);
fences2ndPos[i] = new Vector3(fencePos.x, fencePos.y + newPosY, fencePos.z);
}
}
void Update()
{
if (openFence)
{
StartCoroutine(FenceOpen());
}
else
{
StopAllCoroutines();
}
}
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
openFence = true;
}
}
IEnumerator FenceOpen()
{
Debug.Log("up");
for (int i = 0; i < fences.Length; i++)
{
yield return new WaitForSeconds(waitForSeconds);
Vector3 itemPos = fences[i].transform.position;
fences[i].transform.position = Vector3.SmoothDamp(itemPos, fences2ndPos[i], ref refPos, smoothing * Time.deltaTime);
if (i == (fences.Length - 1))
{
yield return new WaitForSeconds(0.1f);
openFence = false;
//stops the Coroutine
}
}
yield return null;
}
You’re starting a new coroutine every frame in update once openFence is set to true. I suspect this was not your intention.
Why not just star the coroutine in OnTriggerEnter instead of using the flag and the update loop?
I also suspect you don’t fully understand how Coroutines work. Once started, they will run until the method exits or the MonoBehaviour where it was started is disabled, deactivated or destroyed. You can suspend the method executing at any point with a yield instruction. The method will resume executing from the exact point it suspended (literally the next line) at some later point in time based on the yield instruction. That could be the next frame for a yield null or when a particular condition is met such as x time has passed with WaitForSeconds.
public float duration = 2.0f;
IEnumerator MyCoroutine()
{
var elapsed = 0.0f;
while(elapsed < duration)
{
// execute this code every frame until elapsed time passes
elapsed += Time.deltaTime;
yield return null; // half execution untill the next frame.
}
}
Note that this is not normally how IEnumerators are meant to be used. They’re generally used for looping over a set of values (enumerating a set). Unity and the StarCoroutine method are kind of flipping that idea on its head and saying, well if a Method that returns an IEnumerator provides a set of items to iterate/loop over one item at a time, and it suspends executing that method for every yield instruction, we can use that as a nice way to sequence things in order and each yield could be instructions about what conditions must be met before asking for the next one (which in turn starts executing the code in the method from where it left off).
The yieldinstructions inside an IEnumerator method are returning those instructions as an object that the StartCoroutine method is looping/iterating over in sequence. Once the method gets to the end and doesn’t yield any more instructions the “Coroutine” ends.
It’s tricky to wrap your head around with just an explication so here’s an example you can mess around with that uses a method as a kind of filter to only yield numbers greater than a set threshold.
You can see in the example that code execution jumps back and forth between the IEnumerator method and the code consuming the IEnumerator object whenever MoveNext() is called.