Hoo boy, this turned out to be a long one. In short, I wanted a better way to implement cutscenes
in Project Walnut, and with a bit of Lua I ended up with the ability to run a bunch of
independent processes within the game and synchronize between them easily.
There’s a demo project showing it off here, but please, read on for details.
Project Walnut opens with a cutscene, and it was a pain in the ass to put together.
It’s a jumbled up mess of conditional statements, flag variables, and convulted logic for 10
seconds of time where you aren’t even playing the darn game that took me hours to put together,
and it’s a good chunk of why I took a month of from working on the game after slipping my
deadline. I wasn’t terribly motivated to work on the game when every little scene was going
to be that painful to put together.
Actually, I’ll go ahead and share what it looked like when I called it done:
The full setup and update methods are linked above. A good deal of the complexity is my fault: some sort
of event system would help things, or I could have built it as a state machine, etc. The thing that
complicates it the most though is that it has to be written as a function that gets called and returns,
maintaining state somewhere else (hence _hasKefkaAppeared and _hasKefkaStolenFamily).
They have separate threads of execution within the scripts (they call them tracks)
Those threads can block and wait on various things (time passing, events, signals, etc)
I immediately thought “Oh man, I want that.” The section on implementation (starting at slide 138)
mentions how they pull it off: each track is implemented as a continuation.
Essentially, a running function is stopped when it calls one of the wait-* functions, and restarted
from that exact point later on. From the example in the slides:
The two different tracks will block on each of the wait functions, and start at the next expression
when the wait criteria is met.
Getting that in C or Objective-C (what I’ve been using to this point) is possible,
but it’s hacky and I’m not sure how well it’d work in practice. Lua however, has coroutines
built into the language, and they can be used to implement much the same thing. And here’s how to do
it.
I’m going to work under the following assumptions:
You want the ability to wait for a certain time, or for a signal/event
to occur.
You keep track of how much time is passing per frame in your game loop.
Let’s take a look at a sample script:
The first thing we’ll need to do is turn this into a coroutine, which means that the above script
must be wrapped into a function:
That waitSeconds function needs to somehow register the running coroutine to be woken back up and
then suspend. Then in 2 seconds, something needs to resume that coroutine. So we get this:
And that’s it. Call wakeUpWaitingThreads from your game logic loop and you’ll be able to have a bunch
of functions waking up after sleeping for some period of time.
Note: this might not scale to thousands of coroutines. You might need to store them in a priority
queue or something at that point.
Time’s done, how about signals? Another little demo:
To implement signals, we’ll use another table, this one indexed by signal instead of coroutine. There’s
a pretty good chance you’ll want several coroutines waiting on the same signal, so we’ll store a list of
them for each signal in the table.
Easy peasy. The waitSignal stuff doesn’t need to be called per-frame; the wakeups only happen when
the signal function is called. You have to be a little careful with the order of operations when you’re
using signals to synchronize between coroutines, or you might end up with one waiting for a signal that
has already been sent.
On top of those two operations, I think you can implement whatever sort of waiting functions you like.
For example, I have a walkToPointAndSignal function in my game now that’ll find a path from the character’s
current location to whatever target point is given, move the character along that path, and send a signal
when it’s done. In fact, the script I’m using to test this out in my game now looks like this:
I’ve put together a sample project to show this all working together; you can take a look at
it on Bitbucket. It’s a little bit overkill in that it opens a full OpenGL window to
draw some text, but I wanted it to at least have the semblance of a game. It should build on
Windows (I used Visual Studio 2008), Mac OS X (Xcode 4.5), and Linux (umm, GNU make). You’ll
need to have development headers/libraries for SDL and SDL_ttf installed to build it.
The important bits are in:
WaitSupport.lua has the waitSeconds, waitSignal, signal, etc. stuff described above
Demo.lua uses those along with a couple functions defined in Main.cpp to give you a short
conversation between George and Bob.
Main.cpp opens up an SDL window and runs the main loop, which calls Lua’s wakeUpWaitingThreads
function.