Plastic Gem #17: Managed Triggers
by Paul Dana · 07/02/2008 (9:52 am) · 1 comments
Download Code File

Plastic Gem # 17 : Managed Triggers
Difficulty: Intermediate
Before following this gem, you must, at minimum, follow the instructions in Gem #1.
Hello again from Plastic Games, and welcome to gem 17 - the second in a two-parter about Torque Triggers. As I mentioned in the previous gem, this week I want to let Jason Sharp show you some cool stuff, but first we need to finish this small diversion into the world of Triggers.
How Triggers could be better.
Last gem we learned that, while awesome, Torque's Triggers have some flaws. Namely,
1) the existing trigger callback redirection is sent to an object's namespace (not the datablock's namespace)
2) to redirect callbacks to objects they must be in the same group as the trigger
3) the default trigger polyhedron puts the Trigger origin (0,0,0 point) in the lower left, inconvenient for scaling & positioning.
The TriggerManager class and the ManagedTrigger class solve these issues and a few others as well, namely how much of a pain it can be to edit mission files with a lots of triggers in them taking up all the space.
The ManagedTrigger class solves the issues above. It makes the assumption that the trigger only needs to redirect to a single object. In our case this is all we have ever had a need for. For info on redirecting to more than one object see the note at the end of this gem. The ManagedTrigger does not do the default redirection, but does redirect to an object you can specify. You probably will not need to use this class directly, other than as a base class for defining your own managed triggers. Instead the TriggerManager class will do the work for you.
The TriggerManager is a singleton object that manages triggers for you. You simply specify that an object should have a given type of trigger and how it should be positioned and sized. The TriggerManager will do the work for you. You access it with the Ctrl-Alt T cheat bind explained in this gem.

