Plastic Gem #16: Triggers - The Good, the Bad, and the Ugly
by Paul Dana · 07/01/2008 (6:04 am) · 3 comments
Download Code File

Plastic Gem # 16 : Triggers: The Good, the Bad, and the Ugly
Difficulty: Intermediate
Before following this gem, you must, at minimum, follow the instructions in Gem #1.
Hello again from Plastic Games, and welcome to week three of Gem-A-Day. This week I want to hand it over to Jason Sharp, our 3D modeler, animator, and technical artist. Over the years Jason has created an amazing array of game objects using nothing more than a StaticShape, my thread enhancements, and his ingenuity.
There is almost nothing you can't do with Torque animation threads. They are amazingly powerful. This is the core reason why my thread enhancements are so useful. They expose the incredibly powerful animation thread system to script code, allowing you amazing freedom. You can add the script enhancments to your code base by following the instructions in Gem #2 (and Gem #3 as well if you want ease...which you do).
Before I turn it over to Jason I need to explain something we always tend to use with Jason's wonder-shapes, namely Triggers.
The wonderful thing about Triggers....
...is Triggers are wonderful things!
A Trigger is an invisible box you can place in your mission. It will show up in the Mission Editor so you can create and position it, but in game it is invisible.
When a player or a vehicle enters that box a script callback is executed. When a player or vehicle leaves the box a different sript callback is executed. Finally, whenever there is one or more players or vehicles inside the box, a third script callback function can be called at regular intervals. Clearly this is a powerful feature that allows you to add alot of game functionality entirely from script.
This gem shows you how to use Triggers and discusses what is good about them and what isn't.
An example using the Grandfather Clock.

