Torque 3D Game Development Cookbook

By David Wyand
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. TorqueScript: The Only Script You Need to Know

About this book

Torque 3D is a popular game engine that supports you in every step along the way to making your game a reality. Even with all of the power and tools that Torque 3D provides, finishing a high quality 3D game requires time and knowledge.

"Torque 3D Game Development Cookbook" is a practical guide that takes you through each of the major steps on the journey to creating your game, while learning a few tricks along the way.

The recipes in this book start off with learning some of the finer points about TorqueScript. The book then moves on to each of Torque 3D’s subsystems and ends with a variety of game play recipes.

The various topics covered include activating level-specific game code and scheduling game events, dragging and dropping items between windows to work with an in-game inventory system, and covering the seams between objects with well placed decals. Some of the advanced topics include writing custom shaders and postFX, using zones to improve rendering performance, and enhancing your game’s ambience through sound.

Once you are done with Torque 3D Game Development Cookbook you’ll be on your way to creating amazing 3D games and gain expert knowledge of Torque 3D.

Publication date:
January 2013
Publisher
Packt
Pages
380
ISBN
9781849693547

 

Chapter 1. TorqueScript: The Only Script You Need to Know

In this chapter we will cover the following topics:

  • Accessing delimited fields within a string

  • Iterating on words in a string list

  • Retrieving components of a variable using accessors

  • Iterating on objects in a SimSet or SimGroup collection

  • Getting a random object from a SimSet or SimGroup collection

  • Finding an object in a SimSet or SimGroup collection using its internal name

  • Executing a method on a SimSet or SimGroup collection

  • Creating a new SimObject instance

  • Creating a new internal name only SimObject instance

  • Creating a new Datablock object

  • Creating a new singleton

  • Extending a SimObject instance using the class property

  • Using a variable to access methods or properties of a SimObject instance

  • Using call() to call a variable method on a SimObject instance with arguments

  • Using call() to call a variable function with arguments

  • Using script arrays as dictionaries

  • Using ArrayObject and custom script sorting callbacks

  • Scheduling SimObject methods

  • Scheduling functions

  • Activating and deactivating a package

 

Introduction


TorqueScript is the scripting language of the Torque 3D game engine. It is used to define game objects and to create the rules of play. It also forms the basis for manipulating the GUI system of Torque 3D.

All TorqueScript files have a .cs extension. These files may be edited with a standard text editor, or a program such as Torsion, which was made for working with TorqueScript. TorqueScript files may also be precompiled into .dso files. This is a binary representation of the TorqueScript code, and prevents others from modifying it. Very often a game will ship with only the precompiled .dso files.

In this chapter we will learn about some important‑‑and often lesser known‑‑TorqueScript concepts and shortcuts. Let's jump right in!

 

Accessing delimited fields within a string


When working with lists in TorqueScript, be it a list of scene object IDs or a set of Cartesian coordinates, we will invariably come across space-delimited strings. For example, calling the getPosition() method on a scene object will return a three-field string such as 13.4 -2.1 96.35 that represents the world distance along the x, y, and z axes, respectively.

TorqueScript provides a number of functions that allows us to access and manipulate the fields within space-delimited strings. In this recipe we will learn how to use these functions when working with string variables.

Getting ready

We will be adding a new TorqueScript function to a project based on the Torque 3D Full template and try it out using the Empty Terrain level. If you haven't already, use the Torque Project Manager (Project Manager.exe) to create a new project from the Full template. It will be found under the My Projects directory. Then start up your favorite script editor, such as Torsion, and let's get going!

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com . If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

How to do it...

We are going to write a TorqueScript function that will parse a space-delimited string and output the results to the console. This is done as follows:

  1. Open the game/scripts/client/client.cs script file and add the following code to the bottom:

    function operateOnFields1()
    {
       // Get the player's position in the world.
       // Will be a string in the form of "x y z".
       %position =
          ServerConnection.getControlObject().getPosition();
       
       // Print out the player's position to the
       // console in its raw format
       echo("Raw player position: " @ %position);
       
       // Get the number of fields in the returned
       // position string
       %count = getWordCount(%position);
       
       // Print the value of each field to the console
       echo("Player position by field index:");
       for (%i=0; %i<%count; %i++)
       {
          echo("  " @ %i @ ": " @ getWord(%position, %i));
       }
       
       // Print out only the x and y fields
       echo("Player x and y position only: " 
             @ getWords(%position, 0, 1));
       
       // Set the 3rd field (the z value) to 0
       %position = setWord(%position, 2, "0");
       echo("Position variable with new z value: " @ %position);
       
       // Remove the z value (3rd field) to only be
       // left with "x y"
       %position = removeWord(%position, 2);
       echo("Position variable with no z value: " @ %position);
    }
  2. Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

    operateOnFields1();
    

    In the console we will see the following output:

    ==>operateOnFields1();
    Raw player position: 0.121759 1.13497 240.67
    Player position by field index:
      0: 0.121759
      1: 1.13497
      2: 240.67
    Player x and y position only: 0.121759 1.13497
    Position variable with new z value: 0.121759 1.13497 0
    Position variable with no z value: 0.121759 1.13497
    

How it works...

The previous code walks us through all of the functions used to access and manipulate the variables that contain space-delimited fields. We will now examine each of these functions and learn how to make use of them.

After obtaining the player's world position, our first action in the previous example is to get the number of fields within the space-delimited string (the %position variable). This is done using the getWordCount() function that has the following form:

amount = getWordCount( string );

In this form, string is the space-delimited string that contains the number of fields we want to eventually parse. The getWordCount() function returns the number of fields counted. The previous code stores this value into the %count variable. If there are no fields in the string, then 0 is returned.

With the number of fields now known, we can retrieve the individual x, y, and z values of the %position variable based on an index. To do this we use the getWord() function that has the following form:

field = getWord( string, index );

Here, the string parameter is the space-delimited string to parse, and the index parameter is the field number to retrieve. The getWord() function returns a string containing the single requested field. If the field index does not exist within the passed-in string, an empty string is returned.

The next action performed in the example code is to retrieve more than one field at a time. Specifically, the code extracts the x and y values from the player's position (the first and second field). We use the getWords() function to retrieve more than one field, which has the following form:

fields = getWords( string, startIndex, [endIndex] );

Here, the string parameter is the space-delimited string to parse, the startIndex parameter is the start of the range to retrieve, and the optional endIndex parameter is the end of the field range. If endIndex is not provided or has a value of -1, then all of the fields at the end of the string are returned.

The getWords() function returns a string containing all of the requested fields. If none of the requested fields exist within the passed-in string, an empty string is returned.

The example code then goes on to manipulate the %position variable by changing its z value (the third field). This is done with the setWord() function that has the following form:

result = setWord( string, index, replacement );

Here, the string parameter is the space-delimited string to modify, the index parameter is the field index in the string to modify, and the replacement parameter is the string used to replace the current value of the field. The setWord() function returns a new string with the modifications and doesn't change the passed-in string. If we wanted to modify the original variable, we would just use the same variable name for the return value as we did for the passed-in string. For example, consider the following code:

%position = setWord(%position, 2, "0");

The new string will essentially replace the previous string stored in %position.

If the index passed-in to setWord() is larger than the number of fields in the given string, the returned string is padded with empty fields to make up the difference, essentially appending the replacement string on to the end. For example, the following code would print a count of six to the console (the fifth index accesses the sixth field):

%val1 = "1 2 3";
%val2 = setWord( %val1, 5, "4");
echo( getWordCount( %val2 ) );

The final action in the example code removes a field from the string variable. This is done using the removeWord() function that has the following form:

result = removeWord( string, index );

Here, the string parameter is the space-delimited string to modify, and the index parameter is the field index in the string to remove. The removeWord() function returns a new string with the modifications and doesn't change the passed-in string. If the given field index does not exist within the string, the original string is returned unchanged.

As with the setWord() function, if we want to modify the original variable, we would need to pass it in as the string parameter as well as use it to store the result. This is done in the example code with the %position variable.

There's more...

While space-delimited strings are the most common type of list we will come across in TorqueScript, spaces are not the only way to delimit a string. Tabs and new lines may also be used. We could use tab delimiters when we want the fields to contain spaces, and new line delimiters when we want the fields to contain spaces or tabs.

The whole Word family of functions we just explored (getWord() and so on) actually works with more than just spaces. They treat all the spaces, tabs, and new lines as valid delimiters. But what if we don't want to count spaces as a delimiter, such as with a list of peoples' combined first and last names ("John Smith")? There are two other families of functions that narrow the scope of what is a valid delimiter: Field and Record.

Skipping spaces (only tab and new line delimiters)

The Field family of functions performs all of the same operations as the Word family of functions, except they only use tabs and new lines as field delimiters. For example, put the following function at the end of the game/scripts/client/client.cs script file:

function operateOnFields2()
{
   // Build up a list of tab delimited name
   %list = "John Smith" TAB "Mary Contrary" TAB "Fido";
   
   // Get the number of tab delimited fields
   %count = getFieldCount(%list);
   
   // Print out a list of fields by index
   for (%i=0; %i<%count; %i++)
   {
      echo("Field " @ %i @ ": " @ getField(%list, %i));
   }
}

Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

operateOnFields2();

The following will be output to the console:

==>operateOnFields2();
Field 0: John Smith
Field 1: Mary Contrary
Field 2: Fido

With the Field family of functions, any field that contains spaces is treated as a single field.

Skipping spaces and tabs (only new line delimiters)

The Record family of functions performs all of the same operations as the Word family of functions, except they only use new lines as field delimiters. For example, put the following function at the end of the game/scripts/client/client.cs script file:

