top | item 13366153

Using Lua coroutines to create an RPG dialogue system

82 points| stevekemp | 9 years ago |lua.space

55 comments

order
[+] shinymark|9 years ago|reply
I was the producer and an engineer on Grim Fandango Remastered, a remaster of one of the first games to use Lua (2.5, later 3.0 alpha). And the lead engine programmer on the original game in 1998, Bret Mogilefsky, added coroutines to the Lua language in the game engine before they were officially added as an official language feature. They are very important for scripting adventure gameplay in an intuitive way for non-engineers and helped the original team ship Grim Fandango in 1998. It was similar to what LucasArts had done with SCUMM, their adventure game domain specific language, previously.

To reference SomeCallMeTim's comment - the way the save game system worked in Grim Fandango was to serialize the entire Lua environment! Stack, all code, etc! And too jamesu - when you patch the game you patch the code that was serialized into the save game! The story behind all of this in Grim is pretty interesting.

If anyone is interested in more details please come to the Game Developers Conference this year as I'm giving a talk on the technical details of how the original game and the remaster works. Oliver Franzke and I will cover both the Grim Fandango and Day of the Tentacle remasters.

[+] veli_joza|9 years ago|reply
I'd love to hear all the gritty details about game internals, but I cannot attend GDC (wrong continent). Please submit the talk to HN once it's available.
[+] SomeCallMeTim|9 years ago|reply
I've done this kind of thing in Lua before, but what troubles me about it is the fact that a lot of the state of your game ends up encoded in the Lua stack.

What happens when the user wants to save? There's Pluto [1], which is somewhat slow for complex state. And it doesn't work on LuaJIT code -- and I tend to like to run games on LuaJIT.

And it's opaque: If you're debugging some kind of interaction in the game, you can't just look at the save state to see what's where.

I used to be a big fan of Lua, and this kind of AI and behavior coding was a component of that. But ... I think that, on balance, actually creating a data format that represents the state and "coding" using that plus state machines is actually a more robust and useful way to accomplish the same thing.

[1] http://lua-users.org/wiki/PlutoLibrary

[+] jamesu|9 years ago|reply
An additional problem is what happens when you need to patch the game? If you need to modify some form of quest or dialogue script written as a coroutine, how do you migrate the state? What if you've changed the script 5 times in a series of patches, and the user is still on version X?

Then again you still have this problem even if you power your quest/dialog with data instead.

Without sufficient information it's practically impossible to make a smooth migration.

[+] leggomylibro|9 years ago|reply
That is definitely an issue with this sort of thing; I think that you'd need to organize your conversations in a tree structure, and have 'save/load' methods for each node that could transcribe between a serialized data format like json or yaml, and functions in lua.

With a format like that, you have the option of writing your dialogues in code or plain text, and easily switching between the two. The only major downside is that you can't easily define arbitrary behavior that way; every unique action that a dialogue event could trigger would need to be transcribed in your 'save/load' methods, which may feel limiting if you have a lot of hard-to-generalize logic.

[+] buzzybee|9 years ago|reply
On the other hand, it's certainly more engineering to have to build a custom format. I implemented a story engine that compiles to behavior trees, and then subsequently to a small instruction set in this past year and, well, we're talking about three months to go from having no technology to having a pipeline capable of complex scenarios. But I came out of it with a much more competent strategy for addressing assets than anything I've done in the past, so I'm not complaining. It is the core technology of the game(choice driven, text simulation) so it needed a big investment. And the game is going to have content updates, which makes save games terrifying; In the end, I designed it so that it has episodes and restarting the game starts it from that episode so that I don't have to worry about massive amounts of persistent state getting permanently corrupted.
[+] gravypod|9 years ago|reply
You could do this in C with something like libdill as they give you a handle to a coroutine. All you need to do then is create a state_t that holds the coroutine reference managing the object, the channels for it, and all of the state needed for the computation. During saving you prune dead and save only the computation state. During loading you recreate all of your Objects.

It wouldn't be all that slow and I think it would get over most of the overhead in some implementations I've seen of UI/Interaction code for games.

[+] stevekemp|9 years ago|reply
I've certainly hit situations in the past where the Lua stack became too large, and caused all kinds of problems in debugging.

But despite that I think coroutines have their place, and this was a nice example that I found when I was looking for inspiration.

(I've been working on a Lua-scripted mail client for the past couple of years, and I'm starting to think more seriously about async-behaviour.)

[+] pcwalton|9 years ago|reply
> I think that, on balance, actually creating a data format that represents the state and "coding" using that plus state machines is actually a more robust and useful way to accomplish the same thing.

Manually coding up a "state machine" is kind of reinventing the Lua interpreter, no?

[+] dividuum|9 years ago|reply
For simple dialogs that don't have to be saved (see the other discussions), this seems like an easy way to quickly put any format of dialog system together without reinventing your own DSL to encode those dialogs as state machine(s). You can easily compose dialogs just by calling functions and thanks to Luas tail call optimization you could even jump between dialogs without the fear of running out of stack space.

