TorqueScript

From TDN

Contents


TorqueScript

Link to Commands: TorqueScript Commands

If you need to have the benefits of scripting spelled out for you, there is an article available on the subject: Justifying Scripting

There is also a short article available on the basics of Object Oriented Programming for non-programmers, as well as one on fundamental programming principles.


This chapter is adapted from Ed Maurina's outstanding Essential Guide to TGE. The full version of the EGTGE will be released soon, watch for it! In the mean time, a free preview version is available. Check it out, Ed's work is awesome, and EGTGE should be required reading for any serious Torque developer.

This chapter offers an introduction to TorqueScript. You will not be a TorqueScript master after simply reading this chapter-- that takes time and practice. But you will walk away with a good feel for how TorqueScript is structured and how it works in general. You will be familiar with the set of tools TorqueScript offers to help you make your game. With this knowledge in hand, you will be ready to take off and start learning how to best use the tools TorqueScript gives you to work with. There are many additional documentation and tutorial resources to help you along after you finish this book. Check out the GarageGames Torque tutorials page or the Noobs Guide for more information.

It is assumed in the below that you are familiar with some basic programming concepts. You do not need to be a guru, and you don't need to know the nitty-gritty specifics of a particular language like C++, but you should be familiar with the basics of computer programming to get the most from what is offered here.

About TorqueScript

TorqueScript is a very powerful, flexible language with syntax similar to C++. It provides all of the features listed above, including those on the 'would be nice' list. The following sections will describe all of these features and provide examples to clarify concepts. Additionally, there are scripting references in the appendix.

The Console and Simple Scripts

The next few pages detail specific TorqueScript features. During the explanations, many examples are provided. Example code will be shown in one of two ways:

  • Testing examples directly in the console command line, or
  • Loading a test script file via the console, and executing it there.

So, how do you see the console? Easy. First, start the Torque demo application, then hit the tilde key '~'

  • if the '~' does not work , for instance on linux or because of the keyboard :

- edit the file .../client/scripts/default.bind.cs and search for "tilde",

- change the line GlobalActionMap.bind(keyboard, "tilde", toggleConsole) to GlobalActionMap.bind(keyboard, "F12", toggleConsole).

  • You can also run the game with -console from command line

TGE Console Image

You'll be seeing a lot of the echo() command in this chapter. We use the echo() command for testing purposes- it prints values out to the console. Echo() has the following syntax:

    echo(string0 [, string1 [,...,[string n]]]);

Throughout this chapter, statements which appear in a box like the following show lines of actual TorqueScript code. Single-line statements can be copied and pasted (CTRL + V) directly into the console. Try these:


    echo("Torque Rocks");
    echo (1+1); 


If you enter this code into the console and hit enter, you'll see it print "Torque Rocks" and "2". Anything placed inside the parenthesis of the echo() command gets echoed back to in the console. Makes sense, ay? Again, this command is used often during the explanations below.

The TorqueScript Language

Language Features

  • Type-insensitive- In TorqueScript, variable types are converted as necessary and can be used interchangeably. TorqueScript provides several basic literal types, which are described later in this chapter.

                   if("1.2" == 1.2)
                   { 
                      echo("Same- TorqueScript is type-insensitive"); 
                   } 
                   else 
                   { 
                      echo("different- what!?");
                   }


The code above will echo "Same- TorqueScript is type-insensitive."

  • Case-insensitive- TorqueScript ignores case when interpreting variable and function names.

                   $a = "An example";
                   echo($a);
                   echo($A);

This code will echo "An example" twice.

  • Statement termination- Statements are terminated with a semi-colon (;) as in many modern programming languages (C++, Java, Javascript, etc).

                   $a = "This is a statement";

If you do not include the semi-colon at the end of a TorqueScript statement, an error will be echoed to the console.