function operateOnFields3()
{
   // Build up a list of new line delimited items
   %list = "0" TAB "First item" NL
           "1" TAB "Second item" NL
           "2" TAB "Third item";
   
   // Get the number of tab delimited fields
   %count = getRecordCount(%list);
   
   // Print out a list of fields by index
   for (%i=0; %i<%count; %i++)
   {
      echo("Field " @ %i @ ": '" 
           @ getRecord(%list, %i) @ "'");
   }
}

Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

operateOnFields3();

The following will be output to the console:

==>operateOnFields3();
Field 0: '0^First item'
Field 1: '1^Second item'
Field 2: '2^Third item'

In the console, the output above the caret (^) symbol represents a tab. With the Record family of functions, any field that contains spaces and tabs is treated as a single field.

See also

  • Iterating on words in a string list

  • Retrieving components of a variable using accessors

 

Iterating on words in a string list


While creating a game there are times that we need to step through a string list, one item at a time, and do some work on that item. An example could be a collection of object IDs produced by a volumetric search of the scene. We then want to do something with these found objects, such as possibly applying damage. In this recipe, we will learn a quick way to retrieve each item in a string list and do something with that item.

Getting ready

We will be adding some new TorqueScript functions to a project based on the Torque 3D Full template and try it out using the Empty Terrain level. If you haven't already, use the Torque Project Manager (Project Manager.exe) to create a new project from the Full template. It will be found under the My Projects directory. Then start up your favorite script editor, such as Torsion, and let's get going!

How to do it...

We are going to write a TorqueScript function that will retrieve each item from a string list and do some work on it as follows:

  1. Open the game/scripts/server/game.cs script file and add the following code to the bottom:

    function parseStringList1()
    {
       // Populate a variable with some sample object ID's.
       //This could have come from an axis aligned bounding box
       //search in the scene.
       %objects = "1121 1122 1438 1643 2025 1118 1564";
       
       // Step through each object in the string list.
       // This string list could contain any valid
       // string characters and doesn't need to be
       // limited to object ID's.
       echo("-- Starting string list iteration");
       foreach$ (%id in %objects)
       {
          // Perform some action on the object
          doSomething(%id);
       }
       echo("-- Finished string list iteration");
    }
    
    function doSomething(%objID)
    {
       // Print out the object ID to the console.
       echo("Processing object ID: " @ %objID);
    }
  2. Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

    parseStringList1();
    

    In the console we will see the following output:

    ==>parseStringList1();
    -- Starting string list iteration
    Processing object ID: 1121
    Processing object ID: 1122
    Processing object ID: 1438
    Processing object ID: 1643
    Processing object ID: 2025
    Processing object ID: 1118
    Processing object ID: 1564
    -- Finished string list iteration
    

How it works...

The previous code uses the foreach$() function to retrieve each item in the string list and does some work on it. In this example, the item is passed on to another function.

The foreach$() function is different than most of the looping TorqueScript functions (such as for()) in that it takes two parameters that are separated by the in word rather than a semicolon. It is also unusual in that it creates a new variable to hold a string of the item. The foreach$() function has the following form:

foreach$( item in stringList)
{
   ... Do something with item ...
}

Here, the stringList parameter is the list of items to be processed, and the item parameter is a new variable that is created to hold a string of the item. It is the item variable that we work on. In our previous example this is the %id variable.

There's more...

There is another way to step through the items in a string by using the getWordCount() and getWord() functions. For example, put the following function at the end of the game/scripts/server/game.cs script file after the code we entered in the foreach$() example:

function parseStringList2()
{
   // Populate a variable with some sample object ID's.
   %objects = "1121 1122 1438 1643 2025 1118 1564";
   
   // Get the number of items in the string list
   %count = getWordCount(%objects);
   
   // Step through each object in the string list.
   // This string list could contain any valid string
   // characters and doesn't need to be limited to
   // object ID's.
   echo("-- Starting string list processing");
   for (%i=0; %i<%count; %i++)
   {
      // Get the object ID from the string list
      %id = getWord(%objects, %i);
      
      // Perform some action on the object
      doSomething(%id);
   }
   echo("-- Finished string list processing");
}

Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

parseStringList2();

In the console we will see the following output:

==>parseStringList2();
-- Starting string list processing
Processing object ID: 1121
Processing object ID: 1122
Processing object ID: 1438
Processing object ID: 1643
Processing object ID: 2025
Processing object ID: 1118
Processing object ID: 1564
-- Finished string list processing

The results end up being the same as when we used the foreach$() function. So why would we use this method over the foreach$() method? Well, for one thing, we will see this pattern in a number of stock Torque 3D scripts that were written prior to foreach$() being added to the TorqueScript language. We will also see this pattern in a lot of game developers' script code, just because they are not aware of the newer foreach$() function (now you are one of the special ones that do know!).

One advantage of foreach$() over using the getWordCount() and getWord() patterns to step through a list‑‑other than a lot less script code‑‑is that you don't have to perform two calls into the engine (getWordCount() and getWord()); every call we don't have to make back to the engine is a performance increase.

But then why would we actively use this alternative pattern at all? It is the only way to work with the other types of delimiters, such as tab and a new line. By replacing getWordCount() and getWord() with getFieldCount() and getField()respectively, not spaces but only tabs and new lines are treated as delimiters, and by replacing them with getRecordCount() and getRecord(), neither spaces nor tabs, but only new lines are treated as delimiters. This allows us to work with different types of data.

See also

  • Accessing delimited fields within a string

 

Retrieving components of a variable using accessors


Under TorqueScript position, vector, matrix, and color variables are all very similar. They are made up of a string with space-delimited components (or fields). For example, a position variable may be defined as follows:

// A position is of the form "x y z"
%position = "1.2 0.34 13.22";

TorqueScript provides a set of special accessors to work with these common types of variables. This allows us to access each individual component and manipulate it.

Getting ready

We will be adding a new TorqueScript function to a project based on the Torque 3D Full template and try it out using the Empty Terrain level. If you haven't already, use the Torque Project Manager (Project Manager.exe) to create a new project from the Full template. It will be found under the My Projects directory. Then start up your favorite script editor, such as Torsion, and let's get going!

How to do it...

We are going to write a TorqueScript function that will demonstrate how to retrieve components of a variable using the special accessors as follows:

  1. Open the game/scripts/server/game.cs script file and add the following code to the bottom:

    function variableAccessors1()
    {
       // A position is of the form "x y z"
       %position = "23.0 2.35 9.78";
       
       // Print out each component to the console
       echo("Position X: " @ %position.x);
       echo("         Y: " @ %position.y);
       echo("         Z: " @ %position.z);
       echo("\n");
       
       // Add one to the position's y component
       %position.y += 1;
       
       // Print out each component to the console
       echo("New Position X: " @ %position.x);
       echo("             Y: " @ %position.y);
       echo("             Z: " @ %position.z);
       echo("\n");
       
       // Define a vector in the form of "x y z w"
       %vector = "1 0 0 1";
       
       // Print out each component to the console
       echo("Vector X: " @ %vector.x);
       echo("       Y: " @ %vector.y);
       echo("       Z: " @ %vector.z);
       echo("       W: " @ %vector.w);
       echo("\n");
       
       // A color is of the form "r g b a"
       %color = "128 0 128 255";
       
       // Print out each color component to the console
       echo("Color R: " @ %color.r);
       echo("      G: " @ %color.g);
       echo("      B: " @ %color.b);
       echo("      A: " @ %color.a);
       echo("\n");
       
       // Modify the color components
       %color.r += 64;
       %color.g += 64;
       %color.b = 0;
       
       // Print out each color component to the console
       echo("New Color R: " @ %color.r);
       echo("          G: " @ %color.g);
       echo("          B: " @ %color.b);
       echo("          A: " @ %color.a);
    }
  2. Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

    variableAccessors1();
    

    In the console we will see the following output:

    ==>variableAccessors1();
    Position X: 23.0
             Y: 2.35
             Z: 9.78
    
    New Position X: 23.0
                 Y: 3.35
                 Z: 9.78
    
    Vector X: 1
           Y: 0
           Z: 0
           W: 1
    
    Color R: 128
          G: 0
          B: 128
          A: 255
    
    New Color R: 192
              G: 64
              B: 0
              A: 255
    

How it works...

TorqueScript provides two sets of convenience accessors to help work with components of a variable. The first set is x, y, z, and w. This set is most often used with the x, y, and z components of position and vector variables; and we will nearly always use the first three accessors, ignoring the w accessor. The second set is r, g, b, and a, which correspond to the red, green, blue, and alpha components of a variable containing color information.

There's more...

Behind the scenes, whenever we use one of these special accessors, TorqueScript is retrieving the corresponding space-delimited field within the variable. So the x and r accessors refer to the first field, the y and g accessors refer to the second field, and so on.

This also means that the positional/vector accessors and color accessors can be freely mixed together. For example, the red component of a color variable may just as easily be retrieved using the x accessor, and the alpha component may be retrieved with the w accessor. This also works the other way round with the components of a vector retrieved using the color accessors. We can see this in action by copying the following function at the end of the game/scripts/server/game.cs script file:

function variableAccessors2()
{
   // Define a vector in the form of "x y z w"
   %vector = "1 0 0 1";
   
   // Print out each xyzw component to the console
   echo("Vector X: " @ %vector.x);
   echo("       Y: " @ %vector.y);
   echo("       Z: " @ %vector.z);
   echo("       W: " @ %vector.w);
   echo("\n");
   
   // Print out each rgba component to the console
   echo("Vector R: " @ %vector.r);
   echo("       G: " @ %vector.g);
   echo("       B: " @ %vector.b);
   echo("       A: " @ %vector.a);
}

Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

variableAccessors2();

In the console we will see the following output:

==>variableAccessors2();
Vector X: 1
       Y: 0
       Z: 0
       W: 1


Vector R: 1
       G: 0
       B: 0
       A: 1

