Creating a C++ Scripting System - Part 1

Преводни и оригинални статии в областта на разработката на игри.
gemicha
Site Admin
Site Admin
Мнения: 2930
Регистриран: 20 ное 2003 22:20
Местоположение: USA

Creating a C++ Scripting System - Part 1

Мнение от gemicha » 17 фев 2004 23:30

Creating a C++ Scripting System


Автор: Емил Дочевски
Публикувана: Game Developer Magazine


Most of today’s games require some kind of system to allow the game designers to program the story of the game, as well as any functionality that is not general enough to be directly supported by the AI or some other system. Often, the scripting system of a game is also made available to players for customization. Today it is not uncommon for third-party companies to customize an existing game to incorporate completely different gameplay, in part by changing the game scripts.

Depending on the game type, the best results are achieved using different approaches to scripting. In environments that have very well defined rules, such as RTS games, the most important task of the designer is to achieve good balance between the different units and resources, while also producing interesting maps with features that clever players can use to their advantage. Clearly, the scripting support for such games is focused on fast and easy tweaking of the different parameters exposed by the game engine, and is usually directly supported by the map editor.

Other games, for example many FPS titles, require very limited levels of customization. This is usually done by tagging objects in the game editor.

And finally, many games are driven by complex enough logic to require a complete programming language for scripting. In some cases, developers have designed and implemented their own programming languages to serve this need. To cut development time, today most game companies opt to use existing programming languages customized for their own scripting needs.

Scripting Languages

Most so-called scripting languages have one thing in common: they have been developed by a small team or even a single person for the purpose of writing simple programs to solve a particular, limited set of problems. By their very nature they are not universal, yet many end up being employed
beyond their intended purpose.

Sometimes, “scripting language” simply means a programming language used to write scripts. Indeed, people have successfully used languages such as Java for scripting.

When selecting a scripting language for a game, chances are that a developer will not find a language that matches the desired functionality completely. At the very least, a developer will have to design and implement an interface between the game engine and the scripting language. The programming language the developer chooses is just one of the components — often not the most important one — of a scripting engine.

Using C++ for Scripting

One of the most important characteristics of C++ is its diversity. Unlike many other languages that are efficient for a particular programming style, C++ directly supports several different programming techniques. It’s like a bag of tricks that allows many, often very different solutions to a problem. Some of the C++ features — such as function and class templates — are so powerful that even the people who developed and standardized them could not have foreseen the full spectrum of problems they can solve.

The idea of using C++ for scripting may seem strange at first. Indeed, most people associate C++ with pointers and dynamic memory management, which are powerful features but are more complex to work with than what most game designers would consider friendly.

On the other hand, C++ is the natural choice to program the rest of the game in. If the scripts are also written in C++, then integration with the rest of the code is seamless. In addition, C++ is translated to highly optimized machine code. While speed is rarely a problem for most game scripts, faster is always better.

Naturally, using C++ for scripting has some drawbacks. Because it is a compiled (as opposed to interpreted) language, a C++ program can’t be changed on the fly, which is important in some applications. Also, C++ does not provide a standard for plugging in program modules at run time, which makes it nearly impossible to expose the script for customization by users (usually this is not an issue for console titles).

It’s important to create a safe and easy-to-use environment for our scripts. The language used is a secondary concern, because by their very nature scripts are simple and mostly linearly executed, with limited use of if-then-else or switch statements. In this article we will demonstrate how to use some advanced C++ features to create a safe “sandbox” environment for the scripts within our game code.

Creating a Safe Scripting Environment

More often than not, programmers design a class hierarchy to organize the objects that exist in the game’s digital reality. For the purposes of this article, let us assume that our game uses the classes whose partial declaration is given in Listing 1.

Код: Избери всички

// Listing 1

class CRoot {
  virtual ~CRoot();
};

class CActor: public CRoot {
public:
  bool HasWeapon() const;
  bool HasArmor() const;
  int GetHealth() const;
  void SetHealth( int health );
  void Attack();
...
};

class CGrunt: public CActor {
public:
...
};

class CAgent: public CActor {
public:
...
};

Using C++ for scripting allows us to use plain pointers for interfacing with the script code. The main drawback of using pointers is that they can point to invalid memory. The benefits are that pointers are directly supported by the language and are very efficient. Also, pointers make it easy for programmers to implement and later extend the interface between the scripts and the game. To eliminate the possibility for the script code to access invalid memory, the use of pointers must be hidden from the scripts.

In C++, we can do this by organizing the pointers in a set container. Then we can define functions and operations for working with entire sets of (pointers to) objects. This neatly unifies the processing of one or more objects and usually allows the scripts to not have to handle empty sets as a special case.

For example, to represent a set of CGrunt objects (see Listing
1), we can use something like this:

Код: Избери всички

std::set<CGrunt*> grunts;
Let’s also define a functor to expose the CActor::Attack function to the script:

Код: Избери всички

struct Attack {
  void operator()( CActor* pObj ) const 
  {
     pObj->Attack();
  }
};
Now we can use std::for_each with the function object Attack to have all the grunts in the set use their attack functionality:

Код: Избери всички

std::for_each( grunts.begin(), grunts.end(), Attack() );
The for_each template function is defined so that it can work with any sequence of objects, which is a level of flexibility we do not need. Instead, we can define our own version of for_each, which for convenience we can simply call X (from Execute):

Код: Избери всички

template <class Set,class Functor>
void X( const Set& set, Functor f ) 
{
  for( typename Set::const_iterator i=set.begin(); i!=set.end(); ++i )
  f(*i);
}
Now we can simply say:

Код: Избери всички

X( grunts, Attack() );
It’s also possible to write functors that take arguments and pass them to the function they call:

Код: Избери всички

struct SetHealth {
  int health;
  SetHealth( int h ): health(h) {
  }
  void operator()( CActor* pObj ) const {
    pObj->SetHealth(health);
  }
};
Now we can use the SetHealth functor like so:

Код: Избери всички

X( grunts, SetHealth(5) );
Продължава ...

Отговори