Note: When typing commands into the console, a semi-colon will automatically be appended if not present.

  • Full complement of operators- The complete list of TorqueScript's operators is given in the appendix. TorqueScript provides all the basic operators common to most programming languages, along with a few more advanced operators.
  • Full complement of control structures- As with any robust language, TorqueScript provides the standard programming constructs: if-then-else, for, while, and switch.

                   for($a=0; $a < 5; $a++) 
                   {
                      echo($a); 
                   }

  • Functions- TorqueScript provides the ability to create functions with the optional ability to return values. Parameters are passed by-value and by-reference. (See the full section on functions below for detailed description and examples.)
  • Provides Inheritance and Polymorphism- TorqueScript allows you to inherit from engine objects and to subsequently extend or override object methods. (See below for detailed description and examples.)
  • Provides Namespaces- Like C++, TorqueScript supports the concept of namespaces. Namespaces are used to localize names and identifiers to avoid collisions. Huh? This means, for example, that you can have two different functions named doIt() that exist in two separate namespaces, but which are used in the same code. (See the full section on Namespaces below for detailed description and examples.)
  • Provides on-demand loading and unloading of functions- TorqueScript supports a very cool feature, which allows you to load and unload functions as needed. (See the full section on Packages below for detailed description and examples.)
  • Compiles and executes PCODE- As a bit of icing on the cake, the TorqueScript engine compiles scripts prior to executing them, giving a speed increase as well as providing a point at which errors in scripts can be reasonably found and diagnosed.

With this overview of TorqueScript's features, we can begin taking a detailed look at how TorqueScript works. We'll start by examining how TorqueScript handles the basics- variables, operators, and control statements. With these topics covered, we'll move on to cover in detail the more advanced features of TorqueScript.

Variables

Variables come in two flavors in TorqueScript: local and global. Local variables are transient, meaning they are destroyed automatically when they go out of scope. And what is scope? "Scope" is a term used to refer to the block of code a variable is defined in. For example, if we have a function, and we declare a local variable inside of that function, the local variable will be destroyed as soon as the function is done processing. When this happens, we say the variable has "gone out of scope". So, local variables only exist in their local scope- the function or code-block they are defined in. A piece of code inside a different function would not be able to see the local variable. Global variables, on the other hand, are permanent and exist throughout the entire program they are defined in.

TorqueScript specifically marks local and global variables with special characters, so that they are easy to tell apart. The syntax is as follows:

           %local_var = value;
           $global_var = value2;


In TorqueScript, variables do not need to be created before you use them. If a piece of code attempts to evaluate the value of a variable that was not previously created, TorqueScript will create the variable automatically.

           for(%a = 0; %a < 5; %a++) 
           { 
              echo(%a); 
           }
           
           echo(%a);


Let's take a closer look at what this code does:

1)On its first pass through the loop, the above code creates a new variable named %a. It must do so because %a has not yet been created when the loop tries to use it for the first time (i.e. %a=0).

2)The echo() command inside the loop will print the value contained in the variable %a five times, echoing the values 0, 1, 2, 3, and 4, as the loop iterates and %a's value increases.

3)After the loop finishes, %a will be echoed once again, by the line after the loop.

That is a basic description of how local and global variables work in TorqueScript. Now, say we want to create a new variable- we know what will happen if we make it local or global, but what names will TorqueScript allow us to use for our variables?

Variable names may contain any number of alpha-numeric (a..z, A..Z, 0..9) characters, as well as the underscore (_) character. However, the first character in a variable's name cannot be a number. You may end variable names with a number, but if you do, you must be especially careful with array names. For further explanation, see the section on arrays.

Lastly, local and global variables can have the same name, but contain different values.

           $a="EGT";
           for(%a = 0; %a < 4; %a++) 
           { 
              echo($a SPC %a); 
           }


This code will echo "EGT 0", "EGT 1", "EGT 2", "EGT 3".

Data Types

TorqueScript implicitly supports several variable data-types: numbers, strings, booleans, and arrays and vectors. Each type is detailed below.

Numbers:
            123     (Integer)
            1.234   (floating point)
            1234e-3 (scientific notation)
            0xc001  (hexadecimal)

Nothing mysterious here. TorqueScript handles your standard numeric types.

Strings:
            "abcd"	(string)
            'abcd'	(tagged string)

Standard strings, in double-quotes, behave as you would expect. Try these examples:

           echo("Hello!");
           echo("1.5" + "0.5");


Strings which appear in single quotes, 'abcd', are treated specially by TorqueScript. These strings are called "tagged strings", and they are special in that they contain string data, but also have a special numeric tag associated with them. Tagged strings are used for sending string data across a network. The value of a tagged string is only sent once, regardless of how many times you actually do the sending. On subsequent sends, only the tag value is sent. Tagged values must be de-tagged when printing.