The results demonstrate that we can retrieve components of a variable using either set of accessors.

See also

  • Accessing delimited fields within a string

 

Iterating on objects in a SimSet or SimGroup collection


The SimSet and SimGroup classes hold collections of game objects, also known as SimObject (the base class of nearly every object in Torque 3D). In this recipe we will iterate through a collection of objects in order to perform some operation on them.

Getting ready

We will be adding a new TorqueScript function to a project based on the Torque 3D Full template and try it out using the Empty Terrain level. If you haven't already, use the Torque Project Manager (Project Manager.exe) to create a new project from the Full template. It will be found under the My Projects directory. Then start up your favorite script editor, such as Torsion, and let's get going!

How to do it...

We are going to write a TorqueScript function that will iterate through the objects of a SimSet or SimGroup collection as follows:

  1. Open the game/scripts/server/game.cs script file and add the following code to the bottom:

    function iterateSimGroup1()
    {
       // Iterate through the MissionGroup SimGroup to retrieve
       // each SimObject.  This holds the top level objects from
       // the loaded level.
       foreach (%obj in MissionGroup)
       {
          // Print some information about the object in the group
          echo(%obj.getId() SPC %obj.getClassName());
       }
    }
  2. Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

    iterateSimGroup1();
    

    In the console we will see the following output:

    ==>iterateSimGroup1();
    4275 LevelInfo
    4276 ScatterSky
    4277 TerrainBlock
    4278 SimGroup
    

How it works...

The foreach() function (which is different than the foreach$() function that is used to parse space-delimited strings) is used to iterate through a SimSet or SimGroup collection in order to retrieve one SimObject instance at a time and perform some operation on it. In this example, the ID and class name of object are output to the console.

The foreach() function is different than most of the looping TorqueScript functions (such as for()), in that it takes two parameters that are separated by the word in rather than a semicolon. It is also unusual in that it creates a new variable to hold the current SimObject instance (the %obj variable in our previous example). The foreach() function has the following form:

foreach( object in simgroup)
{
   ... Do something with object ...
}

Here, the simgroup parameter is the collection of objects to be processed (could also be a SimSet parameter), and the object parameter is a new variable that is created to hold the current SimObject instance. It is the object variable that we do work on.

There's more...

An alternative method to access a object collection of a SimSet or SimGroup parameter is to use a standard for() loop and getObject() method of the collection. For example, put the following function at the end of the game/scripts/server/game.cs script file, following the code we entered in the foreach() example:

function iterateSimGroup2()
{
   // Get the number of objects in the SimGroup
   %count = MissionGroup.getCount();
   
   // Iterate through the MissionGroup
   for (%i=0; %i<%count; %i++)
   {
      // Retrieve the object
      %obj = MissionGroup.getObject(%i);
      
      // Print some information about the object in the group
      echo(%obj.getId() SPC %obj.getClassName());
   }
}

Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

iterateSimGroup2();

In the console we will see the following output:

==>iterateSimGroup2();
4275 LevelInfo
4276 ScatterSky
4277 TerrainBlock
4278 SimGroup

The results end up being the same as when we used the foreach() function. There is no real advantage of using this method over the foreach() method. We do often see this pattern in a number of stock Torque 3D scripts that were written prior to foreach() being added to the TorqueScript language. We will also see this pattern in a lot of game developers' script code just because they are not aware of the newer foreach() function (now you are one of the special ones that do know!).

See also

  • Getting a random object from a SimSet or SimGroup collection

  • Finding an object in a SimSet or SimGroup collection using its internal name

  • Executing a method on a SimSet or SimGroup collection

 

Getting a random object from a SimSet or SimGroup collection


Sometimes we have a collection of SimObject instances in a SimSet or SimGroup collection and we would like to retrieve a single, random object from this collection. In this recipe we will discover a quick and easy method to do just that.

Getting ready

We will be adding a new TorqueScript function to a project based on the Torque 3D Full template and try it out using the Empty Terrain level. If you haven't already, use the Torque Project Manager (Project Manager.exe) to create a new project from the Full template. It will be found under the My Projects directory. Then start up your favorite script editor, such as Torsion, and let's get going!

How to do it...

We are going to write a TorqueScript function that will demonstrate how to retrieve a random SimObject instance from a SimSet or SimGroup collection as follows:

  1. Open the game/scripts/server/game.cs script file and add the following code to the bottom:

    function getRandomObject1()
    {
       // Retrieve a random object from the MissionGroup.  This holds
       // the top level objects from the loaded level.
       %object = MissionGroup.getRandom();
       
       // Print some information about the object to the console
       echo(%object.getId() SPC %object.getClassName());
    }
  2. Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

    getRandomObject1();
    

    In the console, we will see the following output (you may have a different object chosen):

    ==>getRandomObject1();
    4276 ScatterSky
    

How it works...

The getRandom() method of a SimSet and SimGroup collection returns a randomly selected SimObject ID from the collection. If the collection is empty, a value of -1 is returned.

See also

  • Iterating on objects in a SimSet or SimGroup collection

  • Finding an object in a SimSet or SimGroup collection using its internal name

  • Executing a method on a SimSet or SimGroup collection.

 

Finding an object in a SimSet or SimGroup collection using its internal name


SimObject instances may optionally have a globally unique name. This makes it easy to work with a SimObject instance from any script function or method. However, this can clutter up the global namespace and there is the possibility of naming collisions. SimObject instances may also have an internal name that need only be unique within its direct parent SimSet or SimGroup collection. We most often find internal names being used in Torque 3D's GUI system where all GuiControl objects are also the SimGroup collections, and they are always organized in a hierarchy. In this recipe we will retrieve a SimObject instance from its collection based on its internal name.

Getting ready

We will be adding a new TorqueScript function to a project based on the Torque 3D Full template and try it out using the Empty Terrain level. If you haven't already, use the Torque Project Manager (Project Manager.exe) to create a new project from the Full template. It will be found under the My Projects directory. Then start up your favorite script editor, such as Torsion, and let's get going!

How to do it...

We are going to write a TorqueScript function that will demonstrate how to find a SimObject instance using its internal name as follows:

  1. Open the game/scripts/server/game.cs script file and add the following code to the bottom:

    function getByInternalName1()
    {
       // Create a SimGroup
       new SimGroup(MyGroup);
       
       // Create some script objects with unique internal
       // names and add each one to our SimGroup.
       // The ScriptObject class is just a generic SimObject that
       // we can create in TorqueScript.
       %so = new ScriptObject()
                 {
                    internalName = "name1";
                 };
       MyGroup.add(%so);
    
       %so = new ScriptObject()
                 {
                    internalName = "name2";
                 };
       MyGroup.add(%so);
    
       %so = new ScriptObject()
                 {
                    internalName = "name3";
                 };
       MyGroup.add(%so);
       
       // Find an object in the SimGroup using its internal name
       %object = MyGroup.findObjectByInternalName("name2");
       
       // Print some information about the object to the console
       echo("Found object " @ %object.getId() 
            @ " with internal name: " @ %object.internalName);
       
       // Clean up the SimGroup and all of the script objects
       MyGroup.delete();
    }
  2. Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

    getByInternalName1();
    

    In the console we will see the following output:

    ==>getByInternalName1();
    Found object 4154 with internal name: name2
    

How it works...

So that we may see the internal name search in action, we first create some SimObject instances and add them to a SimGroup collection. In our previous example we use the ScriptObject class for our SimObject instance. The ScriptObject class is the most basic SimObject derived class we can build from script, and can come in handy when we want an object that can hold arbitrary data. We give a unique value to the internalName property of each ScriptObject class.

With all of the objects and a group collection set up, we use the findObjectByInternalName() method of a SimGroup collection. This method is also available in a SimSet collection and has the following form:

foundObject = SimGroup.findObjectByInternalName( name, [searchChildren] );

Here, the name parameter is the internal name we wish to search for in the SimGroup collection, and the optional searchChildren parameter indicates the child collections should be searched when set to true (it is false by default). If a SimObject instance with its internalName property matches the requested name, it is returned. If no SimObject instance matches or there are none yet in the collection, 0 is returned.

There's more...

Let's look at some additional ways to search for a SimObject class in a SimSet or SimGroup collection.

Including children SimSet and SimGroup collections in the search

The SimSet and SimGroup classes are derived from the SimObject class, and therefore may also be children of a SimSet or SimGroup collection. One place we come across this is with a Torque 3D level file. At the top is a SimGroup collection named MissionGroup. Under this group are a number of different objects, and usually a number of different SimGroup objects in order to better organize objects of the level. The Empty Terrain level that we have been using in our examples has a child SimGroup collection named PlayerDropPoints that holds all the locations that a player may spawn at.

Another place we come across a hierarchy of SimGroup objects is when working with Torque 3D's GUI system. All GUI objects are derived from the GuiControl class, which itself is a SimGroup collection. This means that any GuiControl class can be a child of another GuiControl class. So as to not clutter up the global namespace with the names of GuiControl objects, we often use the internalName property as much as possible.

When we want to find a GuiControl class to work with (such as one of many radio buttons in a group), we perform an internal name search at some parent or grandparent GuiControl. If you keep the internalName property unique among GuiControl instances in a dialog window for example, we can start the search with the GuiControl class of the window and need not worry about name collisions with other dialog windows.

To perform a search starting from a SimGroup or SimSet collection and include all of its children, we will set optional searchChildren parameter of the findObjectByInternalName() method to true. For example, consider the following line of code:

%object = MyGroup.findObjectByInternalName("name3", true);

Starting with MyGroup this will search through all children SimGroup and SimSet objects until it comes across the first SimObject instance with its internalName property set to name3. If no SimObject instance matches or there are no objects in the collection, a value of 0 is returned.

Using the special -> operator

