- Home /
When using coroutines, what should I be wary of?
I have discovered coroutines (as of yesterday) and I have come to the conclusion that they are awesome. Even something as simple as jumping in a 2D platformer has benefitted from the use of a coroutine on buttondown; thanks to coroutines, I now have less code and better functionality! (Less state variables to look at in my update calls!)
Naturally, discovering the greatness that is coroutines, I'm thinking I should attempt to use them whenever I can. Yet, as a developer, I know that overzealousness and overdependence on a single paradigm can be foolish - at best, inefficient; at worst, crippling.
I believe that I know the benefits of using coroutines. What, if any, are the drawbacks? What are the potential pitfalls that I can run int? Finally, what other options do I have to turn to if the drawbacks/pitfalls of coroutines will cause crippling problems?
Thanks for your input! I know that this is a broad question, but I'm hoping that it'll help me (and any other Unity developers that want to learn best practices and know the platform better).
Answer by CHPedersen · Jul 18, 2014 at 08:41 AM
Coroutines are indeed awesome. :) Mostly, I think, because they're such a creative use of .Net's iterators and the MoveNext method that I highly doubt the .Net designers saw it coming, but it's really a testimony to the flexibility of that particular language feature. :)
I recommend you read this blog post about coroutines, from Richard Fine aka SuperPig, who is a really experienced developer and took the time to carefully explain exactly what a coroutine is and how it's implemented in Unity:
http://www.altdev.co/2011/07/07/unity3d-coroutines-in-detail/
Understanding this article in-depth will help you answer most of your questions about coroutines and their pitfalls yourself in the future, also. But, I've taken the liberty to add a quick summary of the common misconceptions and misunderstandings here anyway:
Coroutines are not threads. The code you put in a coroutine is executed entirely in serial by the main thread. There is a scheduled time in the script execution order where coroutines are executed, please see this page: http://docs.unity3d.com/Manual/ExecutionOrder.html
Because coroutines are not threads, they are not designed to perform work in parallel. Their primary purpose is to spread work out over multiple frames so it either animates nicely if it's a visual effect, or prevents stalling the engine and causing a hiccup if it's a concentrated amount of heavy workload, like multiple object instantiations of heavy meshes.
A coroutine is an object that implements IEnumerator. This object is added to a list of currently active coroutines inside MonoBehaviour when StartCoroutine is called. This list is then traversed once per Update, and the IEnumerator's MoveNext method is called, which triggers the execution of the next cycle in the coroutine. Therefore, if you kill the MonoBehaviour you used to start a coroutine, the coroutine dies, too. They are not totally fire-and-forget.
Always carefully define a coroutine's exit-condition(s). They are often used to animate something from A to B because they exit and terminate themselves so beautifully when written right. But if the loop's exit conditions is ill-defined, it might run forever, and you won't find out until something starts acting weird.
Beware of starting more coroutines of the same type based on some user-interaction. You could end up having multiple coroutines try to effect changes to the same program state, and thus, they will end up fighting over who gets to do the work. As an example, suppose you have a coroutine open and close a door. Clicking once opens, clicking again closes. If I click twice really fast, two coroutines open and start fighting over opening/closing the door. The result is a door that either twitches, or is frozen in mid-animation.
Probably more I've forgotten. Please add as comments if you think of something. ;)
Hey, great list! I've been careful to use StopCoroutine() when necessary. However, I do have a coroutine on my $$anonymous$$ainCamera that sets the angle based on the player's X velocity. I've included code below; is this a trap (trap #5, to be exact)?
public class CameraController : $$anonymous$$onoBehaviour {
public Transform target;
public float maxTime = 10.0f;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
var pos = this.transform.position;
pos.x = target.position.x;
pos.y = target.position.y;
this.transform.position = pos;
var angle = this.transform.rotation;
angle.y = (target.rigidbody2D.velocity.x / 2) * $$anonymous$$athf.Deg2Rad;
StartCoroutine("AngleCamera", angle); // This is the trap I'm worried about....?
}
private IEnumerator AngleCamera (Quaternion target) {
float elapsedTime = 0.0f;
Quaternion startingPosition = this.transform.rotation;
while (elapsedTime < maxTime) {
elapsedTime += Time.deltaTime;
transform.rotation = Quaternion.Slerp(this.transform.rotation, target, elapsedTime / maxTime);
yield return new WaitForEndOfFrame();
}
}
}
Specifically.....I'm starting a coroutine in an update statement, which means I'm starting one on every frame. This essentially means that they won't finish until a character stops moving. So what I should do is use a state variable for the quaternion rotation, and simply have the coroutine start in the Start() function.....and have it run forever, attempting to target the target rotation. If the rotation is complete (if this.transform.rotation is close enough to target.rotation), it'll yield return null; if not, it'll attempt to slerp to the correct transform. This means that I'd only start one coroutine that would run forever, ins$$anonymous$$d of running multiple routines?
Yes jedd.ahyoung, you are starting a coroutine every frame. So if your app was running at 60 fps, you will have 600 coroutines running at any one time, all trying to do something similar, and only the last one executed doing anything. Apart from that issue, I'm surprised this code is working at all. You are directly manipulating the 'y' of a Quaternion (i.e. angle). Quaternion components are not angles, and they are not measured in degrees. Nor is their representation stable (i.e. they go from -1 to 1 but you cannot simply use values in the -1 to 1 range and manipulate them).
As for your rotation code, assu$$anonymous$$g you got the angle issue sorted out, you could replace the whole coroutine with a line like this in Update():
transform.rotation = Quaternion.RotateTowards(transform.rotation, angle, time.deltaTime * speed);
This code is slightly different in that it rotates at a specific speed (degrees per second), where your code rotates in fixed time no matter how far apart the actual and descried rotation are.
Hmm. I'll look into that method. I'd chosen Slerp
because of the transition easing. Doing it in the update frame might be a better idea, since with my solution I'd be starting a never-ending coroutine (and as such, what's the point, right?).
@robertbu As for the angle issue, I don't think I'm manipulating the quaternion directly. x
, y
, and z
are properties, not fields; I believe that they correspond to Euler angles and have their own getters and setters to manipulate the underlying quaternion. This is why, although transform.rotation
is a quaternion, you can set transform.rotation.x
and transform.rotation.y
to degrees, like you can in the editor. That said, I'll check the documentation on this to make sure that I'm correct (as I wouldn't want to leave that statement unsupported).
@robertbu Well, it turns out that you're right. I looked it up here and it looks as though those properties are actually setting the quaternion directly, not the Euler angles. The strange thing is.....it's working. Checking the editor, the rotation degrees are actually matching up perfectly. I know a little bit about quaternions, but defintely not enough to state that I know what I'm doing....this is something I'll have to solve, now! If you or anyone else knows more about Quaternions, perhaps you could share some knowledge about why this is working?
Answer by smallbit · Jul 18, 2014 at 08:20 AM
Coroutines are cool and one of the essential parts of any of my Unity projects. One problem you may encounter is that if you use the WaitForSeconds operator in your coroutine and you pause your game (timescale = 0) It will never yield. Than you probably need to write a custom WaitForSeconds that uses (realtimeSinceStartup value instead of Time.deltaTime). link text.
Adding to this and generalizing, the biggest thing you need to watch out for with coroutines is to make sure they actually -have- an Ending Condition in general, else it will loop through infinitely, freezing up Unity~
I hadn't thought of this. (Incidentally, from reading your answer, I now have learned how to pause games. :D) I'll definitely look out for this pitfall, although I'd imagine that you'd want most coroutines to pause execution as well? I suppose it depends on your use case.
Your answer
Follow this Question
Related Questions
Working projectile scripts do not work due to if condition 1 Answer
Method keeps looping when using waitForSeconds 1 Answer
Segment within a nested If does not get evaluated 2 Answers
Make a coroutine run when Time.timeScale = 0; 3 Answers
convert timer from update() to coroutine or invokerepeating 0 Answers