Try these examples:

           $a="This is a regular string";
           $b='This is a tagged string';
           echo(" Regular string: ", $a);
           echo("  Tagged string: ", $b);
           echo("Detagged string: ", detag('$b'));

Note: You may find it odd that the last line shows a blank. This is because, although we have created the tagged string, it has not been transmitted to us. You can ONLY detag a tagged string that has been passed to you.

String Operators:
            @       (concatenates two strings)
            TAB     (concatenation with tab)
            SPC     (concatenation with space)
            NL      (newline)

To "concatenate" two strings means, simply, to stick them together. For example, if we concatenate the strings "hello " and "there", we end up with a big string reading "hello there".

The basic syntax for these string operators is

            "string 1" op "string 2"

Where op is a string operator. For example:

           echo("Hi" @ "there.");
           echo("Hi" TAB "there.");
           echo("Hi" SPC "there.");
           echo("Hi" NL "there.");

To get a concrete idea of how these operators work, paste the lines above into the console and observe the differences in the output.

There is one last area you need to know about in order to work with strings in TorqueScript: escape sequences

Escape Sequences:
            \n          (newline)
            \r          (carriage return)
            \t          (tab)
            \c0...\c9   (colorize subsequent text)
            \cr         (reset to default color)
            \cp         (push current color on color stack)
            \co         (pop color from color stack)
            \xhh        (two digit hex value ASCII code)
            \"          (quotation mark)
            \\          (backslash)

As in C, TorqueScript allows you to create new-line and tabs using the tried and true backslash character. These are called "escape sequences". Escape sequences are used to indicate to the string processing system that a special character is being read.

Additionally, for data that is printed to the console and GUIs, you can colorize by using '\cn', where n is a value between 0 and 9, representing a pre-defined set of colors.

           echo("\c2ERROR!!!\c0  => oops!");

The code above prints the line "ERROR!!! => oops!" with the first part in red, and the latter part in black. Going into detail about console output colorizing is beyond the scope of this chapter, but a little experimentation will go a long way toward helping you understand how the system works.

Booleans:

Like most programming languages, TorqueScript also supports Booleans. Boolean numbers have only two values- true or false.

            true    (1)
            false   (0)

Again, as in many programming languages the constant "true" evaluates to the number 1 in TorqueScript, and the constant "false" evaluates to the number 0. However, just like C++, non-zero values are also considered true.

Numbers, Strings, and Booleans, those are the basic data-types in many programming languages, and TorqueScript supports them all. Next, we'll look at higher-level variable data-types: Arrays and Vectors.

Arrays:

           $MyArray[n]         (Single-dimension) 
           $MyMultiArray[m,n]  (Multi-dimension)
           $MyMultiArray[m_n]  (Multi-dimension)

TorqueScript is extremely flexible with arrays. Many scripting languages are more flexible with arrays than compiled languages like C++, but TorqueScript is even more flexible than most scripting languages. All this flexibility can get confusing, and deciphering how TorqueScript handles arrays sometimes trips people up.

For example, it is a common misconception that TorqueScript does not support multi-dimensional arrays. This is not true, as the code in the box above shows. The reason many people get confused about multi-dimensional arrays in TorqueScript is that there are multiple-ways to address the array. As you can see, you can separate the dimension indices (m and n, in the above) with commas, or even underscores

Below are two more interesting features of the way TorqueScript handles arrays:

1) $a and $a[0] are separate and distinct variables. It is advisable to use some kind of naming convention so it is clear when you are dealing with an array.

           $a = 5;
           $a[0] = 6;
           echo("$a == ", $a);
           echo("$a[0] == ", $a[0]);

Run this code, and you will see that $a and $a[0] are distinct in the output.

2) $MyArray0 and $MyArray[0] are the same. It may be surprising, but TorqueScript allows you to access array indices without using the common bracket [] syntax. Again, it is advisable to use a naming convention to identify arrays in your code.

           $aryMyArray[0] = "slot 0";
           echo ($aryMyArray0);
           
           $aryMyArray[1] = "slot 1";
           echo ($aryMyArray1);
           
           $aryMyArray[0,0] = "slot 0-0";
           echo ($aryMyArray0_0);

