Event-Driven Scripted Game Engine

Recent games i played rely a lot on event-driven programming and scripts. This post is about the idea i got from playing games like Fallout, Morrowind and Oblivion about their event-driven systems in a very light perspective but i hope it can still be of some use.

Every game engine relies on several components or sub-systems: graphics engine, controller, ai, sound, database, etc. A complex game will most of the time drive the graphics engine step by step and provide at least a sort of task manager or scheduller that will drive the game in indendent tasks (possibly with their own distinct schedulling times), which are the rendering task, the input pooling task and the logic update task.

This “task” manager should be flexible enough to either work in a single-thread or multiple threads in a transparent way freeing the engine programmer from the burden of having to choose how many threads and how these should be used if the game is going to run in different platforms.

The game logic is responsable for determining what game actors will be updated (AI loding), when to load/unload a scene, when to add/delete actors to the scene, when to activate/deactivate actors, when to suspend/resume the game (doesn’t suspend render or input), when to transform input and output from the game guis and convert basic events to logical events. Input transformation is the part where the game translates primitive input events using the keyboard and mouse bindings table into logical events like jump, run forward, fire, use, heartbeat and stuff like that.

Once the translation process is complete and there are new logical events they are dispatched to the active actors in the game. An event filtering criteria determines which actors receive which events. The player sends events from the use of the keyboard and mouse that are excluvly addressed to the player avatar or what the player controls at a certain moment.

In a simple event dispatching scheme we can consider an event has a piece of info having an event timestamp, event origin and event destination. Event data is exchanged only between the controler and the active actors and never directly bewteen actors. The controler acts as an intermediary for all comunications.

In a typical, non-optimized, game logic cycle:


while(1) {
    receive input
    translate input
    schedulle logical events from logical input 
    analize current scenes
    schedulle logical events from collisions and other scene events
    dispatch events to actors
    actors schedulle their own events requesting some action or do nothing
    change the scene based on requested actions
}

This scheme implies two things. One is that actors don’t modify themselves or the game state directly but instead announce what they intend to do, which is very convinient because that may not be what realy is supposed to happen. Second actors must interpret what event patterns they receive (as well as inspect the scene directly) and update their decision variables acordingly.

This is tipical event-driven programming and it is a completely different programming paradigm from imperative programming. Execution takes place during the ocurrence of certain events and unlike imperative programming we cannot predict with 100% certainty which events will occur and in what order.

So in event-driven programming we have another element that is a pattern of events. Each actor decides what to do next in relation to the patterns he has stored in his local state. When a new event is received its patterns are updated or if no event is received for a long time old patterns may be forgotten.

A pattern may be encoded with something as simple as a finite set of boolean variables for example to represent the state of a gui button, or something as complex as a prolog theory.

The actor reaction maybe a simple state-machine that decides what to do based on the values encoded in its pattern boolean variables, or something that involves AI search on its theory and in this case an actor is called an agent.

The next example is taken from Starflight beta design doc and explains how combat works in this old game. The purpose of this example is to show that little has realy change in game engine design and that the foundations of game engines are still mostly based on actors and event driven programming (simulation part) with reactive agents (AI part) using a central data model to link all game components.

Also see this link containing a document that describes this kind of architecture in detail:
http://www.jeffplummer.com/Writings/Writings.htm

Modern games like Morrowind and Oblivion don’t add much more complexity to the simulation and AI part. Instead the complexity increase happens in other components that have little effect on gameplay. The graphics engine is much improved. The amount of graphical detail and content available to the player has increased a lot. Game engines use external components like tools to create a face and customize the character in many different ways. There are external components to simulate realistic forest and physics. Games are starting to use more than just reactive AI solutions but are still mostly scripted environments which amounts to reactive AI. There is a search involved in path-finding, following and escorting but nothing that uses advanced AI.

Now to start with the example to the describe the behavior of Starflight ships in an encounter scene.

Actors

Scene: Encounter scene in hyperspace (a 2d grid with 2-dim coordinates)
Actors: Player Ship, Alien Ship, Debris
Mute Actors: Missiles, Lasers

Events Sent by Ships to the Game Controller

