Using ES6 Generators to Manage Dialog Flow in a Game
Remember how great it was when you first started using promises to combat javascript callback hell? Your code was much more concise, easier to understand, and didn't nest a million levels deep...
Well EcmaScript 6 generators promise (no pun intended) to bring about a similar revolution in the way we deal with asynchronous code. We've been hearing about how complex code dealing with API calls and user interactions will be able to look just like synchronous code, with everything happening in one flow and being much easier to follow. It sounded great, I looked over some brief code examples and it made sense, I played around with it myself and started to understand all the function*
s and yield
s, but I was waiting for one more thing: a example of a real world app benefiting from generators.
I ran into a nice use case in a side project today, so I decided to write that generator example app myself. Feel free to jump ahead to the full live demo and its source code.
Game Dialog Good and Bad
A series of dialog text in a game is a great example of where generators excel. In the game designer's mind, a series of dialog blurbs come into the game one after the other in a very linear and simple way based on the user's input. Using callbacks and nesting each blurb another level deep just gets in the way.
makeTextbox('Hello! Click to continue.');
events.on('click', function() {
makeTextbox('I\'m the second text bubble, yay!');
events.on('click', function() {
makeTextbox('I hope we don\'t have too many more of these...');
});
});
There is no benefit to the developer for nesting all of these like this. And this is not even considering branching options or more complex conditions.
With generators, we can flatten it out like this:
makeTextbox('Hello! Click to continue.');
yield events.on('click', function() {
makeTextbox('I\'m the second text bubble, yay!');
generator.next();
});
yield events.on('click', function() {
makeTextbox('And we can have as many more as we want without nesting.');
generator.next();
});
How Generators Work
I won't bother repeating what's already well documented on MDN and explained in a very thorough article at 2ality, but here's the basics that you need to get through this article.
When using ES6 generators, you'll be declaring a generator function with the function* syntax:
function* gen() {
yield 1;
yield 2;
yield 3;
}
Then using it to create a generator:
var g = gen();
And calling next
to progress through the yield statements:
g.next() // 1
g.next() // 2
g.next() // 3
Notice how in the generator function, execution pauses until we call next
again. This is what we will exploit to write code that "waits" for something external to happen, like an API call to return or a user to perform an action.
Getting Set Up
As of this writing, native support for generators is not very thorough. An easy way to get around this and start using generators now is to use something like Babel, a compiler that compiles ES6 to ES5.
Even easier, my example project mentioned above (available on Github) will give you a full setup with Gulp, Browserify, and Babel, and compile any changes to the code automatically for you.
A Fully Featured Example
Go ahead and run through the live demo really quickly. Let's take a look at the generator code that makes that happen (you can see the full source of this file here).
That sure is a lot easier to read than a ton of nested callbacks or promises.
We're using the yield
operator to show when our code will pause, and this.flowGen.next()
to indicate that the previous event has happened and we can continue. The result is a flat code structure despite having many layers of asynchronous code waiting for user input.
From Here
I encourage you to pull down the demo source code and play around with it or build something yourself, as building something real that takes advantage of generators is really the hardest part of learning them.