Coroutines In Unity3D – The Gritty Details

In my last post I talked about why some code is easier to write, read and maintain in Unity 3D when it’s written using coroutines, and touched on how Unity3D uses C# iterators to implement them. Now let’s look at how Unity3D makes these work as coroutines.

With iterators your code is responsible for creating an iterator and causing the iterator code to continue after a yield statement, such as using foreach in the MSDN example. In Unity’s world coroutines work if you are a class derived from MonoBehaviour. This class has a function StartCoroutine() which can start any function which returns IEnumerator as a coroutine.

Coroutines aren’t very useful until they include a yield statement. Unity uses the values returned by yield as scheduling information – to decide when to call the next portion of the coroutine. I’ve never found a complete list of acceptable return values from yield for Unity3D. Here’s the ones I know about:

It’s not multithreading!

There’s no multi-threading going on here. All scripts in Unity are called on a single thread, so no locking of variables is required. However other scripts may change global or object state between function calls or across a yield instruction. At times it becomes important to know when during a frame the coroutine will be executed. The overall execution order documentation says:

  • Most coroutines run after Update() and before LateUpdate()
  • Coroutines started from LateUpdate() run after LateUpdate()
  • return yield new WaitForFixedUpdate() // causes a coroutine to run after the next FixedUpdate()

As a final complication StartCoroutine() causes the coroutine to run immediately, until its first yield.

That All Sounds Hard

It’s not. I’ve dug in here, and documented all the gritty details. 90% of the time you don’t need these details at all. Looking at my coroutines they are almost all using

  • yield break;
  • yield return null;
  • yield return StartCoroutine().

What are you waiting for? If it sounds like coroutines will solve a problem for you – try them out!

Advertisement

C# Coroutines In Unity3D

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.

A Games Programmer at Full Frontal – Javascript Conference

I am a games programmer. I work in GLSL/HLSL, C++ and C#, mainly. What was I doing at a JavaScript conference?

  • The browser is everywhere
  • JavaScript powers programming in the browser
  • JavaScript performance is radically better than it used to be
  • WebGL is in Firefox / Chrome / Opera – GPU access in the browser
  • Browser API are increasingly covering games needs: full-screen, mouse capture

Of course it helps that the conference is in my home town, in the funky Duke of York cinema and has a reputation for being world-class event.

I enjoyed all the talks, but I’m only going to talk about the ones that resonated the most strongly with me:

CoffeeScript

Smoothing over the rough edges of JavaScript it’s a language that compiles into JavaScript. Normally I’d run a million miles from that sort of language translation, but with a mantra of “It’s just JavaScript” this might well be worth trying out. Jeremy Ashkenas spent a deal of time showing the clean, readable JavaScript that gets output in a very convincing way. I was sold on the idea of implementing new language ideas and proposals for JavaScript in CoffeeScript. Right now it feels like I need to do a much bigger project in JavaScript before I jump to the next level.

Excessive Enhancement

Phil Hawksworth’s talk resonated with me. On one level it was a plea to remember the user experience when adding fancy ‘shizzle’ to the web. The whole thing reminded me of front-ends for games. In games often the front-end is often completely bespoke, built on homegrown technology or possibly flash with a bunch of 3D thrown in for good measure. I’ve seen beautiful, logical, fun front-ends in video games, but they’re the exception, not the rule. We need to learn from the web!

Beyond The Planet Of The Geeks

I wish I could bottle Brendan Dawes’ charisma, enthusiasm and wittyness, and keep it on tap for grey horrible days like today! It was great to see someone enthuse and obsess about tools and quality: “whoever said a bad workman blames his tools was a moron and I want to smash his head in with a beautifully made hammer”. There isn’t a single, simple takeaway from this talk. My notes at the time say “explore serendipity, question everything, do more of this every day”. A nice thought!

You gotta do what you gotta do

In his 20% time at Google Marcin Wichary helps out on the Google doodles. He’s got unreasonable enthusiasm and set of smarts to back it up. “Nothing is impossible” he says – you can see that from his work. His presentation setup alone was:

  • presentation running in Safari on a Mac
  • Chrome sending accelerometer information from the Mac to Safari
  • his iPhone connecting to the same site as Safari and remote controlling the slides

I remember him talking about at least these doodles:

What’s so remarkable is not just the ideas, polish, and production values, but also that they really work in almost every browser out there – including IE6. They’re not just the latest html5 and Chrome only shizzle. The dancing 2D animation in the last link was done by exporting a whole image for each frame, calculating the minimum different rectangular image region, packing all these into a single image, then having JavaScript add the next frames difference image to the page on a timer. It’s such a neat hack I’m sure Marcin must have been a games programmer back in the day 🙂

His final thought was if you’re not doing something which feels hacky you’re certainly not pushing the state of the art!

More than that

What I liked most of all was the warmth of everyone that I met. It’s a friendly community, make no mistake 🙂 It was great to see Max Williams again who took the time to introduce me to people.

My only disappointment was that I couldn’t stay at the after-party for very long. Big mistake! Not only did I miss a lot of free beer, but I missed the chance to meet and share with a lot of interesting people. Judging by the tweets from the following morning I also missed a killer hangover. Next year I’ll make sure I have no plans for the next day, and painkillers at the ready.

Conflicts in Git Rebasing

After using Git for a while I’m finding the most confusing aspect of it is rebasing. The git-rebase man page says “forward-port local commits to the updated upstream head”. Hmm. Thankfully James Bowes posted a solid explanation of the basic principles.

Which leaves me wondering about corner cases. I have local commits which I’ve submitted as patches upstream. Now I’ll be fetching my changes from upstream, one of which has an updated comment and the other has had minor edits applied. How does Git handle this? In a good way! For commits which are already upstream, with identical content, even with differing comments, rebasing removes the local commit. That’s exactly what you want. For example simply specify the upstream branch to rebase against:

git rebase origin/master

In cases where the commit contents have received minor edits the rebase will pause. Assuming you are happy to drop the local commit tell Git to do exactly that:

git rebase --skip

or if there’s any doubt tell Git to rollback to before the rebase started:

git rebase --abort

Navigating Source Code

Recently I’ve been browsing free software projects on Linux, and wanting to navigate a mixture of C and C++. I’m used to Visual Studio on Windows, more accurately the excellent Visual Assist plug-in, which makes code navigation and completion work for C++. For me this is the best feature of the whole setup: Visual Studio is a way to run Visual Assist. I’ve really missed this on Linux.

Years ago I used a combination tag files generated during the build and some custom Crisp macros for source code navigation. Despite not providing auto-completion this was in some ways better than Visual Assist as tags are generated from pre-processed source:

  • C macros and condition includes do not break the tagging
  • Tag files are per-platform, per build-type and exact

Still, what I wanted was something quick to setup and use with Emacs. A while back Pete Ivey recommended GNU Global, so I thought I’d try that out. Overall it works really well. It’s not without it’s flaws – some variable declarations are hidden in macros in the code I’m reading. Those get missed from the declaration list, but do get caught as symbols. Ideally I’d expand the Emacs macros to automatically search for any symbol if a declaration isn’t found.

The pendant in me want to set up Dehydra and hook it into the build system. The pragmatist says that overall this is working well. For now I’m sticking with it.

What are other people using?