Fire Missile(s1,s2) - create missile at s1 pos and seek ship s2
Fire Laser(s1,s2) - create laser at s1 position and target s2 (instantaneous)
MoveToXY(x,y) - player ship move to x,y
Move Away(a) - from player ship
Approach(a) - player ship
Move Randomly(a) - around player ship
Evade(a) - maintain distance from player ship
Raise Shields(s) - alien or player ship raises shields
Lower Shield(s) -
Arm Weapons -
Disarm Weapons -
Call More Ships(a) - alien ship calls for more ships to aid him in combat
Scan Ship(s,x,y) - scan location x,y
Pick Cargo(s) - pick all cargo in debris near the ship
Hail(s1,s2,p) - player hails at alien ship or alien ship hails at player in posture p

Note: Aliens do not tell the controler where they want to move, instead they request from the controller a certain movement style and the controller decides to each specific location the alien ship should move. The player ship specificaly issues a move to xy command because the player controls the ship directly.

Note2: Starflight is optimized so that these are actualy direct funtion calls inside the game cycle.

Ship Local Status

boolean CanSurrender - if this alien hail for surrender.
pos Position - (x,y)
float Rotation - angle
float MoveRate - how fast the ship can move
int HitPoints - ship hit points
enum Race - race that owns alien ship.
enum LaserStatus = ARMED|DISARMED|DISABLED
enum MissileStatus = ARMED|DISARMED|DISABLED
enum ShieldStatus = ARMED|DISARMED|DISABLED
enum EngineStatus = ENABLED|DISABLED
enum MovementType

Vars Used by Events

boolean InComm

InComm is et when the game enters comm mode and cleared when exits comm mode.
Cleared when scene starts.

boolean InCombat

Player has selected the Navigator combat option.
Cleared when scene starts.

boolean Terminated

Game has left comm mode.
Cleared when palyer enters comm mode again.
Cleared when scene starts.

boolean Surrender

Player has requested to surrender.
Cleared when scene starts.

boolean Attacking

Attack is set to true when any alien ship fires a weapon at the player.
Attack is reset when no alien ship has fired a weapon for 2 minutes or when communications was entered.
Cleared when scene starts.

boolean NothingHappening

If there was no communication within 2 minutes.
Cleared when scene starts.

boolean PFiredMissile

Player has fired a missile that hasn’t exploded yet.
Cleared when missile explodes or timeouts.
Cleared when scene starts.

boolean AFiredMissile

Alien has fired a missile that hasn’t exploded yet.
Cleared when missile explodes or timeouts.
Cleared when scene starts.

boolean Dead

Ship has been destroyed and become debris.
Cleared when scene starts.

float Damage

Set to the number of hit points damaged when ship is attacked.
Cleared when scene starts.

boolean Call

Reinforcements have been called.
Cleared when reinforcements arrive.
Cleared when scene starts.

boolean Arrived

Reinforcements have arrived.
Cleared when scene starts.

enum Range = SHORT|MED|LONG

If player ship is > 20 cell distance from closest alien ship.
If player ship is 7-20 cell distance (missile range) from closest alien ship.
If player ship is < 7 cell distance (laser range) from closest alien ship.
Default depends on initial scene setup.

class ScanData

Scan data of last scanned ship. This gives info about scanned ships like arm and shield status, hit points and movement type. Cleared when a new scan is started.
Cleared when scene starts.

Events Received by Ships or Player

Note: When a scene is started and event vars get their default values no events are generated.

Theres one event for when each event variable is set or cleared. For example NothingHappeningSet and NothingHappeningClear.

Starflight Logic (beta doc)

Taken from this site:
http://www.starflt.com/starflt.php?ID=SF1Univ


(weapons)

FIRE MISSILES        <-- ?IN-COMBAT TRUE ?P-HAS-MISSILES TRUE ?P-FIRE-MISS-NOW TRUE ?MISSILES-DEAD FALSE
FIRE LASERS          <-- ?IN-COMBAT TRUE ?P-HAS-LASERS TRUE ?P-FIRE-MISS-NOW FALSE ?P-FIRE-LASER-NOW TRUE 
                         ?LASERS-DEAD FALSE ?RANGE-SHORT TRUE
(movement)

