Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
115 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Introduction to GameMonkey Script Part 2
Embedding GameMonkey


Creating a simple type

Many game developers will want to expose their own types to a scripting system, and GameMonkey Script is no exception. Imagine a simple class that is in most 3d applications, the Vector class. This can be as simple as a structure composed of 3 floats for x, y and z or can be as complex as a class which holds member functions to perform operations and calculations on the vector data. Sometimes it is possible to use the integral table object for custom-bound objects, but in some situations you require the application to have more control over the object - perhaps by providing extra validation, data conversion or updating another object in your game when the data is changed on the object. With a simple table this sort of control is impossible, hence the need for user-defined types.

Choosing what to bind

When choosing to bind your type you must consider how it will be used in script. For example, the simple Vector type could be easily represented as a fixed array of 3 floats, it could have the constituent items stored in named members (such as x, y and z), or it could be comprised of both. For example:

	v = Vector( 10, 20, 50 );	// Create vector

	// Vector could be represented using 3 floats
	v[0] = 20;
	v[2] = v[1];

	// Access vector using named members
	v.X = 20;				// Set X member to 20
	v.Z = v.Y;

You could choose to provide the exact same interface as your engine's vector class, you could simplify it somewhat, or you could even provide an entirely different interface. For example, some people may wish to access vector maths functions using the dot operator, whereas others may wish to keep the vector type as data-only and provide specialized functions to manipulate the data.

For example:

v = Vector( 10, 20, 50 );	// Create vector

v.Normalise();			// Call normalise using dot operator
NormaliseVector( v );		// Normalise using a specialist function

Many game developers seem to think they need to expose their entire C++ class to the scripting environment. While this is possible, it is often unnecessary; there could be many member functions that are useless in a scripting context so it makes little sense to include them. Even more so, there may be member functions and data that you do not want your script to be able to access, so exposing the full C++ class would be undesirable. Unfortunately, the decisions about what to bind are entirely context dependant and will change for every game project, game system or even game class you wish to bind to a scripting language. In my personal experience, I have found it useful to provide a simplified and refactored interface to the scripting environment. The more complicated it is to use the scripted interface the more likely it is to confuse people and create problems for them whilst using it. If the interface is simple, you can also spend less time documenting it and writing code to debug it.

Binding a simple Vector type

The discussion around type binding will continue by expanding the Vector example examined previously. I will outline a simple 3d vector class and create a type binding for the GameMonkey Script environment.

Imagine a typical game engine's vector type (simplified):

class Vector
{
public:
    Vector() : x(0), y(0), z(0) { }
    Vector( float a_x, float a_y, float a_z ) : x(a_x), y(a_y), z(a_z) { }

    float x, y, z;
    
};

There are three constructors; one default constructor, one taking the values to initialise the vector with and a copy constructor, which is implicitly created by the C++ compiler. The first thing to do to bind this vector type to GM Script is to specify the basic type library. The 'library' is nothing more than a global function declared within the machine global context and an instruction to the gmMachine to register this function with a specified type name. This global function effectively becomes the type's constructor and is the ideal place to begin creating our bound type.

The constructor entry point is specified in a gmFunctionEntry table as such:

namespace gmVector
{

// Declare a type ID
gmType   Type   = GM_NULL;

int GM_CDECL libentry( gmThread *a_thread )
{
    Vector *p = new Vector();
    a_thread->PushNewUser( p, Type );
    return GM_EXCEPTION;
}
	
gmFunctionEntry lib[] =
{
	{ "Vector", libentry }		// type name, entry point
};

}; // end of namespace gmVector

Example: vector_1.cpp

You will notice the global declaration of a gmType variable named Type; this is to hold the GM type ID once the type is registered with the gmMachine. You are free to name the constructor whatever you want, but for organisational reasons I have named it libentry and placed it within the gmVector namespace. The libentry function is where the first step of the binding is done; we create an instance of the native class object and return it to GM by pushing it onto the thread as a new gmUserObject but with the type ID stored within our library.

Now that the initial structure of the library is laid out we can register it with the gmMachine. Initialisation is done with 2 simple function calls:

namespace gmVector
{

void BindLib( gmMachine *a_machine )
{
    // Register one function (the entry point)
    a_machine->RegisterLibrary( lib, 1 );
    Type = a_machine->CreateUserType( lib[0].m_name );
}

}; // end of namespace gmVector

Example: vector_1.cpp

Just like function binding, you need to register the library with the machine. After doing so you create your type and store it away for use in the rest of the library. The CreateUserType member function simply takes the name of a type to register; in this case I've retrieved it directly from the library constructor's name as you should ensure that it corresponds with the function you just registered as the constructor.





Constructor with Parameters


Contents
  Basic Embedding Concepts
  Executing a String as a Script
  Executing a Script from File
  More on Script Execution
  gmVariable Object
  Calling a Scripted Function
  Creating a host-bound function
  Creating a simple type
  Constructor with Parameters
  Operator Overrides
  SetDot Operator
  Garbage Collection

  Source code
  Printable version
  Discuss this article

The Series
  Language Introduction
  Embedding GameMonkey