- Home /
Why Won't My Coroutine Yield?
This section of my code won't yield. It just goes right through it.
while(angle > 1.0f)
{
my_transform.rotation = Quaternion.Slerp(my_transform.rotation, fwdRotation, Time.deltaTime * 2.0f);
angle = Quaternion.Angle(my_transform.rotation, fwdRotation);
Debug.Log ("Inside loop" + angle);
yield return null;
}
I know it does, because I can watch it happen during run-time, because the following line of code happens BEFORE the while loop above is complete.
yield return StartCoroutine(MoveBall(col));
rigidbody.WakeUp(); //<<<<<<-------THIS ONE RIGHT HERE SHOULDN'T HAPPEN UNTIL THE WHILE LOOP IS DONE AND THE ORB IS DESTROYED.
Any ideas why this is happening? Here's the full code...
using UnityEngine;
using System.Collections;
public class EmptyBall : MonoBehaviour
{
public Ball.BallTypes this_ball_type;
public PlayerCharacter this_player;
public AudioClip capture_attempt;
public AudioClip attempting_capture;
public AudioClip capture_success;
public AudioClip capture_fail;
public GameObject capture_orb_prefab;
private Transform my_transform;
private RaycastHit hit;
private float distance_to_ground;
private CalculateCapture calculate_capture_script = new CalculateCapture();
void Start()
{
my_transform = transform;
}
void Update()
{
if(Physics.Raycast(transform.position, -Vector3.up, out hit)){
distance_to_ground = hit.distance;
}
}
void OnCollisionEnter(Collision col)
{
if(col.gameObject.tag == "Capturable")
{
Monster this_monster = col.gameObject.GetComponent<Monster>();
if(!this_monster.is_captured)
{
audio.PlayOneShot(capture_attempt);
col.gameObject.GetComponent<Animation>().enabled = false;
StartCoroutine(Capture(col));
}
else
{
}
}
else
{
Destroy(this.gameObject);
}
}
private IEnumerator Capture(Collision col)
{
yield return StartCoroutine(MoveBall(col));
rigidbody.WakeUp();
while(distance_to_ground > 0.2f)
{
yield return null;
}
rigidbody.isKinematic = true;
yield return StartCoroutine(TryToCatch(col));
}
private IEnumerator MoveBall(Collision col)
{
Vector3 move_to = new Vector3(transform.position.x-1.5f, col.contacts[0].point.y+1.5f, transform.position.z-1.5f);
while(Vector3.Distance(transform.position, move_to) > 0.01f)
{
rigidbody.velocity = Vector3.zero;
rigidbody.angularVelocity = Vector3.zero;
rigidbody.Sleep();
transform.LookAt(col.transform.position);
transform.position = Vector3.Lerp(transform.position, move_to, 5f * Time.deltaTime);
yield return null;
}
animation["Open_Top"].speed = 5;
animation.Play("Open_Top");
GameObject orb = Instantiate(capture_orb_prefab, col.gameObject.GetComponentInChildren<Renderer>().renderer.bounds.center, Quaternion.identity) as GameObject;
col.gameObject.SetActive(false);
while(Vector3.Distance(my_transform.position, orb.transform.position) > 0.01f)
{
Vector3 orb_target = new Vector3(my_transform.position.x, my_transform.position.y, my_transform.position.z);
orb.transform.position = Vector3.Lerp(orb.transform.position, orb_target, 2.7f * Time.deltaTime);
yield return null;
}
orb.transform.parent = my_transform;
animation["Close_Top"].speed = -5f;
animation["Close_Top"].time = animation["Close_Top"].length;
animation.Play("Close_Top");
Vector3 flatFwd = new Vector3(my_transform.forward.x, 0, my_transform.forward.z);
Quaternion fwdRotation = Quaternion.LookRotation(flatFwd, Vector3.up);
float angle = Quaternion.Angle(my_transform.rotation, fwdRotation);
Debug.Log (angle);
while(angle > 1.0f)
{
my_transform.rotation = Quaternion.Slerp(my_transform.rotation, fwdRotation, Time.deltaTime * 2.0f);
angle = Quaternion.Angle(my_transform.rotation, fwdRotation);
Debug.Log ("Inside loop" + angle);
yield return null;
}
Destroy(orb);
yield return null;
}
private IEnumerator TryToCatch(Collision col)
{
Monster this_monster = col.gameObject.GetComponent<Monster>();
audio.PlayOneShot(attempting_capture);
yield return new WaitForSeconds(attempting_capture.length+1);
bool try_to_capture = calculate_capture_script.AttemptCapture(this_monster.status_condition, this_ball_type, this_monster.cur_hp,
this_monster.cur_max_hp, this_monster.capture_rate);
if(try_to_capture){
this_monster.is_captured = true;
this_monster.trainers_name = this_player.players_name;
Monster temp = this_monster;
PlayerMonsterData data_holder_monster = new PlayerMonsterData (temp.is_setup, temp.is_captured, temp.trainers_name, temp.monster_name,
temp.nick_name,
temp.is_from_trade, temp.level, temp.gender, temp.nature, temp.max_hp,
temp.cur_max_hp, temp.max_atk, temp.max_def, temp.max_spatk, temp.max_spdef,
temp.max_spd, temp.cur_hp, temp.cur_atk, temp.cur_def, temp.cur_spatk,
temp.cur_spdef, temp.cur_spd, temp.last_required_exp, temp.current_exp,
temp.next_required_exp);
if(this_player.players_monster_roster.Count < 6)
{
this_player.players_monster_roster.Add(data_holder_monster);
}
else
{
this_player.players_monster_inventory.Add(data_holder_monster);
}
this_monster.is_captured = false;
this_monster.SetDead();
audio.PlayOneShot(capture_success);
yield return new WaitForSeconds(capture_success.length);
}
else
{
animation["Open_Top"].speed = 5;
animation.Play("Open_Top");
audio.PlayOneShot(capture_fail);
yield return new WaitForSeconds(animation["Open_Top"].length);
col.gameObject.SetActive(true);
col.gameObject.GetComponent<Animation>().enabled = true;
animation["Close_Top"].speed = -5f;
animation["Close_Top"].time = animation["Close_Top"].length;
animation.Play("Close_Top");
yield return new WaitForSeconds(capture_fail.length);
}
Destroy(gameObject);
yield return null;
}
}
What does "Debug.Log ("Inside loop" + angle);" say? Also, put a log before the WakeUp.
Also, be careful you are not running more than one "StartCoroutine(Capture(col));" at once.
Does Orb have collision and coroutine code as well? Asking because when you Instantiate Orb in the same spot as the previous Col object, it might throw your transform calculations to something unexpected.
Inside the loop the log shows it slowly decrementing down from where it starts (44.xxx). There's only one StartCoroutine(Capture(col)) running.
Orb doesn't have a collider. It's just a prefab of a particle effect and it has no coroutine.
Although I admit that Coroutines still confuse me, I'm reasonably certain how IEnumerator functions work: Every time they are called, they return the next yielded value from the function.
That means that they return immediately after seeing a yield. They need to, since otherwise you couldn't use them in foreach loops.
It looks to me like your code is saying:
Capture(col)
- Wait for $$anonymous$$oveBall to yield
-- $$anonymous$$oveBall does the *first* iteration of its first distance loop, then hits a yield
-Capture stops waiting and continues, yielding it's new value (the result)
During the next Update() step? (or whenever the next call of Coroutines is),
the Unity engine does the following in some order:
- WakeUp()ing the rigidbody.
- Doing the next iteration of the distance loop in TryToCatch()
Unfortunately, I don't know of a way to wait for a Coroutine to complete before returning. I usually use events and delegates for this. Coroutines are NOT synchronous, and yield return
will not make them so.
Alternatively, one obvious solution to solve the stated problem would be to put the rigidbody.WakeUp() code at the end of $$anonymous$$oveBall() itself.
This is also incorrect. If that was true, the rigidbody.WakeUp();
would occur right before the animation["Open_Top"].speed = 5;
However, it doesn't. The rigidbody.Wakeup();
does not occur until the while loop starts.
...and an IEnumerator IS a Coroutine. >.> Anyway...
"The execution of a coroutine can be paused at any point using the yield statement. The yield return value specifies when the coroutine is resumed. Coroutines are excellent when modelling behaviour over several frames. Coroutines have virtually no performance overhead. StartCoroutine function always returns immediately, however you can yield the result. This will wait until the coroutine has finished execution." -- Taken from the Unity documentation on Coroutines. When I declare the coroutine private IEnumerator Capture(Collider col)
the very first line in that coroutine reads yield return StartCoroutine($$anonymous$$oveBall(col));
. Now since I called that Coroutine, inside of a Coroutine with the yield return statement, that means the coroutine "Capture" will not continue executing until the coroutine "$$anonymous$$oveBall" is completed.
Yea, yield return should block execution for the Coroutine to finish before proceeding. Why it's not working here is the mystery. At this point, I would start commenting out code in chunks, and see if anything changes. Adding debug.logs to any information that may be useful. Hopefully its not some other script or object in the scene causing problems.
"Coroutines are NOT synchronous, and yield return will not make them so" huh? you just say StartCoroutine(...) to make it run "now" (synchronously) and yield return.. to make the calling routine wait.
Answer by DRRosen3 · Feb 22, 2015 at 08:16 AM
Solved it myself. The problem was with lines 60 through 73.
while(Vector3.Distance(transform.position, move_to) > 0.01f)
{
rigidbody.velocity = Vector3.zero;
rigidbody.angularVelocity = Vector3.zero;
rigidbody.Sleep();
transform.LookAt(col.transform.position);
transform.position = Vector3.Lerp(transform.position, move_to, 5f * Time.deltaTime);
yield return null;
}
This while loop was causing the rigibody to wake up and go to sleep each time it iterated. Upon its final iteration, the rigidbody was already awake. This still doesn't explain why the loop that follows wouldn't fully execute however.
"was causing the rigibody to wake up ..." thank God I was right as usual ;-)
glad you found what I said was causing the problem ;-)
Answer by Fattie · Feb 22, 2015 at 04:55 AM
hi Rosen, what's up
1) you know, you have a problem that your code is not really usable dude! No routine should be more than, say, 6 or 7 lines of code. You'll have to take 1 or 2 minutes to refactor the code so that it is clear. (To begin with , it would be impossible to find any flaws, in the current code, since it's just long-form "goes on-and-on" code, there's no named concepts, you know?)
After you do that ...
2) your thinking and use of coroutines seems quite correct.
3) as Star already told you add two lines of debug
Debug.Log("before...");
yield return StartCoroutine(MoveBall(col));
Debug.Log("after...");
rigidbody.WakeUp();
and we'll be a mile closer to understanding the problem. it's almost certainly simply the case that something else is waking the rigidbody, which can happen for many reasons.
And indeed (even more importantly) to repeat what Star said
Debug.Log("I am in the OnCollision and about to run Capture...");
StartCoroutine(Capture(col));
as it could surely be running more than one of those. (I find it tedious and tricky to know just what physx is doing with OnCollisionEnter)
In any event be sure to go back to point (1), y'hear! :) it's not workable like that .. and it will only take a minute to write it "as code" with separate concepts.
For example: your incredibly long MoveBall passage should look something like this (this is purely an example)
whatever MoveBall()
{
CalculateCurrentCriticalBallValues( ball b )
newPosition = GuessBestNewPosition( ball B, enemies E )
goodPositon = CheckIfPositionPossible( newPosition )
while ( ! goodPosition )
AdjustPositionSlightlyAllowingForWalls()
yield return StartCoroutine( MoveOnSplineTo( newPosition )
BeginPlayingExplosionAnimations()
yield return new WaitForSeconds( knownExplosionAnimeTime )
yield return StartCoroutine( TrimFinalPosition( b ) )
BeginSettleAnimations()
yield return new WaitForSeconds( knownSettleTime )
}
Note that that is perfectly understandable. If you read through that you know EXACTLY what is happening - right?
there's a fantastic principle in coding .. "Code should be self-explanatory".
(essentially: "use only very long object and method names, which totally explain what is happening when read as prose")
another thing you hear which means the same is "you shouldn't even need comments". ie, the code should be self-explanatory. Hope it helps!
Anything before "After you do that..." and anything after "...is doing with OnCOllisionEnter)" is CO$$anonymous$$PLETELY irrelevant and unhelpful. I fail to see why people CONTINUE to (try to) bring people down, in a place where you're meant to be HELPFUL.
dude I think you are just reading tone in to it that is not there. what would you want someone to say? i love you but your code is unusable, since no routine should be more than a handful of lines of code - you could have rewritten in in the time we've both spent on these comments now! I will add some smileys to cheer things up
Oh and you're welcome for the time I spent on providing the answer to your problem.
Dude? You don't have to be patronizing to be helpful. 1. "your code is horrible dude!" is completely disrespectful. 2. "You are miles, a lightyear, away from worrying about stuff like whether coroutines work." is disrespectful and degrading. As I said before you don't have to patronize someone ("i love you but your code is unusable") to help them. You don't seem to see that, so I'm not going to waste my time trying to explain that concept to you. However, the problem has NOTHING to do with how many lines of code I put into the routine...and that's what I'm here for, the problem. Not your opinion of how many lines should or should not be in a code. Oh...and I've nothing to be grateful for, you haven't solved anything. You'll know when the problem is answered because I'll mark it answered.
You sound silly. language like "your code is horrible dude!" can be read with a smiley, it's nothing. the actual literal reason you have a problem is that the code is not factored: as I said your use of coroutines is on the face of it correct. i have literally provided the explanation (which Star already did, as I said) ("something else is waking the rigidbody") and I have literally provided the solution! (after you refactor, add the three lines of Debug and you will have the solution instantly)
Your answer
Follow this Question
Related Questions
Waiting twice inside coroutine (C#) 2 Answers
What am I doing wrong with IEnumerator? 1 Answer
Yield Wait for Seconds (A little help here) 2 Answers
C# How to ping servers and get server latency? 1 Answer
WaitForSeconds Not Working 4 Answers