When the codebase of your game project becomes bigger and bigger, it can be a good choice to integrate a scripting library. There are several benefits in using a scripting language that way, for example, seperating the engine code and the story code properly. But it's also a way to lure casual programmers into your project, since it is easier to create mods without the need to recompile the binary and dealing with stuff like memory management.
You probably want a small, fast programming language to do the job, which does not hog all the memory for its standard library. But don't worry, no need to reinvent the wheel, you even have the choice!
The next chapters will deliver some concepts and ideas.
The basic idea
Imagine, you create an RPG with lots of items. Since it may be necessary to change the values to improve the balance from time to time, it would be very annoying if that would require the programmer to recompile the code. So one want to be able to change such values without recompiling, maybe even at runtime in a hidden in-game editor. To be able to do that, one must store these values as data files, be it some obscure binary format, *.ini files, XML, JSON.
But what, if one want to store the behavior of game entities?
The most simple answer to that problem is a primitive command based language like this:
PlayerStep forward 20 PlayerRotate 30 ...
To speed up execution of code like this, the text should be replaced with numbers at the loading phase:
1 1 20 2 30 ...
If you need conditional branches, you can implement a simple label/jump system, not unlike in assembly language:
LineElevenLabel: PlayerEatCake Check playerStillHungry : LineElevenLabel
The advantage of a system like this is obvious: It's rather fast to implement. For example, it can be parsed using regular expressions only. While it may be a good choice for many cases, for many other it isn't fine grained enough. That's where you'd typically start using a full scripting language.
- As a rule of thumb, if you need more than simple if-X-then-Y branches it might be a good idea to think about scripting.
- In some scenarios a graphical DSL might be more appropriate.
- Don't use a scripting language only to escape a tedious host language, e.g. C.
- If you plan to use scripting only as a possibility for hot-loading, consider writing dynamically loaded native plugins or using SDKs for JVM/CLR instead. Not only will you get better performance but also better tools.
- Create your scripting API as high-level as possible. It will probably be more comfortable and faster. With the exception of custom bot logic you shouldn't need a JIT compiler.
- Embedded interpreters pose a certain risk. Make sure you know what you are doing before exposing an interpreter to scripts coming from the internet.
Coroutines, also known as green threads or micro threads (although the terms don't mean exactly the same), are pieces of code that behave similar to a real thread, but instead of letting the OS do the scheduling, they let the process do the scheduling. This leads to a much reduced overhead and also to much more control over the threads. The reason why they are important in gaming is that they allow the creation of actor based scripting and cutscene scripting without falling back to state machine handling. Simple example (pseudo language):
def cutscene(): bob.walk_to(alice) bob.say("Hi") alice.say("Welcome, follow me") bob.follow(alice) alice.walk_to(exit)
What happens here is that Bob goes to Alice, talks a bit and then follows her. Unlike normal code, the running game is not interrupted, but instead the code just inserts tasks into the engine and then waits for their completion. This allows to program sequences. The implementation would look something like this:
class Actor: def walk_to(target): set_target(target) suspend()
The set_target() call, schedules the next tasks, while the suspend() call interrupts the script and returns control to the engine. Once the task is completed, the engine will resume the script and continue at the exact point where suspend() left. On the engine side things look something like this:
scripts = [Script("cutscene1.script"), Script("background-animation.script")] while(1): for script in scripts: script.run() process_input() update_world() draw()
There is a list with running scripts that gets called each frame. Each frame runs until it encounters suspend() and then gives control back to the engine. The whole stack of the script is preserved and allows the script to continue where it left of. Some script engine allow more tight control and for example give you the ability to limit how much CPU a script might take, so that a script gets automatically interrupted when its over its threshold.
A similar idea are latent functions. Latent functions stop the interpreter and return control to the host until the host continues execution.
Simple examples are:
Interpreters that are suitable for game scripting: Small and embeddable. Note that many of these are developed by one person, therefore support may be lacking.
|Name||Syntax||Native host API language||Typing|
|Game Monkey Script||C-like||C||dynamic|
|Squirrel||C-like||C (written in C++, so a C++ compiler is requried)||dynamic|