I'm not saying it's the best solution but it's pretty simple and probably easy to understand. I've used a similar approach way back in a networked multiplayer game where players could telnet into the game server and use text based menus to control the game. You can see the relevant Lua code here: https://github.com/dividuum/infon/blob/master/server.lua. I don't want to imagine how this code would look like without the use of coroutines.

[+] managun|9 years ago|reply
That's interesting. One of my pet projects is a compiler for Bethesda games quests - having source in text/plain file(s), that's the motivation - and as I am designing the underlying language, maybe I should consider coroutines as well...
[+] optionalparens|9 years ago|reply
I can see how it might be fun to do something like this for a blog post or simple project, but in a real game this just doesn't work for many reasons, most listed here already.

There are tons of ways of doing dialog system. Mostly it comes down to your specific game and I highly recommend against generic dialog systems. Rather, the most you can do at a generic level is try to come up with a good way to store dialog and retrieve it in the ways you need. This isn't the best thing I've seen or worked with, but here's an easy to understand and documented method and presentation about it form Valve:

http://www.gdcvault.com/play/1015317/AI-driven-Dynamic-Dialo...

Anyway, I've been programming games and game engines for several decades, and coroutines are always in the list of things that you can throw in the junk pile along with other cool things like continuations, various theoretical data structures, fancy dispatch mechanisms, events/signals, application-wide functional purity, etc. Of course all of these things are useful, but they tend to have problems that make them of limited use in professional game development.

Here's a brief checklist of show-stoppers off the top of my head I tend to go through before introducing anything "creative" into a game, and especially a game engine. Most of the aforementioned items fail one or more of these.

- Performs awful in common cases or when applied to actual game-like conditions vs. theoretical

- Murders cache lines

- Hard or impossible to debug

- Hard to save, load, serialize, deserialize, or snapshot

- Doesn't work over networks

- Doesn't play nice with libraries or data coming from other libraries or languages (ex: 3rd party physics lib)

- Unpredictable execution start or end times

- Non-determinant memory usage or allocation patterns

- Risk of stack-overflows

- Non-portable or problems on specific target architectures

- Requires "religion" meaning it infects all your code or forces you to write all of your code a certain way, everywhere

- Fragments memory

- Long blocking or uninterruptable processing

Conversely, besides the obvious opposites of most of the above, I look for the following when introducing some major data structure or conceptual item like coroutines to a game or game engine:

- Leads to predictable, consistent code and resource usage

- Data-driven

- Editor friendly (as a corollary to the above)

- Clear debugging story

- Both simple and easy if possible. No, they are not the same.

- Plays well with the world around it

- Portable

- Fast

- Loved by the CPU, GPU, and/or compiler, and produces reasonable assembly on target hardware

- Easy for someone else to jump in and understand the flow and how it works, assuming they are not a moron.

Pretty much these two lists mean that games tend to be made of meat and potatoes things. Simple data structures like arrays, custom allocators, predictable branching and execution patterns, up-front memory allocation, and CPU and GPU loving goodness. Pretty much anything stashing things away and building up massive states or jumping around all over the place, pointer chasing, and so-on should always be a no-go.

Anyway, if you're building a truly simple game, as in a 2 day sort of affair, do whatever you want. If you want to build something even remotely complex that you will work on for awhile, it's best to do things the right way which means detach yourself from everything you think you know about most of computer science and think in terms of pipelines, CPU instructions, caches, and predictability among other things. This often means programming against the grain of your language a bit, throwing out sugar, std libraries, and all kinds of things. It really sucks, but that's the essence of good game programming. I hope one day it will be different, but as long as there's still need to push gameplay and graphical boundaries, professional developers almost always need to squeeze out what they can.

[+] davexunit|9 years ago|reply
>Anyway, I've been programming games and game engines for several decades, and coroutines are always in the list of things that you can throw in the junk pile along with other cool things like continuations, various theoretical data structures, fancy dispatch mechanisms, events/signals, application-wide functional purity, etc.

This is one of the worst outlooks on programming I've ever seen. Did you know that any control flow operator such as try/catch is really an application of continuations?

[+] je42|9 years ago|reply
please keep in mind. this is a dialog system.

- murders cache lines is clearly not important for a dialog system.

the use of lua fixes a lot of things you mention in terms of stability, ease of use by other people, etc.

data driven is the most interesting one.

using lua to drive game logic allows you to be less data driven. especially if the lua code is like in this article -- use of very high level lua functions.

the code now looks more like DSL which is more powerful/flexible then using data driven approaches.

[+] Pica_soO|9 years ago|reply
Im wondering, could one auto-convert JavaScript- librarys to lua-code?
[+] veli_joza|9 years ago|reply
The trend is to compile anything under the sun into JS, not the other way around. Although they are quite similar under the hood, Lua programmers take pride in Lua's elegant and minimalistic design, along with JIT speed. JS warts would not be very welcome.

That said, there was a project called lua-colony that attempted the conversion.

[+] etiene|9 years ago|reply
There are some JS to Lua transpilers around, but I'm not sure how robust they are. I want to try some of them later to try to convert some handy JS libraries and feed them into LuaRocks.