As the output from the above code illustrates, TorqueScript treats bracketed indices the same as appended indices.

Caveat: When passing an argument to a function, be careful if the argument is an array. You cannot successfully pass in, e.g., $aryMyArray as a function argument and reference $aryMyArray[0] within the function body. Instead, you must pass in each of the array elements you will use, thus: myFunction( $aryMyArray[0], $aryMyArray[1] ). This aspect of TorqueScript is easier to understand if you think of the bracket notation as simply a shortcut to forming a variable name with trailing digits: our function call is, in effect, myFunction( $aryMyArray0, $aryMyArray1 ).

Now that we have a basic understanding of arrays, it's time to move on to vectors.

Vectors:
            "1.0 1.0 1.0 1.0"   (4 element vector)

"Vectors" are a helpful data-type which are used throughout Torque. For example, many fields in the World Editor take numeric values in sets of 3 or 4. These are stored as strings and interpreted as "vectors". There are a variety of console operations (read as "available in scripts") for manipulating vectors, for example the console function getWord(). Also, vectors are taken as input for many game methods.

In addition to space-delimited vectors described above, TorqueScript has tab-delimited vectors and newline-delimited vectors as well. See, e.g., console functions getField() and getRecord(). Space-, tab-, and newline-delimited vectors can be formed using the TorqueScript concatenation operators SPC, TAB, and NL, respectively.

Summary:

We now have a good understanding of how variables work in TorqueScript. Remember that even though TorqueScript supports specific kinds of data-types, it will let you over-ride a variable's data-type however you see fit.

Next, we take a quick look at TorqueScript's operators.

Operators

A complete listing of TorqueScript's operators appears in the TorqueScript Appendix [1]. In general, operators in TorqueScript behave very similarly to operators in C-derived languages. However, there are two important caveats when working with TorqueScript's operators:

  • The ++ and -- operators are available only in post-fix form (i.e. ++%a; does not work, only %a++)
  • String comparisons are of the form:
            $=      (string equal-to operator)
            !$=     (string not-equal-to operator)
while the switch statement appears in numeric- and string forms:
            switch  () {}      (numeric switch)
            switch$ () {}      (string switch)

These are the most commonly encountered issues with TorqueScript operators. Again, a complete reference can be found in the Appendix. Next we move on to control statements.

Control Statements

Branching Structures

We now take a look at TorqueScript's control statements' branching and looping structures. TorqueScript supports the familiar branching statements along with an extention specifically for strings:

  • if-then-else The general structure of the if-then-else statement is:
            if(<boolean expression>) 
            {
                statements;
            } else 
            {
                alternative statements;
            }

Things to know:

  • Brackets '{}' are optional for single line statements. (Many programmers find it more helpful to always use brackets.)
  • Compound if-then-else-if-then-... statements are perfectly legal (Many programmers find switch statements easier to read for large blocks of related if-cases).
  • switch The general structure of the switch statement is:
            switch(<numeric expression>) 
            {
            case value0:
                statements;
            case value1:
                statements;
            ...
            case valueN:
                statements;
            default:
                statements;
            }

Things to know:

  • switch only (correctly) evaluates numerics. There is a separate statement, 'switch$', for strings.
  • The break statement does not apply to switch. TorqueScript executes only matching cases and automatically breaks at the next case: statement.
  • In TorqueScript, switch statements are no faster than if-then-else statements.
  • switch$ The general structure of the switch$ statement is:
            switch$ (<string expression>) 
            {
            case "string value 0":
                statements;
            case "string value 1":
                statements;
            ...
            case "string value N":
                statements;
            default:
                statements;
            }
  • This construct behaves exactly like the 'switch' construct with one important exeception: it is only for strings.
Looping Structures
  • for The general structure of the for-loop is:

           for(expression0; expression1; expression2) 
           {
               statement(s);
           }

For example, the code:

           for(%count = 0; %count < 3; %count++) 
           {
               echo(%count);
           }

outputs
0
1
2

As you can see, this is identical to C++'s for-loop. Note: it is beyond the scope of this chapter to try to teach programming basics such as what a for-loop is. Very beginning programmers should read some good tutorials on programming before attempting to make their way through this chapter.

  • while The general structure of the while-loop is:

           while(expression) 
           {
                statements;
           }

