Introduction to GameMonkey Script Part 1
Language Introduction
Simulation of 'structs' and simple classes with TablesThe final use of the table structure is to simulate C/C++ structs and classes. If you recall what I mentioned before, the GM Script table object can store any type of data, including functions. Because of this, you can assign a scripted function to an index or key within a table. You should be aware that when declaring a function as a table member you should not put the semi-colon line terminator as you do when declaring a function on its own. myStruct = table(
SayHello = function() { print( "Hello, world!" ); }
);
myStruct.SayHello(); // Call table-bound function
As you see in the example, you can access keyed table data using the period (dot) operator. This allows us to treat the table as a simple class structure, accessing the named elements in a familiar fashion. myAlien = table(
Name = "Alien",
AttackPower = 20,
Strength = 50,
OnAttack = function( entity )
{
entity.Damage( this.AttackPower );
}
);
The slightly more complex example shows how simply a generic alien scripted object can be created using the basic GameMonkey Script types and how it is centred primarily around the use of the table object. Unlike C++ classes, it is important to note that the GM Script table object has no constructor/destructor, cannot be inherited from and does not allow for custom operator overriding. However, you can achieve such behaviour through creating your own bound types (covered in the next instalment of this article). It should also be noted that GM tables have no concept of public, private and protected scoping as C++ presents for structs and classes. All table members are declared as being in the public scope and so can be accessed from anywhere. I will continue the scoping discussion in the next section. ScopingGameMonkey script has a range of scopes for variables (and hence functions). If you wish your functions or methods to be accessible from outside of the script (for example, to be read directly by the host application) you must declare them as being in the global scope. The global scope is accessible everywhere in the script; even within other functions. Without this declaration, the objects are implicitly within local scope, which means they're only accessible to within the current scope or lower. // Create a variable in the global scope global myvar = 100; // parameter 'a_param' is in function local scope myfunc = function( a_param ) { // variable myvar is in local scope myvar = a_param; print( myvar ); }; print( myvar ); myfunc( 50 ); print( myvar );
Hold up a minute; you will notice that I've created 2 variables called myvar, one in the function and the other in global scope. If you run this script you will notice that the value of the global myvar is unchanged, even though you set the value of myvar in the function. The reason for this is simple; they exist in different scopes! GameMonkey allows you to set global variables from within functions by explicitly specifying the scope of the variable. In this case, I add the global keyword to the myvar declaration in myfunc. // Create a variable in the global scope global myvar = 100; // parameter 'a_param' is in function local scope myfunc = function( a_param ) { // Access variable myvar in global scope global myvar = a_param; print( myvar ); }; print( myvar ); myfunc( 50 ); print( myvar );
Things can begin to become tricky, however, when using tables and the this operator. Whenever a variable is part of a table or user-defined object, it exists in the member scope of the parent object, or this. This concept will be familiar to you if you've done any work in C++, so I will not dwell on it. Let's have a look at the member scoping in use: global mytable = table( myMember = 50, setMember = function( a_value ) { myMember = a_value; } ); print( mytable.myMember ); mytable.setMember( 100 ); print( mytable.myMember );
The script above behaves similarly to the local scoping example; the myMember method isn't altered. However, when you include the member scoping keyword you will see a different result. global mytable = table( myMember = 50, setMember = function( a_value ) { member myMember = a_value; } ); print( mytable.myMember ); mytable.setMember( 100 ); print( mytable.myMember );
The this scoping is fairly complicated, but at the same time is very powerful. Using this scoping you can create generic delegates that can access the data of the object that is passed as this. Confused? Take a look at the following example: myTable = table(
myMember = 50
);
setMember = function( a_param )
{
this.myMember = a_param;
};
print( myTable.myMember );
myTable:setMember( 100 );
print( myTable.myMember );
In this example the function setMember is completely external to the myTable object but is able to access its data and methods. The reason it is able to do this is though use of passing the myTable object as this when calling the setMember function. The body of setMember explicitly states that it will alter the data belonging to this without actually belonging to this at compile time. This allows you to create very powerful scripted functions which can exist in the global scope and be called from objects as if they were a member of that object itself. An abbreviation for typing this is to simply type a single period '.'. For a more complex example of this in action, please refer to scoping_6.gm which is included with this article. It should be noted that this scoping is different to member scoping, although you could mistake the two if you're accustomed to C++. This scoping refers to the scope of the object passed as this. Member scoping allows you to specify that a variable is a member of this, making it of use in situations where a global or local member may conflict with your attempts to access a member variable of an object. |