Using findObjectByInternalName() is very useful, especially if we pass-in a computed variable to its name parameter. But if we know the exact name to search for, there is a much less verbose shortcut. We can use the special -> operator. For example, look at the following code:

%object = MyGroup->name2;

The new code is equivalent to the following code:

%object = MyGroup.findObjectByInternalName("name2");

Just like this usage of findObjectByInternalName() the -> operator will not search in child SimGroup or SimSet objects.

Another handy usage of the -> operator occurs when we want to immediately call a method on the found object. In this case we can just append the method to the search call as follows:

MyGroup->name2.doSomething(true);

What happens here is, first a SimObject instance with the internal name of name2 is found, and then the doSomething() method is called on that SimObject instance.

Using the special --> operator

As with the -> operator discussed in the previous section, the --> operator is also a shortcut for using findObjectByInternalName(). The only difference is that the --> operator will also search in child SimGroup and SimSet instances when looking for a matching internal name on a SimObject instance. For example, consider the following line of code:

%object = MyGroup-->name2;

This is equivalent to the following code, with the searchChildren parameter set to true:

%object = MyGroup.findObjectByInternalName("name2", true);

Another handy usage of the --> operator occurs when we want to immediately call a method on the found object. In this case we can just append the method to the search call as follows:

MyGroup-->name2.doSomething(true);

What happens here is first a SimObject instance with the internal name of name2 is found in all the children SimGroup and SimSet objects, and then the doSomething() method is called on that SimObject instance. We often find this pattern when working with the GuiControl objects.

See also

  • Iterating on objects in a SimSet or SimGroup collection

  • Getting a random object from a SimSet or SimGroup collection

  • Executing a method on a SimSet or SimGroup collection

 

Executing a method on a SimSet or SimGroup collection


During game play we may want to call the same method on all the SimObject instances that belong to a SimSet or SimGroup collection. Rather than iterate through each SimObject instance in the collection and execute its method, TorqueScript has a handy one-line shortcut that we'll make use of in this recipe.

Getting ready

We will be adding a new TorqueScript function to a project based on the Torque 3D Full template and try it out using the Empty Terrain level. If you haven't already, use the Torque Project Manager (Project Manager.exe) to create a new project from the Full template. It will be found under the My Projects directory. Then start up your favorite script editor, such as Torsion, and let's get going!

How to do it...

We are going to write a TorqueScript function that will demonstrate how to execute a method on all the SimObject instances in a SimGroup collection as follows:

  1. Open the game/scripts/server/game.cs script file and add the following code to the bottom:

    function executeMethodOnGroup1()
    {
       // Create a SimGroup
       new SimGroup(MyGroup);
       
       // Create some script objects with some properties and
       // add each one to our SimGroup.  Give each a class
       // property so they belong to the same subclass and have
       // access to the same method.
       // The ScriptObject class is just a generic SimObject that
       // we can create in TorqueScript.
       %so = new ScriptObject()
                 {
                    class = "MyClass1";
                    internalName = "name1";
                    myValue1 = "abc";
                 };
       MyGroup.add(%so);
    
       %so = new ScriptObject()
                 {
                    class = "MyClass1";
                    internalName = "name2";
                    myValue1 = "123";
                 };
       MyGroup.add(%so);
    
       %so = new ScriptObject()
                 {
                    class = "MyClass1";
                    internalName = "name3";
                    myValue1 = "a1b2";
                 };
       MyGroup.add(%so);
       
       // Execute our method on all SimObjects in the group.
       // This is the same as iterating through the group
       // and calling object.method() on each of them.
       echo("-- Starting callOnChildren()");
       MyGroup.callOnChildren(outputProperties, "myValue1");   
       echo("-- Finished callOnChildren()");
       
       // Clean up the SimGroup and all of the script obejcts
       MyGroup.delete();
    }
    
    // A method for our custom script class
    function MyClass1::outputProperties(%this, %propertyName)
    {
       // Get the script object's SimObject ID
       %id = %this.getId();
       
       // Get the script object's internal name
       %name = %this.internalName;
       
       // Get the value of the passed-in property name
       %value = %this.getFieldValue(%propertyName);
       
       // Print out to the console
       echo("ScriptObject ID:" @ %id 
            @ "  Name:" @ %name @ "  Value:" @ %value);
    }
  2. Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

    executeMethodOnGroup1();
    

    In the console we will see the following output:

    ==>executeFunctionOnGroup1();
    -- Starting callOnChildren()
    ScriptObject ID:4153  Name:name1  Value:abc
    ScriptObject ID:4154  Name:name2  Value:123
    ScriptObject ID:4155  Name:name3  Value:a1b2
    -- Finished callOnChildren()
    

How it works...

The SimGroup and SimSet callOnChildren() methods automatically step through each child SimObject instance and if the requested method is valid on the object, it is called with the passed-in arguments. The callOnChildren() method has the following form:

SimGroup.callOnChildren( method, args… );

Here, the method parameter is the method to be called on each of the SimObject objects in the collection, and the args… parameter is actually a variable number of arguments that will be passed-in to the method. In our previous example we pass-in one argument, which is the name of the property we wish our method to process.

The callOnChildren() method doesn't just process the child SimObject instances that are part of a SimGroup or SimSet collection, it also traverses any child SimGroup or SimSet collection, and executes the method on their SimObject instances.

There's more...

If we want to call a method only on all the SimObject objects that are the immediate children of a SimGroup or SimSet collection and not traverse through the hierarchy of the collection, we can use the callOnChildrenNoRecurse() method. It has the same form as callOnChildren() as follows:

SimGroup.callOnChildrenNoRecurse( method, args… );

We could modify the method call of the SimGroup collection from our previous example and replace it with callOnchildrenNoRecurse() as follows:

MyGroup.callOnChildrenNoRecurse(outputProperties, "myValue1"); 

With our particular example, we end up with the same output to the console, as there are no child SimGroup or SimSet objects that would be skipped by using callOnChildrenNoRecurse().

See also

  • Iterating on objects in a SimSet or SimGroup collection

  • Getting a random object from a SimSet or SimGroup collection

  • Finding an object in a SimSet or SimGroup collection using its internal name

 

Creating a new SimObject instance


A SimObject instance is the base class from which all the other classes that can be accessed using TorqueScript are derived. We don't work with the SimObject class directly, but rather work with one of the derived classes. In this recipe we will see the various ways to construct a new SimObject-based class.

How to do it...

Creating a SimObject-based class is straightforward and there are a number of options available that we will look into later. Here we will create a ScriptObject instance, the simplest SimObject derived class, and assign it to a variable as follows:

%object = new ScriptObject();

How it works...

We use the new keyword to create a new SimObject-based class, which returns the new object so it may be stored into a variable for future use.

There's more...

There are a number of different options when it comes to creating a SimObject derived class. Let's take a look at them.

Creating a new SimObject instance with a globally unique name

If we want a SimObject instance to be accessible from anywhere in the script ,we can assign a global name to it. Then when we want to work with the SimObject instance we can just use its name. As an example, we will create a Player class object and assign a globally unique name to it as follows:

new Player(MyPlayer);

Here we create a new Player class object and give it a globally unique name of MyPlayer. Making use of the return value of the new keyword in a local variable is optional as we can now access this new object with its unique name. For example, this is how we obtain the player's position using the name of the object:

%pos = MyPlayer.getPosition();

What happens if there is already another object that has the same global name as the one we wish to create? Normally Torque 3D will output an error to the console stating Cannot re-declare object and the new object will not be created (the new keyword will return a value of 0). However, this behavior may be modified through the use of the $Con::redefineBehavior global variable. The following table lists the accepted values for this variable:

String value

Impact on object creation

replaceExisting

This deletes the current object with the same name and replaces it with the new object.

renameNew

This adds a number to the end of the name of the new object. This number starts at one and is incremented until a unique name combination is found.

unnamedNew

This removes the global name from the new object.

postfixNew

This appends a string to the end of the name of the new object. This string is defined in the global variable $Con::redefineBehaviorPostfix and is set to an empty string by default.

oldRedefineBehavior

This indicates the default condition of not creating the new object. This is also the behavior when $Con::redefineBehavior is set to an empty string.

Modifying $Con::redefineBehavior must be done with care as it affects how the object creation system of Torque 3D operates. It can also have a non-intuitive impact on the global name of a new object.

Creating a new SimObject instance with defined properties

We can set up properties of a new SimObject instance at the same time the object is created. This is done by writing a list of properties and their values within curly braces as part of the object creation. Setting properties at the time of object creation saves a lot of typing later on. It also provides immediate values for the properties of an object rather than first having them set to some default value. For example, we can set the properties for a new Player object as follows:

new Player(MyPlayer)
    {
       datablock = SoldierDatablock;
       position = "0 0 10";
       size = "1 1 1";
       squad = "Bravo";
    };

In this example we are setting the standard datablock, position, and size properties for the Player class. We are also setting a script-specific property called squad. This is known as a dynamic property and only means something to our game play code and not the core Torque 3D engine.

Creating a new SimObject instance based on another SimObject instance

Sometimes we want to create a new SimObject instance whose properties are based on another, previously created SimObject instance. This previously created SimObject instance is known as the copy source and is passed-in as part of the creation process of a SimObject instance. For example, we will create a new SimObject instance based on the Player class object that we created in the previous example as follows:

new Player(MyPlayer2 : MyPlayer)
    {
       position = "2 0 10";
    };

In this example, the MyPlayer2 object will have all of the same properties (including the dynamic ones) as the MyPlayer object, except for the position property that we've explicitly set. This full-copying of properties only occurs if the copy source object is of the same class as the new SimObject instance. If the copy source is of a different class, only the dynamic properties (if any) will be copied over and not the class-specific ones.

It should also be noted that this is only a copy of properties at the time of object creation. There is no parent/child relationship occurring. In our previous example, modifying a property on the MyPlayer object later on will have no impact on the properties of MyPlayer2.

