domingo, 13 de octubre de 2013

On Scripting Engines

Hi all!

These last days I have been reading quite a lot about scripting engines or systems and why they should be included yes or yes in any worthy game engine. When I first heard about this, I have to admit that I had a narrow vision on its possibilities, maybe because I didn't fully understand the idea behind having an embedded scripting engine. Now that I understand it better, I want to share some of my (limited) knowledge with you.

Ok, so first of all: what is a script? According to Wikipedia, scripts are 'programs written for a special run-time environment that can interpret and automate the execution of tasks which could alternatively be executed one-by-one by a human operator'. Well, actually, the truth is that there is a fading boundary between a script and a 'traditional' program. Script languages are usually interpreted, which means that their instructions are executed on the fly by the interpreter, which usually is simply a virtual machine capable of understanding the high-level script instructions and of executing them on the target machine. 

Traditionally, interpreted languages have not been considered a good choice for game programming, given the high performance requirements of this kind of software, which must run at a minimum of around 60 frames per second. However, as interpreted languages evolve and devices acquire more powerful processors, this is no longer completely this way. 


Figure 1. Scripting-enabled game engine: Visual3D Game Engine
Most game engines include scripting as part of their suite. 

Well, it is true that the performance of a C or C++-written game will never (for the moment) equal the performance of the same game written in an interpreted language (or in other higher level languages like Java). But it is indeed possible to, let's say, outsource some functionalities to be implemented by these languages.

Let's go with a simple example. Let's suppose you're doing a side-scrolling game and you want that when the player reaches a certain position in the scenario, a particle system sparks off, e.g., an explosion goes off. Let's assume that you don't know anything about scripts, so you must hard-code this effect in the game logic, which would result in something like this:

void ParticleSystem::start(Position x, Position y) {  
     for (int i = 0; i < N_PARTICLES; i++) {
          particles[i] -> start();
          particles[i] -> setVelocity(...//random velocity for each particle);
          ... //more settings
       }
}

void Particle::start() {
     p = new Particle("particle.png");
}

void Level::update() {
    //update of all the objects
   if (player.position > X_EFFECT_POSITION) {
        ParticleSystem::getInstance() -> start(player.position.x, player.position.y);
   }
}

The code is not difficult to follow. I have to check, in each update of the game for a specific level, if the player has reached the specified position. [NOTE: I simplified the code here; I think that an improved version should use the Observer pattern so that the player object itself notifies the particle system when it reaches the destination]. Then, the particle system initializes all the particles with a given image (particle.png) and sets a random velocity for each particle. So far so good. 

However, when you run your game, you notice that setting the velocity to a random number does not provide a realistic explosion effect. Ok, you change the code, compile again, and run again. Ufff... the bitmap used for the particles is not that cool. Ok, let's create a new bitmap, compile again, and test it. And you can be repeating this process dozens of times until you end up happy with the result. 

Now, let's assume that you implemented a scripting engine in your system. Let's see how the code  in your game would look like (of course, this is no real code, just a simplification, as usual):

void ScriptingEngine::explosionEffect(Position x, Position y) {
    initializeEngine(&sh); //sh stands for scripting handler
    sh.pushParameters(x, y);
    sh.executeFile("explosion.script");
}

void Level::update() {
    //update of all the objects
   if (player.position > X_EFFECT_POSITION) {
        ScriptingEngine::getInstance() -> explosionEffect(player.position.x, player.position.y);
   }
}

And now, we create a .script file that will do exactly what the older code did: 

//explosion.script//
Begin Script 
   player.position.x = popParameter(1);
   player.position.y = popParamenter(2);
   image = loadParticleImage("particle.png");   
   ... //code that creates all the particles with random velocity
End Script

Basically, you communicate the required arguments (pushParameters) from the host language (e.g. C++) to the scripting system. Then, the scripting engine executes the explosion effect implemented in the file.

Now, you compile and execute. Mmmm... you're not happy with the particle bitmap or with the velocity of the particles. Let's change all that. Need to recompile again every time? No! Because the effect is completely loaded in the script, you only have to change the .script file and execute the game again. The game engine has outsourced the explosion effect to a scripting system, and this provides higher flexibility and effectiveness. The explosion effect is totally decoupled from the game logic, so any change in the effect doesn't alter the game logic and vice versa. 

This is a really powerful feature that pushes the development experience forward. The possibilities of this approach are unlimited: faster testing and debugging, easy change of characters behaviour, improved extensibility of the game (capability of adding new features effortless), and much more!. And even better: you could provide a terminal with the game so that you can write script commands while the game is running, and you would see the changes immediately at runtime, without the need for restarting it.

From a design perspective, the biggest challenge is to know which parts of the game are worth this outsourcing. If you read enough about it, you'll get the following answer: delegate to the scripting system any aspect of the game that has no significant performance impact. In my opinion, this can be re-formulated as follows: do not call the script engine once per frame. Anything that needs to be executed once per frame should be implemented in the host language (e.g. C++). But any effect or event that does not occur every frame is a great candidate to be delegated to the scripting engine. The more you export (with unnoticeable performance degradation), the higher the productivity boost you will experience.

Regarding the scripting language to choose, there are lots of them out there, and the decision is likely to depend on the host language. For C++, for example, it's common to see bindings with Python and Lua, given that they are lightweight (specially the latter) and are implemented in C. I'm currently looking at Lua because I know that it's being used currently in many commercial games and the API seems to be simple enough. I'll let you know about my progress soon.

Hope this post was interesting, or at least that you learned something new from it. Don't hesitate to ask or comment anything you want. 

See you!

6 comentarios:

  1. I totally agree with you, being able to change things at run time has a massive impact on how quick you can test and tweak values on your game, specially if you work with designers that don't know anything about programming, however, moving some of the game's logic to a scripting languages also has it cons.. bug finding/fixing becomes "funnier" when a lot of things are happening outside c++. Spending time on adding extra logging or on a good debug system would be a good idea :)

    ResponderEliminar
    Respuestas
    1. Yes... I read about that, but I only know the theory up to now. One question: knowing that my purpose is to build a 2d platform game, with all that it entails (enemies, pick-ups, etc), what kind of things (or at least the first things that come to your mind), in your opinion, should I delegate to the scripting engine?

      Eliminar
    2. That depends a lot on personal tastes hehe, I've met people that love lua and did literally everything there, even the management of player health, which sounds a little bit crazy to me.. :)

      I guess, everything that the player can interact with could be a potential candidate to delegate to lua, basically as you said in the post, avoid calling lua every frame, so basically things that respond to events could be done in lua, like opening a door, picking-up an object, creating some kind of cinematic, like when the player enters in a specific area, spawn some particle effects, trigger a conversation, play some sound effects, etc.

      We used lua a lot for cinematics in Dark Ocean and also for things like the lifts we had in the first level and specific jump scares we had in the levels. I can send you the files if you feel nosy :)

      Eliminar
    3. Thanks again! :) It would be great if I could take a look at the files, just to get a feeling on what it looks like is in a real project.

      Eliminar