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
orSimGroup
collectionGetting a random object from a
SimSet
orSimGroup
collectionFinding an object in a
SimSet
orSimGroup
collection using its internal nameExecuting a method on a
SimSet
orSimGroup
collectionCreating a new
SimObject
instanceCreating a new internal name only
SimObject
instanceCreating a new
Datablock
objectCreating a new singleton
Extending a
SimObject
instance using theclass
propertyUsing a variable to access methods or properties of a
SimObject
instanceUsing
call()
to call a variable method on aSimObject
instance with argumentsUsing
call()
to call a variable function with argumentsUsing script arrays as dictionaries
Using
ArrayObject
and custom script sorting callbacksScheduling
SimObject
methodsScheduling functions
Activating and deactivating a package
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!
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.
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.
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:
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); }
Start up our game under the
My Projects
directory and load theEmpty 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
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.
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.
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.
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.
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.
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!
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:
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); }
Start up our game under the
My Projects
directory and load theEmpty 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
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 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.
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.
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!
We are going to write a TorqueScript function that will demonstrate how to retrieve components of a variable using the special accessors as follows:
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); }
Start up our game under the
My Projects
directory and load theEmpty 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
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.
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.
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.
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!
We are going to write a TorqueScript function that will
iterate through the objects of a SimSet
or SimGroup
collection as follows:
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()); } }
Start up our game under the
My Projects
directory and load theEmpty 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
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.
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!).
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.
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!
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:
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()); }
Start up our game under the
My Projects
directory and load theEmpty 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
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.
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!
We are going to write a TorqueScript function that will demonstrate how to find a SimObject
instance using its internal name as follows:
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(); }
Start up our game under the
My Projects
directory and load theEmpty 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
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.
Let's look at some additional ways to search for a SimObject
class in a SimSet
or SimGroup
collection.
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 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.
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.
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.
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!
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:
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); }
Start up our game under the
My Projects
directory and load theEmpty 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()
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.
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()
.
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.
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();
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 are a number of different options when it comes to creating a SimObject
derived class. Let's take a look at them.
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:
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.
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.
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
.
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.
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]);
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.
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 ... };
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.
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; };
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 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.
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.
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.
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.
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.
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.
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; };
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.
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!
We are going to write a TorqueScript function that will demonstrate how to extend a SimObject
instance using its class
property as follows:
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"); }
Start up our game under the
My Projects
directory and load theEmpty 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
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 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.
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.
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.
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:
Optional globally unique name is used as a namespace
Optional
class
property namespaceOptional
superClass
property namespaceDirect C++ class
Parent C++ class
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.
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.
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.
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!
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:
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(); } }
Start up our game under the
My Projects
directory and load theEmpty 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
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();
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.
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!
We are going to write a TorqueScript function that will demonstrate how to programmatically call a
SimObject
method as follows:
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); } } } }
Start up our game under the
My Projects
directory and load theEmpty 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
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.
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.
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.
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!
We are going to write a TorqueScript function that will demonstrate how to programmatically call a function as follows:
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; }
Start up our game under the
My Projects
directory and load theEmpty 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
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
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.
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.
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.
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!
We are to write a TorqueScript function that will demonstrate how to use arrays as dictionaries as follows:
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; }
Start up our game under the
My Projects
directory and load theEmpty 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
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
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
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.
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.
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!
We are going to write a TorqueScript function that will demonstrate how to custom sort an ArrayObject
as follows:
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; }
Start up our game under the
My Projects
directory and load theEmpty 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
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.
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.
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!
We are going to write a TorqueScript function that will demonstrate how to schedule a method of a SimObject
instance as follows:
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"); }
Start up our game under the
My Projects
directory and load theEmpty 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
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.
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.
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.
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!
We are going to write a TorqueScript function that will demonstrate how to schedule a function as follows:
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"); }
Start up our game under the
My Projects
directory and load theEmpty 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
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.
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.
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.
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!
We are going to write a TorqueScript function that will demonstrate how to work with packages as follows:
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(); }
Start up our game under the
My Projects
directory and load theEmpty 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
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.
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.