Event System
This post talks about the event system in Metaplace. At a high level, it is composed of inputs, commands and triggers. Each one of these fills a distinct role in the event system that allows it as a whole to be flexible enough to handle any kind of game. Like everything else in Metaplace, it is driven by MetaMarkup tags.Inputs are the first category of events. These are physical events that happen on the client such as a mouse click or a key press. The Metaplace game client captures these events - whether it is a Flash client embedded in a web page, or a stand-alone executable doesn’t matter, inputs are process the same either way.
Lets start with the example of a simple space shooter game. Here are the basic inputs for our game:
- press space bar to fire
- press the up arrow to begin thrusting
- release the up arrow to stop thrusting
- press the left arrow to rotate counter-clockwise
- press the right arrow to rotate clockwise
- release the left arrow to stop rotating counter-clockwise
- release the right arrow to stop rotating clockwise
This allows us to fly around and shoot bullets. It’s pretty simple, but even this little game requires seven different inputs. In MetaPlace these inputs are declared in a special code block at the top of a script, so any script can define keyboard and mouse inputs. Here what that code would look like:
MakeInput(‘Fire', ‘space’, 'down', 'none', 'fire')
MakeInput('Begin thrust', 'up', 'down', 'none', 'thrust 1')
MakeInput('End Thrust', 'up', 'up', 'none', 'thrust 0')
MakeInput('Start rotating left', 'left', 'down', 'none', 'rotate 1')
MakeInput('Stop rotating left', 'left', 'up', 'none', 'rotate 0')
MakeInput('Start rotating right', 'right', 'down', 'none', 'rotate -1')
MakeInput('Stop rotating right', 'right', 'up', 'none', 'rotate 0')
The arguments to the MakeInput function are:
- a description of the input
- the name of the key
- the type of key event (up or down)
- the key modifiers (shift, control, etc)
- the command text to send to the server
This data in the script gets turned into MetaMarkup tags that are sent to clients when they log in. Inputs are represented by the W_INPUT tag as shown below:
[W_INPUT]|Fire|1|space|down|none|fire
So when the space bar is pressed on the client, the text “fire” is sent to the server as a command. These mapping are handled by the client application automatically. So when you switch worlds within MetaPlace, the client instantly remaps the keyboard to match the way the inputs are setup in the new world.
What is a command you ask? Commands are the next step in the event chain. Commands are functions in scripts that allow the your world to handle input events from clients. Commands are a special kind of event that have more infrastructure behind them than the simpler kind of events – called triggers. Commands are queued on the server to prevent spamming clients, they have access privileges to enable restricted execution, and they have other metadata.
To continue our example, we need three commands to handle the inputs: a fire command, a thrust command, and a rotate command. Note that the number of commands is less than the number of inputs; we have seven inputs. This is possible because the different inputs can invoke the same command with different arguments. The “begin thrust” input sends “thrust 1” and the “end thrust” input sends “thrust 0”. Both of these inputs are handled by the same command, and it processes the argument as a thrusting flag.
Command functions in scripts are prefixed by the letters “cmd_”. This makes them special to the script engine. They cannot be called like regular functions – only invoked by inputs from clients or by explicitly sending a command to an object. Below is the definition of a thrust command:
function cmd_thrust(thrustFlag)
-- handle ship thrusting here
end
Note that the name of this function matches the input sent from the client. There is one more step here that hooks the input and the function together. In the same block of code at the top of the script where inputs where declared, commands are also declared. This declaration looks like:
MakeCommand('thrust', 'Thrust Forward', 'thrustFlag:int’)The final argument defines the name and type of the argument to the command. This allows the engine to pass correctly typed values into command handler functions.
Now, when the user presses the up arrow, their ship will thrust forward! All of this declaration code lives in a single script. This is extremely useful as you can drop a script onto an object and instantly get its configured inputs. This means you can copy a movement system between worlds by cut & pasting a small amount of code.
The final piece of the event system is triggers. Triggers are lower level events that are sent by both user code and by the engine. Triggers have handler functions just like commands, except that they are prefixed by the letters “trg_” instead of “cmd_”. Triggers don’t require any declaration at the top of the script like commands and inputs.
Talking about triggers is difficult without talking about the way scripts work. So to fill in the details a bit, at a high level, game objects have scripts attached to them. These scripts are organized into a priority order. When an object receives a trigger, it is sent to each of the scripts in order. If the script has a handler function for the particular trigger, then that function is invoked. There are advanced features for fallbacks, blocking and changing priorities, but in general, the trigger is invoked for each script on the object.
Here is an example of a trigger invoked by the engine:
function trg_hit(obstacle)
-- object ran into an obstacle
end
This trigger is invoked by the physics system when a collidable object runs into another collidable object. It passes a reference to the obstacle to the trigger. Next is an example of a trigger invoked by user code:
function trg_health_changed(currentHealth, maxHealth)
-- objects health changed
end
This trigger is from a script that implements a health bar user interface widget. It expects that whenever the health of the object changes, the “health_changed” trigger is sent with the new value. This script would also have some other trigger handlers such as trg_attach, trg_reset and trg_die. This combination of triggers is enough to keep the health bar up-to-date while the game is running.
Note that these triggers look pretty generic - they are not specific to this health bar script. You can think of a set of triggers like a behavior interface. If you use a well known set of triggers like trg_die" and "trg_reset", then new scripts can hook into the system very easily.
So to summarize it all, Metaplace uses a data-driven event system that includes dynamically configurable keyboard inputs, commands with built-in security, and triggers for communicating between game objects and the engine. Let’s look at the full example:
function def_commands()
MakeInput(‘Fire', ‘space’, 'down', 'none', 'fire')
MakeInput('Begin thrust', 'up', 'down', 'none', 'thrust 1')
MakeInput('End Thrust', 'up', 'up', 'none', 'thrust 0')
MakeInput('Start rotating left', 'left', 'down', 'none', 'rotate 1')
MakeInput('Stop rotating left', 'left', 'up', 'none', 'rotate 0')
MakeInput('Start rotating right', 'right', 'down', 'none', 'rotate -1')
MakeInput('Stop rotating right', 'right', 'up', 'none', 'rotate 0')
MakeCommand('thrust', 'Thrust Forward', 'thrustFlag:int’)
MakeCommand(‘fire’, ‘Fire Weapon’)
MakeCommand('rotate', 'Rotate Ship', 'direction:int’)
end
function cmd_thrust(thrustFlag)
self.thrust = thrustFlag
end
function cmd_fire()
-- fire weapon code
end
function cmd_rotate(direction)
self.rotation = direction
end
This is the input system for a space shooter game, but it could easily be used in top-down tank game, or even simple action RPG like the old Gauntlet arcade game. Adding it to your own game is as simple as pasting the code into one of your scripts and attaching that script to your player template.
Sean "NinjaInhibitor" Riley
Lead Programmer



In the example of the trg_health_changed, how does the object that has the health know about the health bar to send the health_changed trigger to it?
Lastly, I must say that these systems could cause some debuggin nightmares as there gets to be alot of systems written by different people all operating in the same world...