Why?
It seems like the best way to explain coroutines is by example. We’ll look at a portion of the class which controls the front-end in a game, and doesn’t use coroutines. It’s not horrible, but the logic is scattered across a delegate – OnPlayButton() and UpdateFrame(). We need a class member to hold a reference to the active UI page.
public class Frontend: GameState { // need a member variable to hold a reference between functions private ZoneSelectUIPage mZoneSelectUIPage; public void Create() { mPlayButton = UITextButton.Create(GlobalResources.Instance.ButtonToolkit, "blank_up.png", "blank_down.png", font, fontMat, fontDropShadowMat, fontHighlightMat); mPlayButton.SetText("Play"); mPlayButton.pixelsFromTopLeft(20, 20); // add delegate to handle button presses mPlayButton.AddOnTouchUpInside(OnPlayButton); ... } private void OnPlayButton(UIButton sender) { Hide(); mZoneSelectUIPage = ZoneSelectUIPage.create(); mZoneSelectUIPage.Show(); } // called once a frame - that's how the game engine works! public GameState UpdateFrame() { if (mZoneSelectUIPage != null) { if (mZoneSelectUIPage.Level >= 0) { GlobalResources.Instance.SetLevel(mZoneSelectUIPage.Zone, mZoneSelectUIPage.Level); mNextState = LevelLoadState.Instance; } if (mZoneSelectUIPage.Done) { mZoneSelectUIPage.Destroy(); mZoneSelectUIPage = null; Show(); } } return mNextState; } }
On top of that there’s also a bunch of code to test whether mZoneSelectUIPage is null – we may not have one! The page also needs to explicitly contain a property ZoneSelectUIPage.Done which we can test to see if it’s work is done. Re-writing that using coroutines moves the logic into a single function CoroutineUpdate() and makes the logic linear to read.
public class Frontend: MonoBehaviour, GameState { public void Create() { mPlayButton = UITextButton.Create(GlobalResources.Instance.ButtonToolkit, "blank_up.png", "blank_down.png", font, fontMat, fontDropShadowMat, fontHighlightMat); mPlayButton.SetText("Play"); mPlayButton.pixelsFromTopLeft(20, 20); // now we start a coroutine to do our work StartCoroutine(CoroutineUpdate()); ... } // called once a frame - that's how the game engine works! // bridge to the engine by having the coroutine set mNextState public GameState UpdateFrame() { return mNextState; } public IEnumerator CoroutineUpdate() { while (true) { // wait until the next frame update yield return null; if (mPlayButton.TouchUp) { Hide(); ZoneSelectUIPage zoneSelectUIPage = ZoneSelectUIPage.create(); yield return StartCoroutine(zoneSelectUIPage.CoroutineUpdate()); if (zoneSelectUIPage.Level >= 0) { GlobalResources.Instance.SetLevel(zoneSelectUIPage.Zone, zoneSelectUIPage.Level); mNextState = LevelLoadState.Instance; } zoneSelectUIPage.Destroy(); // exit the coroutine yield break; } } } }
Also we no longer have to check whether zoneSelectUIPage is null – it’s a local variable we’ve just created – or whether it’s finished. Once ZoneSelectUIPage.CoroutineUpdate() returns it’s done.
For this simple example the code is neater and simpler. As more buttons and pages are added the more of a win using coroutines is. Often game classes will end up with a state enumeration and state member variable, and the UpdateFrame() function becomes a gigantic switch statement. Each state may need additional variables, which become member variables. Over time it becomes hard to follow the logic and remember which variables affect which state. I’ve always found code like this to be hard to write and harder to read. With coroutines the entire state machine is written in a linear fashion with local variables and yield statements. There’s a more direct mapping of the code written to its effect. That’s got to be a win!
C# Support
C#, of course, doesn’t really support coroutines. You’ve probably noticed that I’m using yield statements which are part of iterators. Iterators provide the language infrastructure to return a value from partway through a function (using yield), and then start executing the function again at a later time from just after the yield statement. Unity3D adds support for using iterators as coroutines, which I’ll cover in my next post.
Pingback: Coroutines In Unity3D – The Gritty Details | Julian Adams
I don’t really like using coroutines. If you are working with other programmers a lot of times they don’t know how many couroutines you have going on and can’t predict when they will return. It can get messy. Can’t you use a finite state machine instead where each state is a subclass of a state parent class? So instead of the switch statement you just call stateupdate() on whichever state is currently loaded?
If you’re working in Unity 3D with GameObjects and Components then you have similar issues with seeing what’s running and when the logic will stop. If you’re in the Unity editor you can see which GameObjects you have – and I admit the same isn’t true of coroutines. Really that’s a tooling issue – Unity could add that to the UI. I’d love it if they did! It’s never bitten me though.
My point is that for state machine code coroutines are the neatest way I’ve come across to write the code with the least boilerplate. All the attempts I’ve seen to do it with classes use a state enum and a switch statement, or a bunch of derived state classes. The former tends to need a lot of member variables many of which are only valid in some states. I find it gets confusing and error-prone pretty quickly. The latter tends to involve a lot of boiler-plate which gets in the way of changing the state machine.
That said if I had a way to do state machines which was better for me I’d go with it. I’d love to see your state machine code.