Let's pretend we want to make a clock that will stop when the player enters a trigger area, and stop when the player leaves that area. There are many ways to do this which we will cover in this gem, but first we need a Grandfather Clock. Let's make a mission with a clock in it we can use for this.
Place a grandfather clock using the same method described in Gem #1. Use the World Editor Inspector to give this clock the name TriggerClock. Save this mission and quit out so we can add some code to make our clock stop. I have provided a sample Trigger that will do this in the most simplistic and straight forward way, so we can see that first.
1) Unzipping the files
Remember you must first follow the instructions in Gem #1 to get the grandfather clock shape and the clock.cs script file. After doing that, unzip the pg16_triggers.zip file provided with this resource. Copy the echoTrigger.cs file to your ~/server/scripts folder, which is where your clock.cs file should be.
2) Executing the echoTrigger.cs script file
Edit the file ~/server/scripts/game.cs. Find the function called onServerCreated(). In there you will see lines of code that execute scripts. Here we need to execute the script we added. After these lines:
3) The example EchoTrigger.
By default Torque provides a datablock and script method callback definitions for one type of trigger, DefaultTrigger. I created the EchoTrigger by copying the DefaultTrigger code and adding the code to start and stop the pendulum on the TriggerClock and the echo statements (and fixing a typo in the comments in the ::onTriggerTick() method).
Let's take a look at the code in the echoTrigger.cs file. First we define the trigger datablock.
One we define the above code then this type of Trigger becomes placeable from the Mission Editor.
Next is the definition of the callbacks that are called when a player or vehicle enters/leaves the trigger:
Notice how each method calls on it's Parent. You are not required to do that in your callback, but if you do then the C++ code will redirect this callback to any objects in the same group as the trigger. More on this later.
Notice that we stop thread zero (the pendulum thread) for our grandfather clock in the enter callback and play it again in the leave callback.
Notice also the echo statements we added.
Finally we define the callback to call periodically whenever one or more players or vehicles intersect with the Trigger box.
Again notice that the script can call on the Parent to have this callback redirected and the echo statement we added.
4) Placing a Trigger in a Mission.
Placing a Trigger into a mission is similar to placing a shape as defined in Gem #1, but the steps are a little different. As with placing a shape you must load your mission and hit F11 to enter the Editor mode.. As before you want to have the center of the screen looking at the place you want to place something. As before you should select "World Editor Creator" from the Window menu and a tree of options will display in the window, but this time we want to open the Mission Objects section, not the Shapes section.
In the Mission Objects section will be a section labled Mission. Open that and you will see one of the choices is Trigger.
Make sure the center of screen shows the place you want to place the Trigger. Choose a place close enough to the grandfather clock you placed so that you can see the clock from the trigger area. (It does not matter if the clock is inside the trigger area or not.)
Click on Trigger and a dialog box will appear. Type any name you like in the Name field (we wont be using the name) and hit the OK button.
A small wire box should appear in the center of the screen. You have just placed a trigger.
Now let's make it bigger. Choose "World Editor Inspector" from the Window menu and click on the trigger to select it and the Trigger fields should appear in the bottom half of the editing window. Find the field marked "scale" where the value will read "1 1 1". Change the value to read "3 3 3" and hit the apply button and you should see that the Trigger has gotten larger!
Now test the Trigger by walking into the box area. You can either hit F11 to toggle off the edit mode and just walk to where yoy know the Trigger is located, or you can walk around while in Edit mode by holding the right mouse button down to turn and such.
When you are inside the Trigger area the clock pendulum should stop swinging. When you leave the Trigger area the clock should start the pendulum swinging animation over again.
Also you can hit the tilde key (~) to drop the console and see the results of the echo() statements while you walked into and out of the Trigger area.
Don't exit the mission after testing, we are not done yet!
5) The Good.
Clearly Triggers are useful, even if you had to define a new Trigger type for each use as we did for the echoTrigger. But you don't always have to! As mentioned before the Trigger can be asked to redirect the callback to other objects. If the reason you want a Trigger is to get some behavior from a scripted object when the player gets close, then you can simply use the DefaultTrigger already defined by Torque. Let's rework our example to do things that way.
While still in Edit mode, delete the EchoTrigger you added. Select it and hit the delete key (or choose Cut from the Edit menu).
OK so the idea is that we want a DefaultTrigger and our grandfather clock in a group by themselves so that only the clock gets the redirected callback.
First we need to create a group. Then we need to drag our clock into this group. Next we need to tell the editor that new objects should be placed into our group. Finally we can place a DefaultTrigger right into our group.
While still in Edit mode, choose World Editor Creator from the Window menu. Open the Mission Objects section and from there open the System section. You will see a choice called SimGroup. Click that and a new SimGroup will be added to the bottom of the Mission Group.
Choose World Editor Inspector from the Window menu. Find the new SimGroup at the bottom of the Mission Group. You should see the clock you placed earlier also near the bottom of the list.
Drag the clock into the group. Now the clock should be inside the group which should show with a plus [+] by its name.
Now hold down ALT and click the group. It should now render green, indicating new objects will be placed into this group.
Now place a DefaultTrigger near the clock and resize it the same way you did for the EchoTrigger. Notice that the Trigger should automatically appear into the group you made.
Save the mission while in Edit mode and quit the game so we can add some code to respond to the trigger.
6) The Bad
Here is where the bad part comes in. While it is true that the DefaultTrigger will redirect the callbacks, it calls on the object namespace. Let's complete the example of using a DefaultTrigger to see why this is not so good. Add the following code somewhere it will execute. You can add it to the end of the clock.cs file if you like.
Execute the misison you have been using for these tests and, once again the clock pendulum should stop swinging when you enter the trigger, etc. So the code works. It is a little bit better because now it doesn't matter if the clock is named TriggerClock or if we wanted this to happen for three clocks. In fact the neither the clock nor the trigger nor the group really has to have a name at all. As long as they clocks are in the group with the trigger the callback will be redirected.
However the code is hardly ideal. Since the redirection code calls on the object namespace we have to handle this at the StaticShape level, ie this one function has to work for all the different types of Static Shapes we define. There is a better way and next Gem we will cover that.
7) The Ugly
There is also another unpleasant fact about Triggers that you have actaully already seen. Did you notice that when you resized the triggers they appeared to move? That is because the default polyhedron for the trigger defines the origin (0,0,0) to be the bottom corner of the trigger which is less than convenient.
Lucikly there is a fix for this ugliness. When you place a Trigger and the dialog box appears don't accept the default polyhedron which begins with the value "0 0 0 ...".
To get a box with the origin in the center of the box, replace that with "-0.5 0.5 -0.5".
To get a box with the origin in the center of the bottom face of the box, replace that with "-0.5 0.5 0".
You can even search the scripts for the word "polyhedron" and you will see the code in the mission editor where it sets up the default polyhedron and just change the values there once and for all!
The Next Gem
That's all for this gem. We have reviewed why Triggers are awesome, but how they have a bad and ugly side as well. Next gem we show how to improve upon the Trigger code and and how to create auto-managed Triggers with the TriggerManager class.