As a concrete example, the code:

           %count = 0;
           
           while (%count < 3) 
           {
               echo(%count);
               %count++;
           }

produces the same as the for-loop above.

In sum, TorqueScript supports the standard set of control statements and handles them very similarly to familiar languages like C++.

In the next section, we continue our detailed examination of TorqueScript's standard features. We'll be looking at how TorqueScript handles functions (surprise: it's similar to C++, but more flexible).

Functions

Basic functions in TorqueScript are defined as follows:

           function func_name([arg0],...,[argn]) 
           {
               statements;
               [return val;]
           }

Here is an example:

           function echoRepeat (%echoString, %repeatCount) 
           {
               for (%count = 0; %count < %repeatCount; %count++)
               {
                   echo(%echoString);
               }
           }
           echoRepeat("hello!", 5);

The code above will echo the string "hello!" five times to the console.

TorqueScript functions can take any number of arguments, each separated by commas. Functions may return a value by using the return statement, just as in C++.

Things to know:

  • If you define a function and give it the same name as a previously defined function, TorqueScript will completely override the old function. Even if you define the new function with a different number of parameters, if its name is exactly the same as another function, the older function will be overridden. This is important to note: TorqueScript does not support function polymorphism in the same way C++ does. However, TorqueScript provides packages (see the full section on packages below), which can get around this problem.
  • If you call a function and pass fewer parameters than the function's definition specifies, the un-passed parameters will be given an empty string as their default value.
  • TorqueScript supports recursion, and it behaves just as in C++. The following example is a re-write of the echoRepeat() function we used above, but this version uses recursion instead of a for-loop:

           function echoRepeat (%echoString, %repeatCount) 
           {
               if (%repeatCount > 0) 
               {
                   echo(%echoString);
                   echoRepeat(%echoString, %repeatCount--);
               }
           }
           echoRepeat("hello!", 5);

Objects

Having covered the basics of the language, it's time to examine some of TorqueScript's more interesting details.

In Torque, every item in the game world is an object, and all game world objects can be accessed via script. For example, Player, WheeledVehicle, Item, etc are all accessible via script, though they are defined in C++.

Objects are created in TorqueScript using the following syntax:

           // In TorqueScript
           %var = new ObjectType(Name : CopySource, arg0, ..., argn) 
           {
           
               <datablock = DatablockIdentifier;>
               
               [existing_field0 = InitialValue0;]
               ...
               [existing_fieldM = InitialValueM;]
               
               [dynamic_field0 = InitialValue0;]
               ...
               [dynamic_fieldN = InitialValueN;]
           };

This syntax is simpler than it looks. Let's break it down:

  • %var- Is the variable where the object's handle will be stored.
  • new- Is a key word telling the engine to create an instance of the following ObjectType.
  • ObjectType- Is any class declared in the engine or in script that has been derived from SimObject or a subclass of SimObject. SimObject-derived objects are what we were calling "game world objects" above.
  • Name (optional)- Is any expression evaluating to a string, which will be used as the object's name.
  • CopySource (optional)- The name of an object which is previously defined somewhere in script. Existing field values will be copied from CopySource to the new object being created. Any dynamic fields defined in CopySource will also be defined in the new object, and their values will be copied. Note: If CopySource is of a different ObjectType than the object being created, only CopySource's dynamic fields will be copied.
  • arg0, ..., argn (optional)- Is a comma separated list of arguments to the class constructor (if it takes any).
  • datablock- Many objects (those derived from GameBase, or children of GameBase) require datablocks to initialize specific attributes of the new object. Datablocks are discussed below.
  • existing_fieldM- In addition to initializing values with a datablock, you may also initialize existing class members (fields) here. Note: In order to modify a member of a C++-defined class, the member must be exposed to the Console. This concept is discussed in detail later.
  • dynamic_fieldN- Lastly, you may create new fields (which will exist only in Script) for your new object. These will show up as dynamic fields in the World Editor Inspector.

Due to the way Torque parses script files, the ObjectType must be known at compile time. In order to create types that are only known post compile, use the eval() function. For example, this will not compile:

           new %myObjectType(SomeObjectName);

Place this in an eval() function to force Torque to evaluate it properly:

           eval("new %myObjectType(SomeObjectName);");

Let's create one object that doesn't use a datablock and one that does:

           // create a SimObject w/o modifying any fields
           $example_object = new SimObject();
           
           // create a SimObject w/ dynamic fields
           $example_object = new SimObject() 
           {
               a_new_field = "Hello world!";
           };
           
           // create a StaticShape using a datablock
           datablock StaticShapeData(MyFirstDataBlock) 
           {   
               shapeFile = "~/data/shapes/player/player.dts";   
               junkvar = "helloworld";
           };
           
           new StaticShape() 
           {
               dataBlock = "MyFirstDataBlock";
               position = "0.0 0.0 0.0";
               rotation = "1 0 0 0";
               scale = "1 1 1";
           };

Handles and Names

Every object in the game is identified and tracked by two parameters:

  • Handle- Every object is assigned a unique numeric ID upon creation. This is generally referred to as the object's handle.
  • Name- Additionally, all objects may have a name.

In most cases, Handles and names may be used interchangeably to refer to the same object, but a word of caution is in order. Handles are always unique, whereas multiple objects may have the same name. If you have multiple objects with the same name, referencing that name will find one and only one of the objects.

Fields and Commands

TorqueScript object Fields and Commands are the equivalents of C++ object Members and Methods. Objects instantiated via script may have data members (referred to as Fields) and functional Methods (referred to as Commands). In order to access an object's Fields or Commands, one uses the standard dot notation, as in C++.

           //NOTE: none of the code here will actually run in the console, because 
           //      we are using names and handles which do not actually exist.
           //      This code just illustrates some concepts.
           
           // Directly access via handle
           123.field_name = value;
           123.command_name();
           
           // Directly access via name
           AName.field_name = value;
           AName.command_name();
           
           // Indirectly access via a variable
           // containing either a name or a handle
           %AVar.field_name = value;
           %AVar.command_name();

To get a picture of how this works for real do this:

  1. Start the SDK (you can just run the Torque demo, if you like)
  2. Run one of the Missions
  3. Start the World Editor Inspector (press F11)
  4. Switch to camera view (press Alt+C) and select the character (hold down the right mouse-button to look around, drag the mouse down until the character comes into view. Release the right mouse-button and left-click on the character.)
  5. Give the character a name, such as 'myGuy' (type "myGuy", without quotes, in the textbox next to the "Apply" button, and then click the Apply button)
  6. Open the Console (press the ~ key)
  7. Enter the following lines of code:

           $player_name = "myGuy";
           $player_id = $player_name.getID();
           
           echo($player_name.position);
           echo($player_name.getID());
           echo(myGuy.getid());

Dynamic Fields

In addition to normal fields, which are common between all instances of an object type, TorqueScript allows you to create dynamic fields. Dynamic fields are associated with a single instance of an object and can be added and removed at will. Adding a dynamic field in TorqueScript is automatic. If you try to read an object field and the field is not found, TorqueScript will simply return an empty string, and no dynamic field will be created. However, if you try to write to an object field which doesn't exist, TorqueScript will automatically create a matching dynamic field for the object, and assign it the value you are trying to.

           // new_var will not be created because we are only
           // 'reading' it
           echo($player_id.new_var); 
           
           // new_var2 will be created and initialized to "Hello"
           $player_id.new_var2 = "Hello";
           echo($player_id.new_var2); 

Console Methods

In addition to supporting the creation of functions, TorqueScript allows you to create methods that have no associated C++ counterpart:

           function Classname::func_name(%this, [arg0],...,[argn]) 
           {
               statements;
               [return val;]
           }

The syntax breaks down as follows:

  • function- Is a keyword telling TorqueScript we are defining a new function.
  • ClassName::- Is the class type this function is supposed to work with.
  • func_name- Is the name of the function we are creating.
  • %this- Is a variable that will contain the handle of the 'calling object'.
  • ...- Is any number of additional arguments.

At a minimum, Console Methods require that you pass them an object handle. You will often see the first argument named %this. People use this as a hint, but you can name it anything you want. As with Console functions any number of additional arguments can be specified separated by commas. Also, a console method may return an optional value.

Here are some examples:

           function Goober::hi(%this) 
           {
               echo("Goober Hello ", %this);
           }

Assuming our player handle is 1000, if we type:

           1000.hi();

we get,

            <input> (0): Unknown command hi.
            Object (1000) Player->ShapeBase->GameBase
            ->SceneObject->NetObject->SimObject

What has happened is that Torque has searched the entire hierarchy of Player and its parent classes, looking for a function called hi() defined in the context of one of those classes. Not finding one, it prints the above message. To demonstrate that Torque does search the class hierarchy of Player, try this next:

           function NetObject::hi(%this) 
           {
               echo("NetObject Hello ", %this);
           }

typing,

               1000.hi();

we get,

            NetObject Hello 1000

Next, if we define:

           function Player::hi(%this) 
           {
               echo("Player Hello ", %this);
               Parent::hi(%this);
           }

we would type,

           1000.hi();

and get,

            Player Hello 1000
            NetObject Hello 1000

Do you see what happened? Torque found Player:hi() first, but we also wanted to execute the next previous defintion of hi(). To do this we used the 'Parent::' keyword. Of course, not finding a ShapeBase instance, which is Player's literal parent, Torque then searched down the chain till it came to the NetObject version. Voila!

Lastly, we can force Torque to call a specific instance as follows:

           NetObject::hi(1000);

gives us,

            NetObject Hello 1000

and

           ShapeBase::hi(1000);

also gives us,

            NetObject Hello 1000

since there is no ShapeBase instance of hi() defined. Too cool.

Note: Defining subsequent console methods with the same name as prior console methods over-rides the previous definition permanently, unless the re-definition is within a package (see the Packages section).

Packages

Packages provide dynamic function-polymorphism in TorqueScript. In short, a function defined in a package will over-ride the prior definition of a same named function when the package is activated. Packages have the following syntax:

           package package_name
           {
               function function_definition0() 
               {
                   [statements];
               }
               ...
               function function_definitionN() 
               {
                   [statements];
               }
           };

Things to know:

  • The same function can be defined in multiple packages.
  • Only functions can be packaged.
  • Datablocks (see the Datablocks section for more information) cannot be packaged.

Packages can be activated,

           ActivatePackage(package_name);

and deactivated:

           DeactivatePackage(package_name);

The easiest way to get a feel for packages is with an example. The example below is the most detailed we've looked at so far in this guide, but don't worry. It will make perfect sense when we're done.

Copy the code from the box below into a simple text file. You can use your favorite code/text editor (GarageGames recommends JEdit). Save the code as a new file called "package_test.cs" and save it in the "test_scripts" sub-directory of the example directory we created previously *INSERT LINK*.

           //
           // Define an initial function: demo()
           //
           function demo() 
           {
               echo("Demo definition 0");
           }
           
           //
           // Now define three packages, each implementing 
           // a new instance of: demo()
           //
           package DemoPackage1 
           {
                 function demo() 
               {
                       echo("Demo definition 1");
               }
           };
           
           package DemoPackage2 
           {
               function demo() 
               {
                   echo("Demo definition 2");
               }
           };
           
           package DemoPackage3 
           {
               function demo() 
               {
                   echo("Demo definition 3");
                   echo("Prior demo definition was=>");
                   Parent::demo(); 
               }
           };
           
           //
           // Finally, define some tests functions
           //
           function test_packages(%test_num) 
           {
               switch(%test_num) 
                 {
               // Standard usage
               case 0: 
                   echo("----------------------------------------");
                   echo("A packaged function over-rides a prior"); 
                   echo("defintion of the function, but allows");
                   echo("the new definition to be \'popped\' "); 
                   echo("off the stack."); 
                   echo("----------------------------------------");
                   demo();
                   ActivatePackage(DemoPackage1);
                   demo();
                   ActivatePackage(DemoPackage2);
                   demo();
                   DeactivatePackage(DemoPackage2);
                   demo();
                   DeactivatePackage(DemoPackage1);
                   demo();
               // Parents
               case 1: 
                   echo("----------------------------------------");
                   echo("The Parent for a packaged function is"); 
                   echo("always the previously activated ");
                   echo("packged function."); 
                   echo("----------------------------------------");
                   demo();
                   ActivatePackage(DemoPackage1);
                   demo();
                   ActivatePackage(DemoPackage3);
                   demo();
                   DeactivatePackage(DemoPackage3);
                   DeactivatePackage(DemoPackage1);
                   echo("----------------------------------------");
           
                   demo();
                   ActivatePackage(DemoPackage1);
                   demo();
                   ActivatePackage(DemoPackage2);
                   demo();
                   ActivatePackage(DemoPackage3);
                   demo();
                   DeactivatePackage(DemoPackage3);
                   DeactivatePackage(DemoPackage2);
                   DeactivatePackage(DemoPackage1);
               // Stacking oddities
               case 2: 
                   echo("----------------------------------------");
                   echo("Deactivating a \'tween\' package will"); 
                   echo("deactivate all packages \'stacked\' after");
                   echo("it."); 
                   echo("----------------------------------------");
                   demo();
                   ActivatePackage(DemoPackage1);
                   demo();
                   ActivatePackage(DemoPackage2);
                   demo();
                   DeactivatePackage(DemoPackage1);
                   demo();
               }
           }

Once you've done that, load the script you just created by opening up the TGE Console and typing

           exec("test_scripts/package_test.cs");

Now the script has been loaded, so Torque is aware of all of the code in the file. As such, we can start using the functions we defined above to test with.

The standard way to use a package is to define a previously defined function inside the package, activate it as needed, and then deactivate it to go back to the 'default' case for the function. To see this in action, type: test_packages(0);

TorqueScript provides a useful keyword, 'Parent::'. By using the 'Parent::' keyword in a packaged function, we can execute the function that is being over-ridden. To see this in action, type: test_packages(1);

It is important to understand that packages are, essentially, stacked atop each other. So, if you deactivate a package that was activated prior to other packages, you are in effect automatically deactivating all packages that were activated after it. To see this in action, type: test_packages(2);

Things to know:

  • Packages may define new functions. Remember, when you deactivate a package, these functions get removed from the namespace.
  • Parent:: keyword is not recursive, i.e. Parent::Parent::fun() is illegal.
  • Again, deactivating packages activated prior to other more recently activated packages deactivates all subsequently activated packages.

Namespaces

As previously mentioned, namespaces are provided in TorqueScript. The way they work is quite simple. First, all objects belong to a namespace. The namespace they belong to normally defaults to the same name as the class.

           // Player class Namespace
           Player::

Also as previously mentioned, these namespaces provide separation of functionality, such that one may have functions with the same name, but belonging to separate namespaces. To use one of these functions, you must either manually select the appropriate namespace, or in some cases this is done automatically for you.

It is important to understand that the "::" is not magical in any way. In fact, you can create functions with "::" in their name. This doesn't mean they belong to a namespace. If the expression prefixing the "::" is not a valid class/namespace name, in effect, all you have done is create a unique name.

           // Not really namespaces
           function Ver1::doIt() 
           {
               ...
           };
           
           function Ver2::doIt() 
           {
               ...
           };

Now, there is more to namespaces that you need to understand, but before we can address that we need to learn about some other topics. So, we will re-visit namespaces below in the appropriately titled Datablocks, Objects, and Namespaces Revisited section below.

Datablocks

Of all the features in TorqueScript, Datablocks are probably the most confusing. To make things worse, they are central to the creation of most objects, which means you need to understand them relatively early. I will give a summary of datablocks here, but because you need to understand some other more advanced topics prior to really jumping into datablocks I will defer the in-depth review till later (see "Datablocks, Objects, and Namespaces Revisited" below).

The official definition of datablock is:

"Datablocks are special objects that are used to transmit static data from server to client." --engine.overview.txt in the Torque Game Engine SDK.

In the words of Keanu Reeves himself, "Whoaa...". Or perhaps in my own words, "huh?". This definition, although true, didn't really tell me much. Some searching turned up additional definitions:

"A datablock is an object that contains a set of characteristics which describe some other type of object."-- Joel Baxter

Better, but I'm still a little blurry on the purpose and use of datablocks.

"A datablock is a(n) object that can be declared either in C++ engine code, or in script code ... Each declared datablock can then be used as a "template" to create objects..."-- Liquid Creations, Scripting Tutorial #2

OK, now I get it. Datablocks are templates and we use them to create new objects with the attributes specified by the template. Cool. But, how do we do this? For the answer to that question, you'll have to wait. First we need to discuss a few other important topics, and then we will revisit datablocks and give them the thorough coverage that they deserve.

Read more about datablocks...