APPROACH             <-- ?IN-COMBAT TRUE ?RANGE-LONG TRUE ?A-FIRED-MISSILE FALSE ?ENGINES-DEAD FALSE
APPROACH             <-- ?IN-COMBAT TRUE ?RANGE-MED TRUE ?A-FIRED-MISSILE FALSE ?MOVE-RND-NOW FALSE 
                         ?APPROACHING-NOW TRUE ?ENGINES-DEAD FALSE
MOVE RANDOMLY        <-- ?IN-COMBAT TRUE ?RANGE-LONG FALSE ?A-FIRED-MISSILE FALSE ?MOVE-RND-NOW TRUE 
                         ?ENGINES-DEAD FALSE
EVASIVE              <-- ?IN-COMBAT TRUE ?A-FIRED-MISSILE TRUE ?ENGINES-DEAD FALSE

(if none of these are done then DO NOTHING)

Alien Auxillary Actions

RAISE SHIELDS        <-- ?SHIELDS-UP FALSE ?FRIENDLY FALSE ?SURRENDER FALSE
LOWER SHIELDS        <-- ?SHIELDS-UP TRUE ?FRIENDLY TRUE
LOWER SHIELDS        <-- ?SHIELDS-UP TRUE ?SURRENDER TRUE
ARM WEAPONS          <-- ?WEAPONS-ARMED FALSE ?FRIENDLY FALSE ?NEUTRAL FALSE ?SURRENDER FALSE
DIS-ARM WEAPONS      <-- ?WEAPONS-ARMED TRUE ?HOSTILE FALSE ?OBSEQUIOUS FALSE ?FIGHT FALSE
DIS-ARM WEAPONS      <-- ?WEAPONS-ARMED TRUE ?SURRENDER TRUE
CALL FOR MORE SHIPS  <-- ?CALL TRUE ?CALLED FALSE ?RACE TRUE ?<3SHIPS TRUE ?FRIENDLY FALSE ?NEUTRAL FALSE 
                         ?SURRENDER FALSE 
MORE SHIPS ARRIVE    <-- ?CALLED TRUE ?ARRIVE TRUE
SCAN PLAYER'S SHIP   <-- ?RACE TRUE ?SCAN TRUE ?SCANNED FALSE

Performance has been improved in the last years enormously due to quicker hardware which means we get lower waitingt times in games and nicer graphics. User friendliness has also been improved so that we have more live interfaces that easier hint the user what to do and this has also alot to do with the increased performance since with computers 20 years ago you would not have the CPU power to do it.

However gameplay and AI still has alot of improvements to do. For instance, I seen so many modern games which are almost totally deterministic meaning if you pay $50 then you always succeed and with $49 bribe you always fail. This is what creates those “gamey” games that has no feel. This is bad design and much of it also goes into AI. Deterministic behaviour will only work in a deterministic game (like chess).

To make a real AI with intelligence, you need fussy logics, genetic algoritms and neural networks and if you do not have all those then I think someone has to down the line identify the factors that the AI should count as factors.

A human can come up with factors on their own but the computer of today cannot.

So, therefore when I program I try to make something in between, not deterministic event driven and not self learning but instead it should

  1. indentify the alternatives
  2. create a score for each alternative where score is weighted value of factors plus a random value
  3. Always pick the alternative that got the highest score (since the random value can be set high enough that the actual score is not the best alternative when excluding the random)

Works quite well, and the only disadvantage is that the factors the AI takes into account must be made by a human :slight_smile:

Where does collision detection fit into this event-driven paradigm? Let’s say I have a hero backed against a wall, and three zombies charging towards him, all set to collide in the next frame. What I would expect, from my personal experience being bum-rushed by zombies, is that we would end in a state of equilibrium as long as the wall can absorb the force of the hero’s falling back into it. Which of the actors, then, would be responsible for really handling the collision in this update cycle?

Thats interesting actually. Having actually created an event drive “game system”, I found that collision detection is enormously hard to integrate properly. Your question is slightly in the wrong direction because the equilibrium calculations would fall on the physics engine, not the entity firing event system. But actually firing off events about what entity collided with which and what collisions are important is rather hard.

I ended up coding OR, XOR and AND gates with variables attached to each where you connect them together like puzzles (sort of like a miniture circuit) and made it generate Java code from that…worked quite well actually as an initial framework, you can then carry on fillin in the gaps regarding response.

Anyways, my 2 pence :slight_smile:

DP