Plastic Gem # 16 : Triggers: The Good, the Bad, and the Ugly
Difficulty: Intermediate
Before following this gem, you must, at minimum, follow the instructions in Gem #1.
Hello again from Plastic Games, and welcome to week three of Gem-A-Day. This week I want to hand it over to Jason Sharp, our 3D modeler, animator, and technical artist. Over the years Jason has created an amazing array of game objects using nothing more than a StaticShape, my thread enhancements, and his ingenuity.
There is almost nothing you can't do with Torque animation threads. They are amazingly powerful. This is the core reason why my thread enhancements are so useful. They expose the incredibly powerful animation thread system to script code, allowing you amazing freedom. You can add the script enhancments to your code base by following the instructions in Gem #2 (and Gem #3 as well if you want ease...which you do).
Before I turn it over to Jason I need to explain something we always tend to use with Jason's wonder-shapes, namely Triggers.
The wonderful thing about Triggers....
...is Triggers are wonderful things!
A Trigger is an invisible box you can place in your mission. It will show up in the Mission Editor so you can create and position it, but in game it is invisible.
When a player or a vehicle enters that box a script callback is executed. When a player or vehicle leaves the box a different sript callback is executed. Finally, whenever there is one or more players or vehicles inside the box, a third script callback function can be called at regular intervals. Clearly this is a powerful feature that allows you to add alot of game functionality entirely from script.
This gem shows you how to use Triggers and discusses what is good about them and what isn't.
An example using the Grandfather Clock.