The example in this gem is exactly the same as in the previous gem, Gem #16. A grandfather clock that stops it's pendulum swing when you enter a trigger, and starts it again when you leave the trigger. In this case the trigger is managed for you and you only have to place the grandfather clock.
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 pg17_triggerManager.zip file provided with this resource. Copy the TriggerManager.cs and ManagedTrigger.cs files to your ~/server/scripts folder, which is where your clock.cs file should be. If you followed Gem #5 and created a plastic folder, you can place them in there instead. I keep the files in plastic/server/core.
2)Executing the TriggerManager.cs and ManagedTrigger.cs script file
If you followed Gem #5 then you can create a folder, plastic/server/core and put the files there. Edit the plastic/server/init.cs file to execute these files.
If you did not follow Gem #5, then 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) Adding code to the Grandfather Clock
Edit the clock.cs file you added as part of Gem #1. Add this code to the bottom of the file:
Notice we have defined an ::onAdd() method specifically for the Grandfather clock. This method calls the normal ::onAdd() method to do the usual clock processing. Next comes the magic! Next it calls on the TriggerManager to tell it to manage triggers for this object, and that the type of trigger to create for it is a ManagedTrigger, which is both a default managed trigger type as well as a base class. You can make your own trigger types by extending the ManagedTriggerBase class, then simply pass that value into TriggerManager.manageTrigger() as the trigger datablock to use.
Notice also we define what to do for the ENTER and LEAVE callbacks, namely to stop/start the pendulum threads as in the example in Gem #16.
4) Binding the Cheat Function
Now edit your file ~/client/scripts/default.bind.cs. Add the following code to the bottom of the file to create a ctrl-shift T cheat bind to hide/show your triggers. NOTE: In Gem #9 we used this same bind for the "pretend spawners". Since those were pretend you can delete them now. :-)
5) Trying it out
Now lets see how this works! Edit a mission and place a grandfather clock. Refer to Gem #1 for details on doing that.
Note that when you place the grandfather clock in the mission editor it *does* create a trigger for you right away. Unfortunately when you place an object in the mission editor it does not, at time of creation, have a correct position. The upshot is that when you place the clock in mission editor the Trigger created for it is over at 0,0,0 (the world origin). Woops!. This wont matter in game becuase when the clock is loaded from the mission file (.mis file) it will have the right position. To fix this issue right now we just use the cheat binds we setup.
Remember that if you press Ctrl-Alt-T it will "toggle" the triggers, meaning create or destroy them. You cannot see triggers in the mission editor until you select them or run mouse over them. You should be able to verify that a trigger is created for your clock when you toggle the triggers off and then back on again. You can move the clock around and toggle triggers on and off and then see your trigger is correctly positioned for the new clock position.
6) Why is this useful?
There are several things that are useful about this method. The obvious one is you dont need to place the triggers yourself if all you wanted was a trigger centered around some object. The next thing is that when you have a bunch of triggers in your mission it gets hard to edit. Your selecting triggers all the time when you don't mean to. With this method you simply toggle them off before editing and back on to test.
Where are these triggers anyway?
The triggers themselves are not saved with the mission. They are in the MissionCleanup group, which is the correct thing for objects created dynamically when the game is running. When you reload the mission all the triggers are dynamically created so it doesn't matter that they are not saved in the file.
What was that other file, ShapeBase.cs, all about?
The observant coder will have noticed the extra file included in the .zip file that has not been mentioned. What is that all about? The previous gem, Gem #16, showed how the regular Torque DefaultTrigger wil redirect callbacks to objects in the same group as the trigger. ManagedTriggers avoid the hassle of all that if the idea was to redirect to a single object. However - you might like using the old system better. For those times it's useful to have the extra code in the ShapeBase namespace provided in the ShapeBase.cs file provided with this gem.
That code looks like this:
Notice that what it does is redirect the callbacks to the namespace of the datablock, if the object has a datablock, and that datablock actaully has the re-direct methods. You can put this code wherever you like. You can copy it from the file I provided, and paste it into the end of your existing ~/server/scripts/shapebase.cs file. If you followed Gem #5 and made a plastic folder you can place it in the plastic/server/core file next to the TriggerManager.cs and ManagedTrigger.cs files from this gem, and then edit the plastic/server/init.cs file to make sure it gets executed.
The Next Gem
That's all for this gem. Now that we know good ways to handle triggers we can now hand it over to Jason! Next gem Jason will be introducing one of the many game objects he has built, a fan object that has threads to rotate it both horizontally and vertically, as well as a clever cyclic thread that creates the blade-spinning efffect. It had a managed trigger that would detect when the player got close which would cause the fan to turn toward the player. Also every tick of the trigger the fan would apply a force to any player within range and within a given angle of the fan. All this behavior was achieved with Jason's clever animation threads and a small bit of script code.

Plastic Gem # 17 : Managed Triggers
Difficulty: Intermediate
Before following this gem, you must, at minimum, follow the instructions in Gem #1.
Hello again from Plastic Games, and welcome to gem 17 - the second in a two-parter about Torque Triggers. As I mentioned in the previous gem, this week I want to let Jason Sharp show you some cool stuff, but first we need to finish this small diversion into the world of Triggers.
How Triggers could be better.
Last gem we learned that, while awesome, Torque's Triggers have some flaws. Namely,
1) the existing trigger callback redirection is sent to an object's namespace (not the datablock's namespace)
2) to redirect callbacks to objects they must be in the same group as the trigger
3) the default trigger polyhedron puts the Trigger origin (0,0,0 point) in the lower left, inconvenient for scaling & positioning.
The TriggerManager class and the ManagedTrigger class solve these issues and a few others as well, namely how much of a pain it can be to edit mission files with a lots of triggers in them taking up all the space.
The ManagedTrigger class solves the issues above. It makes the assumption that the trigger only needs to redirect to a single object. In our case this is all we have ever had a need for. For info on redirecting to more than one object see the note at the end of this gem. The ManagedTrigger does not do the default redirection, but does redirect to an object you can specify. You probably will not need to use this class directly, other than as a base class for defining your own managed triggers. Instead the TriggerManager class will do the work for you.
The TriggerManager is a singleton object that manages triggers for you. You simply specify that an object should have a given type of trigger and how it should be positioned and sized. The TriggerManager will do the work for you. You access it with the Ctrl-Alt T cheat bind explained in this gem.

