A major breakthrough

I had a revelation the other day. There is something that has eluded me for a very long time, which is how to cleanly script a "scene" in a map. For example, lets assume we need a sequence of "events" to happen when the player's character sprite walks onto a certain trigger zone inside a cave map.

  1. Play a sound
  2. Pan the camera to a different area of the cave
  3. Open a sealed door in a cave and play another sound
  4. Pan the camera back to the player, now visible with the other character sprites in the party
  5. Initiate a conversation between characters
  6. Have a monster sprite appear and attack the party
  7. After the battle, show another short dialogue
  8. Fade the screen out briefly and then back in, and let the player resume control

Now technically we can do this all right now, but it would be a mess and is not a feasible approach for the long-term. For...I'd say the last two years this problem has been on the back of my mind and I never came up with a good solution thinking about it idly. Last week, I directly approached this problem for the first time. I came up with a solution that frankly seems almost too good to be true. Its simple, flexible, and does everything we'll need. Before I explain how it works, let me give a little background.

Obviously to do all of these things, we'll need to do it inside the map script (in Lua). We can't hard code all of our maps in C++ for obvious reasons. The way we've been doing our scripting up until now is to enable C++ to call Lua functions. But we can't call a single Lua function to do all of the things I listed in that example scene above, because we require a lot of game loops to occur so that the player can see and experience all of these events playing out in real time (instead of just seeing the end result of all these actions being done). If we called the same Lua function over and over every loop, we'd have to keep track of where we are in the function, and also make sure that we don't do "too much" in a single function call that would produce a lag in the game or advance the progress of events too far.

The technology I created to resolve this problem is called the event system. At its core are two classes: MapEvent and EventSupervisor. MapEvent is an abstract class that contains two pure virtual functions: Start and Update. When an event begins, its Start function is called only once. The event's Update function is called every game loop until it returns a true value, indicating that the event is finished. MapEvent objects contain a vector of event "links". Each link consists of an event ID, a start/finish boolean, and an interger time value. What these three pieces of data allow us to do is to spawn child events any time after the parent event starts or finishes. So using these links, from a single starting event we can produce a cascading of further events, complete with accurate timing. Take a look at the image below, showing a directed acyclic graph of events. Any line with a +#s by it indicates that we wait that number of seconds before starting that event. If an event is located below its parent, the event starts after its parent. If instead it is located to the side of the parent, it starts when the parent starts.

The EventSupervisor class simply manages all of these events. It contains a mapping of all registered events, updates active events, and analyzes event links and starts the child events at the specified time. Recall that the MapEvent class is abstract. To create an event purely in Lua, we can either create a Lua class that derives from it, or use a C++ derived class called ScriptedEvent which does nothing more than call two Lua functions in its Start and Update methods. We can also define a number of "common" events in C++, such as moving a sprite to a destination on the map. This makes it easy to re-use and share these events throughout maps without requiring that each map file re-define their function.

This design is simple, flexible, and elegant. Honestly, I'm surprised at myself that I came up with such a great solution. The system is so powerful that I'm considering using the event system in place of some of our other constructs, namely sprite actions and dialogue actions. Sprite actions are what controls the NPCs on the map, telling them to walk from A to B, etc. Dialogue actions are nothing more than a Lua function that may optionally be called after each line of dialogue, or after a dialogue option is selected. Replacing these systems with events would allow them to take advantage of all of the offerings of events (namely linking and timing).

Truly, I think this is a major breakthrough for the map code. It will allow us to make the maps come to life in our next release. I'm really excited to start using this system in the game. There are still some questions about its implementation that remain to be answered, like event chain looping, simultaneous execution of the same event, etc., but I'm not worried about those cases just yet. Instead of trying to predict what we'll need in the future, we'll work with what we need to do in the present.