Let's pretend we want to make a clock that will stop when the player enters a trigger area, and stop when the player leaves that area. There are many ways to do this which we will cover in this gem, but first we need a Grandfather Clock. Let's make a mission with a clock in it we can use for this.
Place a grandfather clock using the same method described in Gem #1. Use the World Editor Inspector to give this clock the name TriggerClock. Save this mission and quit out so we can add some code to make our clock stop. I have provided a sample Trigger that will do this in the most simplistic and straight forward way, so we can see that first.
1) Unzipping the files
Remember you must first follow the instructions in Gem #1 to get the grandfather clock shape and the clock.cs script file. After doing that, unzip the pg16_triggers.zip file provided with this resource. Copy the echoTrigger.cs file to your ~/server/scripts folder, which is where your clock.cs file should be.
2) Executing the echoTrigger.cs script file
Edit the file ~/server/scripts/game.cs. Find the function called onServerCreated(). In there you will see lines of code that execute scripts. Here we need to execute the script we added. After these lines:
exec("./crossbow.cs");
exec("./environment.cs");add a line like this// > pg triggers
exec("./echoTrigger.cs");
// < pg triggers3) The example EchoTrigger.
By default Torque provides a datablock and script method callback definitions for one type of trigger, DefaultTrigger. I created the EchoTrigger by copying the DefaultTrigger code and adding the code to start and stop the pendulum on the TriggerClock and the echo statements (and fixing a typo in the comments in the ::onTriggerTick() method).
Let's take a look at the code in the echoTrigger.cs file. First we define the trigger datablock.
datablock TriggerData(EchoTrigger)
{
// The period is value is used to control how often the console
// onTriggerTick callback is called while there are any objects
// in the trigger. The default value is 100 MS.
tickPeriodMS = 1000; // 1000 = called every second...
};One we define the above code then this type of Trigger becomes placeable from the Mission Editor.
Next is the definition of the callbacks that are called when a player or vehicle enters/leaves the trigger:
function EchoTrigger::onEnterTrigger(%this,%trigger,%obj)
{
// This method is called whenever an object enters the %trigger
// area, the object is passed as %obj. The default onEnterTrigger
// method (in the C++ code) invokes the ::onTrigger(%trigger,1) method on
// every object (whatever it's type) in the same group as the trigger.
Parent::onEnterTrigger(%this,%trigger,%obj);
// stop the pendulum thread on the trigger clock
TriggerClock.stopThread(0);
echo("EchoTrigger::onEnterTrigger");
}
function EchoTrigger::onLeaveTrigger(%this,%trigger,%obj)
{
// This method is called whenever an object leaves the %trigger
// area, the object is passed as %obj. The default onLeaveTrigger
// method (in the C++ code) invokes the ::onTrigger(%trigger,0) method on
// every object (whatever it's type) in the same group as the trigger.
Parent::onLeaveTrigger(%this,%trigger,%obj);
// play the pendulum thread on the trigger clock again...
TriggerClock.playThread(0);
echo("EchoTrigger::onLeaveTrigger");
}Notice how each method calls on it's Parent. You are not required to do that in your callback, but if you do then the C++ code will redirect this callback to any objects in the same group as the trigger. More on this later.
Notice that we stop thread zero (the pendulum thread) for our grandfather clock in the enter callback and play it again in the leave callback.
Notice also the echo statements we added.
Finally we define the callback to call periodically whenever one or more players or vehicles intersect with the Trigger box.
function EchoTrigger::onTickTrigger(%this,%trigger)
{
// This method is called every tickPerioMS, as long as any
// objects intersect the trigger. The default onTriggerTick
// method (in the C++ code) invokes the ::onTriggerTick(%trigger) method on
// every object (whatever it's type) in the same group as the trigger.
// You can iterate through the objects in the list by using these
// methods:
// %trigger.getNumObjects();
// %trigger.getObject(n);
Parent::onTickTrigger(%this,%trigger);
echo("EchoTrigger::onTickTrigger (" SPC %trigger.getNumObjects() SPC "obj(s) in the trigger)");
}Again notice that the script can call on the Parent to have this callback redirected and the echo statement we added.
4) Placing a Trigger in a Mission.
Placing a Trigger into a mission is similar to placing a shape as defined in Gem #1, but the steps are a little different. As with placing a shape you must load your mission and hit F11 to enter the Editor mode.. As before you want to have the center of the screen looking at the place you want to place something. As before you should select "World Editor Creator" from the Window menu and a tree of options will display in the window, but this time we want to open the Mission Objects section, not the Shapes section.
In the Mission Objects section will be a section labled Mission. Open that and you will see one of the choices is Trigger.
Make sure the center of screen shows the place you want to place the Trigger. Choose a place close enough to the grandfather clock you placed so that you can see the clock from the trigger area. (It does not matter if the clock is inside the trigger area or not.)
Click on Trigger and a dialog box will appear. Type any name you like in the Name field (we wont be using the name) and hit the OK button.
A small wire box should appear in the center of the screen. You have just placed a trigger.
Now let's make it bigger. Choose "World Editor Inspector" from the Window menu and click on the trigger to select it and the Trigger fields should appear in the bottom half of the editing window. Find the field marked "scale" where the value will read "1 1 1". Change the value to read "3 3 3" and hit the apply button and you should see that the Trigger has gotten larger!
Now test the Trigger by walking into the box area. You can either hit F11 to toggle off the edit mode and just walk to where yoy know the Trigger is located, or you can walk around while in Edit mode by holding the right mouse button down to turn and such.
When you are inside the Trigger area the clock pendulum should stop swinging. When you leave the Trigger area the clock should start the pendulum swinging animation over again.
Also you can hit the tilde key (~) to drop the console and see the results of the echo() statements while you walked into and out of the Trigger area.
Don't exit the mission after testing, we are not done yet!
5) The Good.
Clearly Triggers are useful, even if you had to define a new Trigger type for each use as we did for the echoTrigger. But you don't always have to! As mentioned before the Trigger can be asked to redirect the callback to other objects. If the reason you want a Trigger is to get some behavior from a scripted object when the player gets close, then you can simply use the DefaultTrigger already defined by Torque. Let's rework our example to do things that way.
While still in Edit mode, delete the EchoTrigger you added. Select it and hit the delete key (or choose Cut from the Edit menu).
OK so the idea is that we want a DefaultTrigger and our grandfather clock in a group by themselves so that only the clock gets the redirected callback.
First we need to create a group. Then we need to drag our clock into this group. Next we need to tell the editor that new objects should be placed into our group. Finally we can place a DefaultTrigger right into our group.
While still in Edit mode, choose World Editor Creator from the Window menu. Open the Mission Objects section and from there open the System section. You will see a choice called SimGroup. Click that and a new SimGroup will be added to the bottom of the Mission Group.
Choose World Editor Inspector from the Window menu. Find the new SimGroup at the bottom of the Mission Group. You should see the clock you placed earlier also near the bottom of the list.
Drag the clock into the group. Now the clock should be inside the group which should show with a plus [+] by its name.
Now hold down ALT and click the group. It should now render green, indicating new objects will be placed into this group.
Now place a DefaultTrigger near the clock and resize it the same way you did for the EchoTrigger. Notice that the Trigger should automatically appear into the group you made.
Save the mission while in Edit mode and quit the game so we can add some code to respond to the trigger.
6) The Bad
Here is where the bad part comes in. While it is true that the DefaultTrigger will redirect the callbacks, it calls on the object namespace. Let's complete the example of using a DefaultTrigger to see why this is not so good. Add the following code somewhere it will execute. You can add it to the end of the clock.cs file if you like.
function StaticShape::onTrigger(%this, %trigger, %enter)
{
if (%this.getDataBlock $= "GrandfatherClock")
{
if (%enter)
%this.stopThread(0);
else
%this.playThread(0);
}
}Execute the misison you have been using for these tests and, once again the clock pendulum should stop swinging when you enter the trigger, etc. So the code works. It is a little bit better because now it doesn't matter if the clock is named TriggerClock or if we wanted this to happen for three clocks. In fact the neither the clock nor the trigger nor the group really has to have a name at all. As long as they clocks are in the group with the trigger the callback will be redirected.
However the code is hardly ideal. Since the redirection code calls on the object namespace we have to handle this at the StaticShape level, ie this one function has to work for all the different types of Static Shapes we define. There is a better way and next Gem we will cover that.
7) The Ugly
There is also another unpleasant fact about Triggers that you have actaully already seen. Did you notice that when you resized the triggers they appeared to move? That is because the default polyhedron for the trigger defines the origin (0,0,0) to be the bottom corner of the trigger which is less than convenient.
Lucikly there is a fix for this ugliness. When you place a Trigger and the dialog box appears don't accept the default polyhedron which begins with the value "0 0 0 ...".
To get a box with the origin in the center of the box, replace that with "-0.5 0.5 -0.5".
To get a box with the origin in the center of the bottom face of the box, replace that with "-0.5 0.5 0".
You can even search the scripts for the word "polyhedron" and you will see the code in the mission editor where it sets up the default polyhedron and just change the values there once and for all!
The Next Gem
That's all for this gem. We have reviewed why Triggers are awesome, but how they have a bad and ugly side as well. Next gem we show how to improve upon the Trigger code and and how to create auto-managed Triggers with the TriggerManager class.
#2
I think the resources you guys have provided are awesome. I am however having a little problem with this tutorial. I got the echo.cs to work fine but when I switched to the default trigger nothing happens.
I copied the above code to my clock.cs and my clock and trigger are in the same group, but no results.
I'm using tgea1.8
also i was wondering if there is a list, and explanation of all the commands to use in script such as "playthread"
I looked through my documentation of console objects and functions but didnt see it..
Thanks
Kevin
12/16/2008 (10:36 am)
Paul,I think the resources you guys have provided are awesome. I am however having a little problem with this tutorial. I got the echo.cs to work fine but when I switched to the default trigger nothing happens.
I copied the above code to my clock.cs and my clock and trigger are in the same group, but no results.
I'm using tgea1.8
also i was wondering if there is a list, and explanation of all the commands to use in script such as "playthread"
I looked through my documentation of console objects and functions but didnt see it..
Thanks
Kevin
#3
I do not know if there is a good list of the thread methods and what they do. Certainly we did not update any such list when we added the features such as setThreadSpeed() and etc...
01/07/2009 (7:37 am)
Kevin - did you find out how to get past this issue? We are having some other folks having issues with the TGEA 1.8 release and some of the Gems, and so we are generally attempting to update them to fix these issues.I do not know if there is a good list of the thread methods and what they do. Certainly we did not update any such list when we added the features such as setThreadSpeed() and etc...

Associate Steve Acaster
[YorkshireRifles.com]