Scripting

NOTE: this document is in no way complete. I haven't looked at it for a while. It's probably worthless.

Scripting systems are pretty common in games these days. They reduce development costs by (ideally) providing faster turnaround than if you implemented all of your game object behavior in C++ or whatever. They also create a "safe" execution environment in which bugs (ideally) won't crash the whole game and malicious code (ideally) can't destroy your computer, which makes them a good way to allow modders to extend your game's functionality.


Choosing a language

Choosing a scripting language isn't necessarily easy. There are lots of free language compilers or interpreters out there that are easily integrated with your "native" C++ code. And there's always the possibility of writing your own language, which might let you provide a feature set better targetted to the functionality you want from your scripts.


Scripting basics

Fortunately, the choice of scripting language doesn't really matter. What matters about putting a scripting system in a game is the interface between the scripting system and the game's native code. These interfaces vary from language to language and from implementation to implementation; different Java VMs, for example, might expose different mechanisms by which they can be dropped into your game engine.

To understand how scripting languages can and should interact with game code, it's important to know a few things about how most games work. Most games have what's usually called the "game loop" or "main loop", which in pseudocode looks something like this:


while (!quitting)
{
    ProcessAllUserInput();
    foreach GameObject do
    {
        UpdateGameObjectState();
    }
    RenderScene();
}

This loop is basically executed as quickly as possible, over and over, for the whole time your game is running. If you want your game to run at 60 frames per second, you have to execute one pass through that loop in just under 17 milliseconds. If we pick some random numbers and say that you have a scene which renders in 6 ms, and it takes 1 ms to process your user input, that leaves 10 ms for all your game objects to update themselves. If you have 10 objects active at any time, like you might for a simple platform game, each one gets just 1 ms every frame; if it's more like 100 objects, say for a complex RPG, then each object has only .1 ms to do its thing.


Time and scripting

This tells us a couple of different things. First, it tells us that our scripting system better be fast, which is no surprise; everything in a game has to be fast. In the world of implementations of little languages, there's a continuum of efficiency. The least efficient are languages which are interpreted on-the-fly; fortunately these are few and far between, because they're too damn slow. The most efficient are those which are compiled directly (or JIT-compiled) to machine code. These are plenty efficient, but also much harder to manage in terms of giving them a safe sandbox in which to play, in order to protect your game process and/or machine from malicious or just plain bad code. One pretty common intermediate between those two extremes is the bytecode-compiled language. These are compiled to a "bytecode" representation, which is then interpreted on the fly. There have been quite a few games which have used bytecode interpreters, so I think it's safe to assume that this sort of mechanism works fine for games.

The other thing that this time limitation tells us is that we have two choices about how to let our scripts control our game objects. One is to force the scripts to fit within this timing constraint; each object's script might have an "UpdateState()" method which is called once per frame. Obviously, these scripts have to be pretty limited in what they do; if you've got a character in your game who for some reason has to spout out prime numbers, you better compute them in advance, cuz you're not gonna have time to do it in script in 100ns. This kind of scheme works fine -- lots of games work this way -- but it has its limitations, and I've got bigger aspirations than that.

Let's take a little detour to talk about natural ways to express certain types of ideas in scripts. Suppose you want to use scripting to implement complex non-player character behavior in an RPG. The natural way to describe that kind of behavior is something like:


Routine EvilBob::PerformSecretMission()
{
    if (Player.HasMagicSword)
    {
        HuntDown Player
        Kill Player
    }
}

Obviously, this is not 100ns worth of activity. This is a grand, large-scale, sweeping set of actions. Hunting the player down might involve hitching a ride on a boat and travelling to an outlying island. What happens if EvilBob runs into a monster on his way and gets distracted by chopping the monster up into tiny bits? And, of course, the player is going to have some objecting to EvilBob trying to kill him.

What I'm getting at is that the other way that you can have your scripts interact with your game engine is to hide them from the vicissitudes of your time limitations. Instead, give each object its own execution context and its own state.




Please send any comments or suggestions to dave@sanderman.org.