The example in this gem is exactly the same as in the previous gem, Gem #16. A grandfather clock that stops it's pendulum swing when you enter a trigger, and starts it again when you leave the trigger. In this case the trigger is managed for you and you only have to place the grandfather clock.
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 pg17_triggerManager.zip file provided with this resource. Copy the TriggerManager.cs and ManagedTrigger.cs files to your ~/server/scripts folder, which is where your clock.cs file should be. If you followed Gem #5 and created a plastic folder, you can place them in there instead. I keep the files in plastic/server/core.
2)Executing the TriggerManager.cs and ManagedTrigger.cs script file
If you followed Gem #5 then you can create a folder, plastic/server/core and put the files there. Edit the plastic/server/init.cs file to execute these files.
If you did not follow Gem #5, then 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");and a line like this// > pg triggers
exec("./triggerManager.cs");
exec("./managedTrigger.cs");
// < pg triggers3) Adding code to the Grandfather Clock
Edit the clock.cs file you added as part of Gem #1. Add this code to the bottom of the file:
// NOTE we do this in the GrandfatherClock namespace so this
// trick is ONLY for GrandfatherClock objects...we could also
// define it in the AnalogClock namespace if we wanted this
// behavior from ALL clocks...
// Note also the slight name inversion. The callback on the trigger itself
// is called onEnterTrigger() whereas the callback on the object entering
// the trigger is onTriggerEnter()
// tell trigger manager to manage our triggers for us...
function GrandfatherClock::onAdd(%this,%obj)
{
// do normal onAdd() stuff...
AnalogClock::onAdd(%this,%obj);
// tell trigger manager to manage this trigger for us!
TriggerManager.manageTrigger(%obj, ManagedTrigger);
}
// handle ENTER...
function GrandfatherClock::onTriggerEnter(%this,%obj, %trigger)
{
// stop pendulum on first person to enter...
if (%trigger.getNumObjects() == 1)
%obj.stopThread(0); // stop the pendulum thread
}
// handle LEAVE...
function GrandfatherClock::onTriggerLeave(%this,%obj, %trigger)
{
// start pendulum on the last person to leave...
if (%trigger.getNumObjects() == 0)
%obj.playThread(0); // start pendulum thread again
}Notice we have defined an ::onAdd() method specifically for the Grandfather clock. This method calls the normal ::onAdd() method to do the usual clock processing. Next comes the magic! Next it calls on the TriggerManager to tell it to manage triggers for this object, and that the type of trigger to create for it is a ManagedTrigger, which is both a default managed trigger type as well as a base class. You can make your own trigger types by extending the ManagedTriggerBase class, then simply pass that value into TriggerManager.manageTrigger() as the trigger datablock to use.
Notice also we define what to do for the ENTER and LEAVE callbacks, namely to stop/start the pendulum threads as in the example in Gem #16.
4) Binding the Cheat Function
Now edit your file ~/client/scripts/default.bind.cs. Add the following code to the bottom of the file to create a ctrl-shift T cheat bind to hide/show your triggers. NOTE: In Gem #9 we used this same bind for the "pretend spawners". Since those were pretend you can delete them now. :-)
// cheat code to bind to show/hide your triggers....
GlobalActionMap.bind(keyboard, "ctrl-shift t", cheatToggleTriggers);
function cheatToggleTriggers(%val)
{
if (%val)
commandToServer('CheatToggleTriggers');
}5) Trying it out
Now lets see how this works! Edit a mission and place a grandfather clock. Refer to Gem #1 for details on doing that.
Note that when you place the grandfather clock in the mission editor it *does* create a trigger for you right away. Unfortunately when you place an object in the mission editor it does not, at time of creation, have a correct position. The upshot is that when you place the clock in mission editor the Trigger created for it is over at 0,0,0 (the world origin). Woops!. This wont matter in game becuase when the clock is loaded from the mission file (.mis file) it will have the right position. To fix this issue right now we just use the cheat binds we setup.
Remember that if you press Ctrl-Alt-T it will "toggle" the triggers, meaning create or destroy them. You cannot see triggers in the mission editor until you select them or run mouse over them. You should be able to verify that a trigger is created for your clock when you toggle the triggers off and then back on again. You can move the clock around and toggle triggers on and off and then see your trigger is correctly positioned for the new clock position.
6) Why is this useful?
There are several things that are useful about this method. The obvious one is you dont need to place the triggers yourself if all you wanted was a trigger centered around some object. The next thing is that when you have a bunch of triggers in your mission it gets hard to edit. Your selecting triggers all the time when you don't mean to. With this method you simply toggle them off before editing and back on to test.
Where are these triggers anyway?
The triggers themselves are not saved with the mission. They are in the MissionCleanup group, which is the correct thing for objects created dynamically when the game is running. When you reload the mission all the triggers are dynamically created so it doesn't matter that they are not saved in the file.
What was that other file, ShapeBase.cs, all about?
The observant coder will have noticed the extra file included in the .zip file that has not been mentioned. What is that all about? The previous gem, Gem #16, showed how the regular Torque DefaultTrigger wil redirect callbacks to objects in the same group as the trigger. ManagedTriggers avoid the hassle of all that if the idea was to redirect to a single object. However - you might like using the old system better. For those times it's useful to have the extra code in the ShapeBase namespace provided in the ShapeBase.cs file provided with this gem.
That code looks like this:
// Note also the slight name inversion. The callback on the trigger itself
// is called onEnterTrigger() whereas the callback on the object entering
// the trigger is onTriggerEnter()
function ShapeBase::onTrigger(%obj, %trigger, %enter)
{
%db = %obj.getDataBlock();
if (%enter)
{
if (%db.isMethod("onTriggerEnter"))
%db.onTriggerEnter(%obj, %trigger);
}
else
{
if (%db.isMethod("onTriggerLeave"))
%db.onTriggerLeave(%obj, %trigger);
}
}
// the trigger system calls on the OBJECT itself so
// we need to define this function to redirect that
// to the datablock so we can handle it on a case-by-case basis
function ShapeBase::onTriggerTick(%obj, %trigger)
{
%db = %obj.getDataBlock();
if (%db.isMethod("onTriggerTick"))
%db.onTriggerTick(%obj, %trigger);
}Notice that what it does is redirect the callbacks to the namespace of the datablock, if the object has a datablock, and that datablock actaully has the re-direct methods. You can put this code wherever you like. You can copy it from the file I provided, and paste it into the end of your existing ~/server/scripts/shapebase.cs file. If you followed Gem #5 and made a plastic folder you can place it in the plastic/server/core file next to the TriggerManager.cs and ManagedTrigger.cs files from this gem, and then edit the plastic/server/init.cs file to make sure it gets executed.
The Next Gem
That's all for this gem. Now that we know good ways to handle triggers we can now hand it over to Jason! Next gem Jason will be introducing one of the many game objects he has built, a fan object that has threads to rotate it both horizontally and vertically, as well as a clever cyclic thread that creates the blade-spinning efffect. It had a managed trigger that would detect when the player got close which would cause the fan to turn toward the player. Also every tick of the trigger the fan would apply a force to any player within range and within a given angle of the fan. All this behavior was achieved with Jason's clever animation threads and a small bit of script code.
Associate Paul Dana
ALSO: anyone who might have downloaded an early version of this resource should look at the .zip file they grabbed. If it is called pg17_triggerManager.zip then download the .zip again. It should be called pg17_managedTriggers.zip. In the old version I forgot to define the ManagedTriggerBase class.