See also

  • Creating a new internal name only SimObject instance

  • Creating a new Datablock object

  • Creating a new singleton

  • Extending a SimObject instance using the class property

 

Creating a new internal name only SimObject instance


The internal name of a SimObject instance is not exposed to the world in the same way that its optional globally unique name is. We may access it using the internalName property of the SimObject instance and it is useful when searching for SimObject instances within a SimGroup or SimSet collection. When working with the GuiControl objects we will often make use of the internal name. Creating a new internal name only SimObject instance is a rarely used feature of TorqueScript, but we will learn to create it in this recipe.

How to do it...

Creating an internal name only SimObject instance is almost the same as creating an ordinary SimObject instance. The difference comes down to how we decorate the name we use. Here we will create a ScriptObject instance with an internal name, the simplest SimObject derived class, and assign it to a variable:

%object = new ScriptObject([MyScriptObject]);

How it works...

By surrounding the name of the SimObject instance with square brackets, Torque 3D automatically sets the internalName property of object rather than its globally unique name. If we call the getName() method on our new ScriptObject instance, it will return an empty string. But if we call the getInternalName() method, MyScriptObject will be returned.

There's more...

This shortcut to setting the internal name of a SimObject instance can be handy when working with GuiControl instances. The normal pattern works like the following:

new GuiWindowCtrl(MyDialog) {
   ... some properties here ...

   new GuiControl() {
      internalName = "control1";
      ... some properties here ...
   };
   new GuiControl() {
      internalName = "control2";
      ... some properties here ...
   };
};

With this pattern we have to set the internalName property of each GuiControl class manually. Using the special name decorators, the internalName property will be set automatically as follows:

new GuiWindowCtrl(MyDialog) {
   ... some properties here ...

   new GuiControl([control1]) {
      ... some properties here ...
   };
   new GuiControl([control2]) {
      ... some properties here ...
   };

See also

  • Creating a new SimObject instance

  • Creating a new Datablock object

  • Creating a new singleton

  • Extending a SimObject instance using the class property

 

Creating a new Datablock object


Datablock objects have static properties and are used as a common data store between game objects that derive from the GameBase class. They are defined on the server and are passed to clients (by SimObject ID only) during the initial transmission of a game level. In this recipe we'll see how to build a new Datablock object.

How to do it...

Creating a Datablock instance is straight forward. Here we will create a StaticShapeData Datablock, one of many possible Datablock classes as follows:

datablock StaticShapeData(MyShapeData)
{
   category = "Scenic";
   shapeFile = "art/shapes/rocks/rock1.dts";
   computeCRC = true;
   isInvincible = true;
};

How it works...

We use the datablock keyword when creating a new Datablock class object and always give it a unique global name. This name is used by other objects to reference this Datablock through the use of datablock property of the GameBase class.

If we happen to create two Datablock instances with the same global name but of different classes, then a Cannot Re-declare data block with a different class error is output to the console and nothing is done with the second Datablock instance. However, if the two global names and classes match, then all of the properties from the second Datablock instance are copied into the first.

There's more...

There are a number of different things to keep in mind when it comes to creating a Datablock instance. Let's take a look at them.

Creating a new Datablock object based on another Datablock object

We can base the properties of one Datablock instance on a previously created Datablock instance through the use of a copy source during the creation of the Datablock object. The process is the same as when using the new keyword. See the Creating a new SimObject instance recipe for more information on using a copy source.

Limited total number of Datablocks

The SimObject ID of a Datablock instance comes from a special pool that is reserved for the Datablock class. This ID pool only allows 1024 Datablock instances to be defined per game level. This number may be increased by changing the source code of Torque 3D. It is this special SimObject ID that is transferred between the server and client in a multiplayer game, and is used by the GameBase derived classes to reference their Datablock object on the client.

The datablock keyword should only be used on the server

Use of the datablock keyword should be limited to the server script files. Only the server keeps a track of the special Datablock ID pool, and all the Datablock objects on the client are deleted just prior to a game level being loaded.

Datablock properties should be considered static

Once a Datablock object has been created, its properties should be considered static. It is possible to modify the properties of a Datablock object at any time, just as with any other SimObject, but this should be avoided. The modified Datablock properties are not retransmitted between the server and client and will result in strange errors during game play.

See also

  • Creating a new SimObject instance

  • Creating a new internal name only SimObject instance

  • Creating a new singleton recipes

 

Creating a new singleton


A singleton is a SimObject instance that we only ever want one instance of. Typically we use singletons for shader objects, materials, and some audio objects. In this recipe we will learn how to create an object as a singleton.

How to do it...

Creating a singleton is straight forward. Here we will create a Material singleton, one of a number of SimObject classes that may be created as a singleton, as follows:

singleton Material(DECAL_scorch)
{
   baseTex[0] = "./scorch_decal.png";
   translucent = true;
   translucentBlendOp = None;
   translucentZWrite = true;
   alphaTest = true;
   alphaRef = 84;
};

How it works...

We use the singleton keyword when creating a new SimObject class object that we want only one instance of, and always give a unique global name to it. Other than ensuring that only one instance of this object will exist, the creation process is exactly the same as when the new keyword was used.

See also

  • Creating a new SimObject instance

  • Creating a new internal name only SimObject instance

  • Creating a new Datablock object

 

Extending a SimObject instance using the class property


TorqueScript allows us to extend the script methods of a SimObject derived class through the use of the class property. This is a very powerful feature as we can modify the behavior of a SimObject instance without the need to change its source code. In this recipe, we will learn how to make use of the class property to extend the methods available to a SimObject instance.

Getting ready

We will be adding a new TorqueScript function to a project based on the Torque 3D Full template and try it out using the Empty Terrain level. If you haven't already, use the Torque Project Manager (Project Manager.exe) to create a new project from the Full template. It will be found under the My Projects directory. Then start up your favorite script editor, such as Torsion, and let's get going!

How to do it...

We are going to write a TorqueScript function that will demonstrate how to extend a SimObject instance using its class property as follows:

  1. Open the game/scripts/server/game.cs script file and add the following code to the bottom:

    function useClassProperty1()
    {
       // Create a ScriptObject and define its class property.
       // The ScriptObject class is just a generic SimObject that
       // we can create in TorqueScript.
       new ScriptObject(MyScriptObj)
          {
             class = MyExtensionClass1;
          };
       
       // Call the first method defined by the new class
       %result = MyScriptObj.addValues(2, 3);
       
       // Output the result to the console
       echo("addValues(2, 3) returned: " @ %result);
       
       // Call the second method defined by the new class
       MyScriptObj.newMethod();
       
       // Clean up our object
       MyScriptObj.delete();
    }
    
    // First method defined by our new class
    function MyExtensionClass1::addValues(%this, %param1, %param2)
    {
       return %param1 + %param2;
    }
    
    // Second method defined by our new class
    function MyExtensionClass1::newMethod(%this)
    {
       // Get the top level C++ class this object derives from.
       %objClass = %this.getClassName();
       
       // Output to the console
       echo(%objClass SPC %this.getId() 
            @ " is using the MyExtensionClass1 class");
    }
  2. Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

    useClassProperty1();
    

    In the console we will see the following output:

    ==>useClassProperty1();
    addValues(2, 3) returned: 5
    ScriptObject 4152 is using the MyExtensionClass1 class
    

How it works...

In the example code we create a ScriptObject instance and set its class property to MyExtensionClass1. This extends the namespace of the ScriptObject instances to include all the methods that are defined by this new class. We then create two methods for this new class: addValues() and newMethod(). The first method takes two parameters, adds them together, and returns the result. The second method takes no parameters and just outputs some information to the console.

Making use of these new methods is straight forward. We just call them on the object like any other method. The Torque 3D engine takes care of everything for us.

There's more...

There is a lot more to know about working with the class property and extending the namespace of a SimObject instance. Let's take a look at these points.

Working with a class namespace after object creation

Object creation is not the only time we can extend the methods of a SimObject instance. We can either set the class property of the object directly or use the setClassNamespace() method to extend a SimObject instance at any time. The setClassNamespace() method has the following form:

SimObject.setClassNamespace( name );

Here the name parameter is the equivalent value passed-in to the class property. This also allows us to modify the class namespace hierarchy of a SimObject instance after having already set the class property to a different value. We can even clear the class namespace of a SimObject instance by passing-in an empty string.

If we want to know whether a SimObject instance is making use of a particular class namespace, we can use the isInNamespaceHierarchy() method. This method searches through the entire namespace tree of the object and has the following form:

result = SimObject.isInNamespaceHierarchy( name );

Here the name parameter is the namespace to search for, and the result is either true or false depending on whether the SimObject instance is making use of the given namespace or not. This is different from the isMemberOfClass() method, which only tests if the object is an instance of the given C++ class.

Extending even further with the superClass property

We can add a second layer of new methods through setting the superClass property of a SimObject instance. By using this property, we insert another set of methods between the C++ class of the object and the class namespace set with the class property.

It is not very common to make use of the superClass property, and sometimes it can be confusing understanding where each method call is routed to. But this extra namespace layer is available to those advanced users if they need it.

As with the class property, we can set the superClass instance either at the time of object creation or any time afterwards. To set the superClass namespace after object creation, we can set the superClass property directly or use the setSuperClassNamespace() method. The setSuperClassNamespace() method has the following form:

SimObject.setSuperClassNamespace( name );

Here the name parameter is the equivalent value passed-in to the superClass property.

Understanding the namespace hierarchy

The class and superClass properties of the SimObject instance are powerful features that allow us to extend the functionality of Torque 3D through script. However, it can be confusing to figure out where a method call will be handled. Let's walk through how this works behind the scene.

Unfortunately, the class and superClass properties have misleading names. They are not used to extend the class of a SimObject instance in a C++ sense. What they do is allow us to add new namespaces to an object when it comes to method lookup. We can insert new functionality into the namespace hierarchy of an object and intercept a method call before it is passed on to the C++ engine layer.

The namespace hierarchy of an object derived from a SimObject instance looks like the following list, with the numbers exhibiting the actual order:

  1. Optional globally unique name is used as a namespace

  2. Optional class property namespace

  3. Optional superClass property namespace

  4. Direct C++ class

  5. Parent C++ class

  6. Grandparent C++ class and so on…

So when we call a method on a SimObject instance, it is first sent to the globally unique name of the object as a namespace, if any. This allows us to write the methods that are specific to that object instance. If it is not handled at this level, the method call is passed on to the class property namespace, if any. If the method call is not handled there, it is passed on to the superClass property namespace if it has been defined. If the method call has still not been handled at this point, it then moves on to the C++ class hierarchy, where it is passed along until it reaches the SimObject class.

When a method call is handled at the previous hierarchy levels 1, 2, or 3, we can decide to continue to pass the method call along the call chain. This allows us to intercept a method call but still allows for the original functionality. To do this we use the special Parent namespace to call the method again.

It's time for an example. Add the following code to the end of the game/scripts/server/game.cs script file:

function useClassProperty2()
{
   // Create a ScriptObject and define its class property.
   // The ScriptObject class is just a generic SimObject that
   // we can create in TorqueScript.
   new ScriptObject(MyScriptObj)
      {
         class = MyExtensionClass2;
      };
   
   // Get the name of our ScriptObject.  Normally this
   // would just return our globally unique name, but
   // our class namespace will change how this method
   // works.
   %result = MyScriptObj.getName();
   
   // Output the result the console
   echo("getName() returned: '" @ %result @ "'");
   
   // Clean up our object
   MyScriptObj.delete();
}

function MyExtensionClass2::getName(%this)
{
   // Call the parent method and obtain its result
   %result = Parent::getName(%this);
   
   // Return our modified result
   return "Our name is: " @ %result;
}

Now start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

useClassProperty2();

In the console we will see the following output:

==>useClassProperty2();
getName() returned: 'Our name is: MyScriptObj'

The MyExtensionClass2 namespace defines the getName() method and the ScriptObject instance is making use of this namespace by setting its class property. When we call the getName() method of the object, as expected the MyExtensionClass2 intercepts it.

Within MyExtensionClass2::getName() the original getName() method is called with the static Parent namespace. This is done with the following code:

   // Call the parent method and obtain its result
   %result = Parent::getName(%this);

As this is a static call we need to manually include our object with the method call. Here this is done by passing in the %this variable as the first parameter to getName(). This Parent call eventually makes its way to the C++ SimObject class, where the globally unique name of the object is retrieved and passed into the %result variable. MyExtensionClass2::getName() uses this result for its own work and passes everything back to the caller.

Limitations of the class property

A limitation of using the class property is that once a class namespace has been assigned to a particular C++ class, it may only be used by that C++ class from then on.

For example, we had assigned the MyExtensionClass1 class namespace to the class property of a ScriptObject instance at the beginning of this recipe. From that point onwards, we can only use the MyExtensionClass1 class namespace with another ScriptObject instance. If we were to try to assign the same class namespace to a Player class instance, we would get an error in the console and the assignment would not occur.

The superClass property does not have this limitation. You may reuse a namespace between different C++ classes, so long as you limit its use to the superClass property.

See also

  • Creating a new SimObject instance

  • Creating a new internal name only SimObject instance

 

Using a variable to access methods or properties of a SimObject instance


Sometimes we don't know the global unique name of a SimObject instance or its ID when writing our script code. We may need to look up the name or ID, or even compute it. In these cases, we need to be able to reference a SimObject instance using a variable and in this recipe, we will learn how to do just that.

Getting ready

We will be adding a new TorqueScript function to a project based on the Torque 3D Full template and try it out using the Empty Terrain level. If you haven't already, use the Torque Project Manager (Project Manager.exe) to create a new project from the Full template. It will be found under the My Projects directory. Then start up your favorite script editor, such as Torsion, and let's get going!

How to do it...

We are going to write a TorqueScript function that will demonstrate how to retrieve the properties and methods of a SimObject instance using a variable as follows:

  1. Open the game/scripts/server/game.cs script file and add the following code to the bottom:

    function variableObjectAccess1()
    {
       // Build some ScriptObjects to work with.
       // The ScriptObject class is just a generic SimObject that
       // we can create in TorqueScript.
       new ScriptObject(MyScriptObj1)
          {
             MyValue1 = "obj1";
          };
    
       new ScriptObject(MyScriptObj2)
          {
             MyValue1 = "obj2";
          };
    
       new ScriptObject(MyScriptObj3)
          {
             MyValue1 = "obj3";
          };
       
       // Access each ScriptObject using a computed variable
       for (%i=0; %i<3; %i++)
       {
          // We will reference the object using its globally
          // unqiue name.  Build out that name here.
          %name = "MyScriptObj" @ (%i + 1);
          
          // Get a property's value from the ScriptObject.
          %value = %name.MyValue1;
          
          // Print out to the console
          echo("ScriptObject " @ %name 
               @ " has a value of: " @ %value);
       }
       
       // Clean up our ScriptObejcts also using a computed
       // variable
       for (%i=0; %i<3; %i++)
       {
          // Build the name of the object
          %object = "MyScriptObj" @ (%i + 1);
          
          // Delete the ScriptObject using its delete() method.
          %object.delete();
       }
    }
  2. Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

    variableObjectAccess1();
    

    In the console we will see the following output:

    ==>variableObjectAccess1();
    ScriptObject MyScriptObj1 has a value of: obj1
    ScriptObject MyScriptObj2 has a value of: obj2
    ScriptObject MyScriptObj3 has a value of: obj3
    

How it works...

In the example code we first set up some ScriptObject objects to work with. We give each ScriptObject instance a global unique name and some data to hold. We then move on to step through each object and retrieve its data using a for() loop. We want to focus on this part as shown in the following code:

      // We will reference the object using its globally
      // unqiue name.  Build out that name here.
      %name = "MyScriptObj" @ (%i + 1);

Here we build up a string variable named %name using a constant string that is appended with a computed value. When put together, these give us a string that matches the global unique name of each ScriptObject instance. We then use this string variable to retrieve the data from the ScriptObject instance as follows:

      // Get a property's value from the ScriptObject.
      %value = %name.MyValue1;

Within the Torque 3D engine, any time we attempt to access a property or method using the dot (.) operator, the engine performs a search in the SimObject name dictionary based on the preceding variable. If the name is found in the dictionary, the property is retrieved or the method is executed. If the name is not found in the dictionary, an error is output to the console. Our previous example code performs the same lookup when executing the delete() method as follows:

      // Build the name of the object
      %object = "MyScriptObj" @ (%i + 1);
      
      // Delete the ScriptObject using its delete() method.
      %object.delete();

See also

  • Using call() to call a variable method on a SimObject instance with arguments

 

Using call() to call a variable method on a SimObject instance with arguments


There are times when we don't know the name of the method of a SimObject instance while writing a script code. In these circumstances, we need to be able to call the method of a SimObject instance based on a variable. This recipe will show us how to use the SimObject call() method to execute another method, with possible passed-in arguments.

Getting ready

We will be adding a new TorqueScript function to a project based on the Torque 3D Full template and try it out using the Empty Terrain level. If you haven't already, use the Torque Project Manager (Project Manager.exe) to create a new project from the Full template. It will be found under the My Projects directory. Then start up your favorite script editor, such as Torsion, and let's get going!

How to do it...

We are going to write a TorqueScript function that will demonstrate how to programmatically call a SimObject method as follows:

  1. Open the game/scripts/server/game.cs script file and add the following code to the bottom:

    function callSimObjectMethod1()
    {
       // Set up an array to hold the methods we
       // will call.
       %methods[0] = "getPosition";
       %methods[1] = "getEulerRotation";
       %methods[2] = "getScale";
       
       // Iterate through the MissionCleanup SimGroup to retrieve
       // each SimObject.  This holds the top level objects that
       // have been created since the game level has started.
       foreach (%obj in MissionCleanup)
       {
          // Print some information about the object in the group
          echo(%obj.getId() SPC %obj.getClassName());
          
          // If the object derives from a SceneObject then call
          // our methods
          if (%obj.isMemberOfClass(SceneObject))
          {
             for (%i=0; %i<3; %i++)
             {
                // Call the method and obtain its result.
                // Note: none of our methods require passing
                // an argument.
                %result = %obj.call(%methods[%i]);
                
                // Output the result to the console
                echo("  " @ %methods[%i] @ "(): " @ %result);
             }
          }
       }
    }
  2. Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

    callSimObjectMethod1();
    

    In the console we will see the following output:

    ==>callSimObjectMethod1();
    4281 ScriptObject
    4289 Camera
      getPosition(): -0.277961 -0.851682 243.342
      getEulerRotation(): 0 -0 -0
      getScale(): 1 1 1
    4290 Player
      getPosition(): 0.595642 1.25134 240.748
      getEulerRotation(): 0 -0 67.1649
      getScale(): 1 1 1
    4291 SimGroup
    

How it works...

Our example code first fills up an array with the methods we will call on the objects. It then loops though all the dynamically created objects in the scene and if an object derives from the SceneObject class, call our methods on it using the SimObejct call() method.

The SimObject call() method has the following form:

result = SimObject.call( method, args… );

Here, the method parameter is the method we want to call on the SimObject instance, and the args… parameter is actually an optional set of arguments to pass into method. The call() method returns the output as denoted by result of the method call, if any.

If the given method does not exist on the SimObject instance, an empty string is returned. No error will be output to the console.

There's more...

If we are unsure whether a particular method exists on a SimObject instance, we can use the isMethod() method. It has the following form:

result = SimObject.isMethod( method );

Here the method parameter is the method name to search for, and result is either true or false depending on whether the method exists on the SimObject instance or not.

See also

  • Using a variable to access methods or properties of a SimObject instance

  • Using call() to call a variable function with arguments

 

Using call() to call a variable function with arguments


There are times when we don't know the name of a script function while writing a script code. In these circumstances, we need to be able to call a function based on a variable. This recipe will show how to use the call() method to execute a function, with possible passed-in arguments.

Getting ready

We will be adding a new TorqueScript function to a project based on the Torque 3D Full template and try it out using the Empty Terrain level. If you haven't already, use the Torque Project Manager (Project Manager.exe) to create a new project from the Full template. It will be found under the My Projects directory. Then start up your favorite script editor, such as Torsion, and let's get going!

How to do it...

We are going to write a TorqueScript function that will demonstrate how to programmatically call a function as follows:

  1. Open the game/scripts/server/game.cs script file and add the following code to the bottom:

    function getUniqueID1(%idType)
    {
       // Determine the function to use based on the
       // passed-in ID type
       switch$ (%idType)
       {
          case 0:
             %function = "generateUUID";
          
          case 1:
             %function = "getRealTime";
          
          case 2:
             %function = "getSimTime";
          
          default:
             %function = "generateUUID";
       }
       
       // Call the function
       %result = call(%function);
    
       // Return the result   
       return "Your unique ID is: " @ %result;
    }
  2. Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

    getUniqueID1(0);
    

    In the console we will see the following output:

    ==>getUniqueID1(0);
    Your unique ID is: 7be5014c-7d5a-11e1-b801-e57f4eda779f
    
  3. We can try another run by entering a new command at the bottom of the screen:

    getUniqueID1(1);
    

    In the console we will see the following output:

    ==>getUniqueID1(1);
    Your unique ID is: 294298553
    

How it works...

Our example code chooses a particular function name to use based on a parameter passed into our function. It then uses the standard call() function to call the function by name, do something with the result, and return the result to the caller. The call() function has the following form:

result = call( function, args… );

Here the function parameter is the console function we want to call, and the args… parameter is actually an optional set of arguments to pass into function. The call() function returns the result denoted by the result parameter of the function call, if any.

If the given function does not exist, an empty string is returned. No error will be output to the console.

There's more...

If we are unsure whether a particular function exists, we can use the isFunction() method. It has the following form:

result = isFunction( functionName );

Here the functionName parameter is the function name to search for, and result is either true or false, depending on whether the function exists or not.

See also

  • Using call() to call a variable method on a SimObject instance with arguments

 

Using script arrays as dictionaries


Arrays are a common form of data structure found in nearly all programming languages. When working with an array, you start at a zero-based index and keep incrementing the index until the array is full. TorqueScript arrays are a little different, in that their index need not be consecutive, nor do they even need to be a number. In this recipe, we will learn how to use TorqueScript arrays to store and retrieve arbitrarily indexed data, sometimes referred to as dictionaries.

Getting ready

We will be adding a new TorqueScript function to a project based on the Torque 3D Full template and try it out using the Empty Terrain level. If you haven't already, use the Torque Project Manager (Project Manager.exe) to create a new project from the Full template. It will be found under the My Projects directory. Then start up your favorite script editor, such as Torsion, and let's get going!

How to do it...

We are to write a TorqueScript function that will demonstrate how to use arrays as dictionaries as follows:

  1. Open the game/scripts/server/game.cs script file and add the following code to the bottom:

    function getWeaponDamage(%weaponName)
    {
       // Begin by defining some damage amounts for
       // various weapons.  Normally we would set this
       // up at the beginning of the game rather than
       // every time this function is called.
       $WeaponDamage["pistol"] = 2;
       $WeaponDamage["rifle"] = 6;
       $WeaponDamage["rocket"] = 12;
       
       // Look up the damage amount
       %damage = $WeaponDamage[%weaponName];
       
       // Check if the damage amount was found.  If not
       // then set it to some default value.
       if (%damage $= "")
       {
          // The damage was an empty string and was
          // therefore not found in our array.  Set it
          // to a default value.
          %damage = 1;
       }
       
       return %damage;
    }
  2. Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

    getWeaponDamage("pistol");
    

    In the console we will see the following output:

    ==>getWeaponDamage("pistol");
    2
    
  3. We can try another run with a different weapon by entering the following new command at the bottom of the screen:

    getWeaponDamage("rocket");
    

    In the console we will see the following output:

    ==>getWeaponDamage("rocket");
    12
    
  4. We will also try a weapon that is not in the array as follows:

    getWeaponDamage("knife");
    

    In the console we will see the following output:

    ==>getWeaponDamage("knife");
    1
    

How it works...

The code example first sets up a global array so we have some data to play with. Normally this sort of set up would be outside of the function we're using. Our global array is special in that each index is actually a string.

The getWeaponDamage() function then attempts to retrieve the given weapon from the array as follows:

   // Look up the damage amount
   %damage = $WeaponDamage[%weaponName];

If an empty string is returned, we know that the weapon name was not found in the array. We then provide some default damage value. If the weapon name was found in the array, we use its damage value.

Behind the scenes, TorqueScript is not actually creating any sort of traditional array to hold these values. When you use square brackets ([,]) to denote an array index, what actually happens is TorqueScript appends the index to the name of an array. So using our previous example, the definition for weapon damage of the rifle looks like the following:

$WeaponDamage["rifle"] = 6;

But what TorqueScript is doing behind the scenes looks like the following:

$WeaponDamagerifle = 6;

You can test this out yourself by typing the following line into the console after running our getWeaponDamage() function at least once:

echo($WeaponDamagerifle);

If you do this you will see 6 printed to the console as expected.

So in the end, accessing the index of an array is just a string lookup into the TorqueScript variable table.

See also

  • Using ArrayObject and custom script sorting callbacks

 

Using ArrayObject and custom script sorting callbacks


The ArrayObject class provides a true key/value pair dictionary in TorqueScript. It allows for easy searching and counting of key/value pairs and can optionally remove duplicates by either key or value. An ArrayObject class may also sort by key or value using standard algorithms, but it also supports custom sorting using script callbacks. In this recipe, we will see how to set up a custom sort callback that will be used by an ArrayObject instance.

Getting ready

We will be adding a new TorqueScript function to a project based on the Torque 3D Full template and try it out using the Empty Terrain level. If you haven't already, use the Torque Project Manager (Project Manager.exe) to create a new project from the Full template. It will be found under the My Projects directory. Then start up your favorite script editor, such as Torsion, and let's get going!

How to do it...

We are going to write a TorqueScript function that will demonstrate how to custom sort an ArrayObject as follows:

  1. Open the game/scripts/server/game.cs script file and add the following code to the bottom:

    function sortArrayObject1()
    {
       // Create a new ArrayObject
       %array = new ArrayObject();
       
       // Fill the array with any SceneObject in the
       // MissionGroup SimGroup.
       foreach (%obj in MissionGroup)
       {
          if (%obj.isInNamespaceHierarchy("SceneObject"))
          {
             // This is a SceneObject so add it to the array
             %array.add(%obj.getId(), %obj.getClassName());
          }
       }
       
       // Fill the array with any SceneObject in the
       // MissionCleanup SimGroup.
       foreach (%obj in MissionCleanup)
       {
          if (%obj.isInNamespaceHierarchy("SceneObject"))
          {
             // This is a SceneObject so add it to the array
             %array.add(%obj.getId(), %obj.getClassName());
          }
       }
    
       // Sort the array's keys in ascending order using our
       // custom sort function.  This function will sort
       // all objects according to their world y position.
       %array.sortfk(arraySortFunction1);
       
       // Now output the list of objects to the console
       %count = %array.count();
       for (%i=0; %i<%count; %i++)
       {
          // The key holds the SimObject ID
          %key = %array.getKey(%i);
          
          // The value hold the class name
          %value = %array.getValue(%i);
          
          // Get the object's position
          %pos = %key.getPosition();
          
          // Print to the console
          echo(%value @ " [" @ %key @ "] Y Position: " @ %pos.y);
       }
    }
    
    // Our array sort function.  %a and %b hold the
    // SimObject ID's of the objects to sort (the keys).
    function arraySortFunction1(%a, %b)
    {
       %posA = %a.getPosition();
       %posB = %b.getPosition();
       
       if (%posA.y < %posB.y)
          return -1;
       else if (%posA.y > %posB.y)
          return 1;
       else
          return 0;
    }
  2. Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

    sortArrayObject1();
    

    In the console we will see the following output:

    ==>sortArrayObject1();
    TerrainBlock [4277] Y Position: -1024
    ScatterSky [4276] Y Position: 0
    Camera [4289] Y Position: 0.250583
    Player [4290] Y Position: 5.14696
    

How it works...

The example code begins by creating the ArrayObject instance that will be used to store some objects. It then steps through all the MissionGroup and MissionCleanup objects and stores any SceneObject instances it finds onto the array. The SimObject ID of the object is used as the key, and the class name of the object is used as the value.

With the array populated, we do an ascending sort using our custom sorting function, arraySortFunction1() as follows:

   // Sort the array's keys in ascending order using our
   // custom sort function.  This function will sort
   // all objects according to their world y position.
   %array.sortfk(arraySortFunction1);

The code then steps through the sorted array and prints the results to the console.

The critical component here is our custom sorting function, arraySortFunction1(). Each time the function is called, it passes two items to compare. As we're doing a key-based sort, the key of each item is passed to our sort function. When we created the array we placed SimObject ID of each SceneObject instance into the key, so we may now use the key to retrieve information about the SceneObject instance. In our case we get the world position of each object as follows:

   %posA = %a.getPosition();
   %posB = %b.getPosition();

The rule for the sorting function is that if item A is less than item B then return a value of -1. If item A is greater than B then return a value of 1. And if items A and B are equal, return a value of 0. It is up to our sorting function to determine what makes item A lesser than or greater than item B. In our example we're using world Y position of each object as follows:

   if (%posA.y < %posB.y)
      return -1;
   else if (%posA.y > %posB.y)
      return 1;
   else
      return 0;

We then end up with a nicely sorted list from the lowest Y position to highest Y position that we output to the console.

See also

  • Using script arrays as dictionaries

 

Scheduling SimObject methods


Scheduling allows for an action to occur sometime in the future. In TorqueScript we may have a schedule to trigger a method of an object after a specified amount of time has passed. In this recipe, we will learn how to schedule method of a SimObject instance.

Getting ready

We will be adding a new TorqueScript function to a project based on the Torque 3D Full template and try it out using the Empty Terrain level. If you haven't already, use the Torque Project Manager (Project Manager.exe) to create a new project from the Full template. It will be found under the My Projects directory. Then start up your favorite script editor, such as Torsion, and let's get going!

How to do it...

We are going to write a TorqueScript function that will demonstrate how to schedule a method of a SimObject instance as follows:

  1. Open the game/scripts/server/game.cs script file and add the following code to the bottom:

    function scheduleObjectMethod1()
    {
       // Create a new ScriptObject that will have a
       // method scheduled
       new ScriptObject(MyScheduleObject);
       
       // Schedule a method to execute 250ms from now.  This
       // method is found in the MyScheduleObject namespace and
       // takes one parameter: the time the schedule was started.
       // We store the returned event ID in case we want to cancel
       // the schedule before it calls the method.
       MyScheduleObject.eventId = 
          MyScheduleObject.schedule(250, myMethod, getRealTime());
    }
    
    // Our function that will be executed by the object's
    // schedule.
    function MyScheduleObject::myMethod(%this, %startTime)
    {
       // Get the current time
       %currentTime = getRealTime();
       
       // Calculate the time delta
       %delta = %currentTime - %startTime;
       
       // Output to the console
       echo("Event ID " @ %this.eventId @ " sat for " 
            @ %delta @ "ms before it was called");
    }
  2. Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

    scheduleObjectMethod1();
    

    In the console we will see the following output:

    ==>scheduleObjectMethod1();
    Event ID 1 sat for 256ms before it was called
    

How it works...

The example code begins by creating a ScriptObject instance with a globally unique name of MyScheduleObject. This name is later used as the namespace for the method that will be scheduled. The example code then schedules the method using the SimObject schedule() method as follows:

   MyScheduleObject.eventId = 
      MyScheduleObject.schedule(250, myMethod, getRealTime());

The schedule() method has the following form:

eventID = SimObject.schedule( time, method, args… );

Here the time parameter is the delay in milliseconds before the method is executed, the method parameter is the name of the method on the SimObject instance to execute, and the args… parameter is actually a variable number of optional arguments that are passed to given method. The eventID value of the scheduled event is returned by schedule(), so that we may cancel the schedule before it is invoked.

The actual time it takes for the scheduled method to execute may be greater than the delay time requested. This can be due to a number of factors, such as current engine load. However, the delay will never be less than the requested time.

There's more...

If the SimObject instance is deleted before the schedule has fired, the schedule will automatically be canceled. It is also possible to manually cancel a schedule by using the cancel() function. This function has the following form:

cancel( eventID ); 

Here the eventID parameter is the value returned by the schedule() method of the SimObject instance.

See also

  • Scheduling functions

 

Scheduling functions


Scheduling allows for an action to occur sometime in the future. In TorqueScript we may have a schedule invoke a function after a specified amount of time has passed. In this recipe we will learn how to schedule a function.

Getting ready

We will be adding a new TorqueScript function to a project based on the Torque 3D Full template and try it out using the Empty Terrain level. If you haven't already, use the Torque Project Manager (Project Manager.exe) to create a new project from the Full template. It will be found under the My Projects directory. Then start up your favorite script editor, such as Torsion, and let's get going!

How to do it...

We are going to write a TorqueScript function that will demonstrate how to schedule a function as follows:

  1. Open the game/scripts/server/game.cs script file and add the following code to the bottom:

    function scheduleFunction1()
    {
       // Schedule a function to execute 250ms from now.  This
       // function takes one parameter: the time the schedule
       // was started.  We store the returned event ID in case
       // we want to cancel the scedule before it calls the
       // function.
       $MyEventID = 
          schedule(250, 0, myScheduledFunction, getRealTime());
    }
    
    // Our function that will be executed by the schedule.
    function myScheduledFunction(%startTime)
    {
       // Get the current time
       %currentTime = getRealTime();
       
       // Calculate the time delta
       %delta = %currentTime - %startTime;
       
       // Output to the console
       echo("Event ID " @ $MyEventID @ " sat for " 
            @ %delta @ "ms before it was called");
    }
  2. Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

    scheduleFunction1();
    

    In the console we will see the following output:

    ==>scheduleFunction1();
    Event ID 1 sat for 256ms before it was called
    

How it works...

The example code schedules our MyScheduleFunction using the schedule() function that has the following form:

eventID = schedule( time, SimObjectID, function args… );

Here, the time parameter is the delay in milliseconds before the function is executed. The SimObjectID parameter is an optional object to invoke this function on (or 0 if no object), the function parameter is the name of the function to execute, and the args… parameter is actually a variable number of optional arguments that are passed to the given function. The eventID parameter of the scheduled event is returned by schedule() so that we may cancel the schedule before it is invoked.

If a SimObject ID is provided to the schedule() function, the schedule essentially operates as if we used the SimObject schedule() method. If the SimObject ID is left as 0 then the function is invoked on its own.

The actual time taken for the scheduled method to execute may be greater than the delay time requested. This can be due to a number of factors, such as current engine load. However, the delay will never be less than the requested time.

There's more...

It is possible to manually cancel a schedule by using the cancel() function. This function has the following form:

cancel( eventID ); 

Here the eventID parameter is the value returned by the schedule() function.

See also

  • Scheduling SimObject methods

 

Activating and deactivating a package


TorqueScript packages allow us to encapsulate functions and SimObject methods into chunks that may be turned on and off. Packages are often used to modify the behavior of standard code, such as for a particular game play type. In this recipe, we will learn how to create a package and then how to activate and deactivate it.

Getting ready

We will be adding a new TorqueScript function to a project based on the Torque 3D Full template and try it out using the Empty Terrain level. If you haven't already, use the Torque Project Manager (Project Manager.exe) to create a new project from the Full template. It will be found under the My Projects directory. Then start up your favorite script editor, such as Torsion, and let's get going!

How to do it...

We are going to write a TorqueScript function that will demonstrate how to work with packages as follows:

  1. Open the game/scripts/server/game.cs script file and add the following code to the bottom:

    function printStuff1()
    {
       echo("Non-packaged printStuff1()");
       
       // Print out four random numbers to the console
       for (%i=0; %i<4; %i++)
       {
          echo("Number " @ %i @ ": " @ getRandom());
       }
    }
    
    //Start the definition of our package
    package ChangeItUp
    {
       function printStuff1()
       {
          echo("Packaged printStuff1()");
          
          // This version of the function just counts to 10
          %counter = "";
          for (%i=1; %i<=10; %i++)
          {
             %counter = %counter SPC %i;
          }
          
          echo(%counter);
       }
    };
    
    // This function will test everything out
    function unitTest1()
    {
       // Invoke the non-packaged function
       printStuff1();
       
       // Activate the package
       activatePackage(ChangeItUp);
       
       // Invoke what should be the packaged function
       printStuff1();
       
       // Deactivate the package
       deactivatePackage(ChangeItUp);
       
       // Now we should be back to the non-packaged
       // function
       printStuff1();
    }
  2. Start up our game under the My Projects directory and load the Empty Terrain level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:

    unitTest1();
    

    In the console we will see the following output:

    ==>unitTest1();
    Non-packaged printStuff1()
    Number 0: 0.265364
    Number 1: 0.96804
    Number 2: 0.855327
    Number 3: 0.473076
    Packaged printStuff1()
     1 2 3 4 5 6 7 8 9 10
    Non-packaged printStuff1()
    Number 0: 0.982307
    Number 1: 0.639691
    Number 2: 0.278508
    Number 3: 0.888561
    

How it works...

The example code first defines an ordinary function, printStuff1(). It just prints out four random numbers to the console. Then the code defines a package named ChangeItUP. A package is defined by using the package keyword followed by the name of the package. Any function or method that is defined within the curly braces of the package will override the same regular function or method when the package is activated. When a package is deactivated, the overridden functions and methods go back to their regular versions.

The unitTest1() function demonstrates this in action. It first invokes the regular printStuff1() function. Then the ChangeItUp package is activated. Now when printStuff1() is called, it is the one defined within the package that is used. Finally, the package is deactivated and the regular printStuff1() function is called.

There's more...

The order in which the packages are activated and deactivated is important. When multiple packages are activated we have what is called the package stack. If the same function or method is defined across multiple packages and all of those packages are activated, the last package that was activated will be where the function or method is called.

If a package in the middle of the stack is deactivated, then all packages that were activated later in the stack (following the one we are about to deactivate) will also be deactivated.

To get a view of the current package stack use the getPackageList() function. This function returns a space-delimited list of all of the currently active packages, and in the order in which they were activated.

About the Author

  • David Wyand

    David Wyand has been using GarageGames' Torque Game Engine for the past 10 years. Among his other interestes are 3D graphics applications, computer networking and Artificial Intelligence for computer games.

    Browse publications by this author
Book Title
Access this book, plus 7,500 other titles for FREE
Access now