Generic Event System for Torque
by Dan Keller · 10/08/2007 (10:25 am) · 7 comments
This was inspired by Unreal's event system. Anyone familiar with UnrealEd will find this system very intuitive. If not, just bear with me.
Installation:
Open gameBase.h and add the bold code:
At the top of the file:
Around line 60:
Around line 157:
Around line 193:
After the gameBase definition, add all of the following code
Around line 410:
Delete the addObject function and replace it with the following:
Save and close.
Now open gameBase.cc
Around line 32, add:
Around line 71:
Around line 156:
Around line 191:
Around line 546 (or really anywhere) add the following code:
Around 588:
Save and close. Compile the project.
Use:
First of all, this system can only be used (obviously) in objects that are derived from gameBase. Here's a list:
# GameBase
* Debris
* Explosion
* fxLight
* Lightning
* ParticleEmitter
* ParticleEmitterNode
* PathedInterior
* Precipitation
* Projectile
* ShapeBase
o Camera
o Item
o MissionMarker
+ SpawnSphere
+ WayPoint
o PathCamera
o Player
+ AIPlayer
o StaticShape
o Vehicle
+ FlyingVehicle
+ HoverVehicle
+ WheeledVehicle
* Splash
* Trigger
There are two ways to use it, and each way is used for a different application. The first is setting Event and Tag in the mission editor. This is useful for things like particles, which may need to be triggered separately, but all do the same thing (ie. start emitting).
The second way is to set Event and Tag in the datablock. (Note that if you set both, the mission editor overrides the datablock.) This is less flexible in that all objects with that datablock have the same event/tag, but it has its uses. For example, it can be used with objects such as projectiles that aren't normally added to the mission file.
Another potentially more flexible way is setting the Event or Tag when an object is created in script. This can be used to give every object a unique tag. I haven't had much success with this, so tell me if you get it to work.
Example:
Notes:
o The Event or Tag has to be set when the object is created or it won't work.
o If you're setting them in the mission editor, restaret the mission afterwards. Better yet, set them in the mission file directly.
o When using this system with particles, remember that the datablock the actual object uses is a ParticleNodeData, so set your function in there.
o Be creative!
Installation:
Open gameBase.h and add the bold code:
At the top of the file:
[b]#include <string.h>[/b] class NetConnection; class ProcessList; [b]struct tagList;[/b] struct Move;
Around line 60:
public: bool packed; StringTableEntry category; StringTableEntry className; [b] StringTableEntry eventName; StringTableEntry eventTag; [/b] bool onAdd();
Around line 157:
private: GameBaseData* mDataBlock; StringTableEntry mNameTag; [b] tagList* eventWatcher;[/b]
Around line 193:
GameBase();
virtual ~GameBase();
[b] StringTableEntry mEventName;
StringTableEntry mEventTag; [/b]
enum GameBaseMasks {
[i]...[/i]
};
[b] void event();
void setEventWatcher (tagList * list) { eventWatcher = list; }
tagList * getEventWatcher () { return eventWatcher; }[/b]After the gameBase definition, add all of the following code
typedef struct tagList {
tagList(GameBase* iEl) //create from tagged object
{
strcpy(name, strlen(iEl->getDataBlock()->eventTag) ? iEl->getDataBlock()->eventTag : iEl->mEventTag);
mEvents = new Vector<GameBase*>;
mEvents->push_back(iEl);
users = 0;
}
tagList(GameBase* iEl, const char * tag) //create from event object
{
strcpy(name, tag);
mEvents = new Vector<GameBase*>;
users = 0;
}
~tagList()
{
delete mEvents;
}
char name [1024];
int users;
Vector<GameBase*> *mEvents;
}tagList;Around line 410:
void orderList(); void advanceObjects(); [b] Vector<tagList*> mEventList;[/b]
Delete the addObject function and replace it with the following:
void addObject(GameBase* obj) {
obj->plLinkBefore(&head);
if (!mIsServer)
return;
const char *en, *et;
if (strlen(obj->getDataBlock()->eventTag) || strlen(obj->mEventTag)) { // do tag
et = strlen(obj->mEventTag) ? obj->mEventTag : obj->getDataBlock()->eventTag;
bool matchfound = false;
for (Vector<tagList*>::iterator i=mEventList.begin(); i!= mEventList.end(); ++i) {
if (!strcmp((*i)->name, et)) {
(*i)->mEvents->push_back(obj);
matchfound = true;
break;
}
}
if (!matchfound) {
tagList *tL = new tagList (obj);
mEventList.push_back(tL);
}
}
if (strlen(obj->getDataBlock()->eventName) || strlen(obj->mEventName)) { // do name
en = strlen(obj->mEventName) ? obj->mEventName : obj->getDataBlock()->eventName;
bool matchfound = false;
for (Vector<tagList*>::iterator i=mEventList.begin(); i!= mEventList.end(); ++i) {
if (!strcmp((*i)->name, en)) {
matchfound = true;
obj->setEventWatcher(*i);
((*i)->users)++;
break;
}
}
if (!matchfound) {
tagList *tL = new tagList (obj, en);
mEventList.push_back(tL);
obj->setEventWatcher(tL);
((tL)->users)++;
}
}
}
void removeObject(GameBase* obj) { //only on server
const char *et;
bool found = false;
if (strlen(obj->getDataBlock()->eventTag) || strlen(obj->mEventTag)) { // do tag
et = strlen(obj->mEventTag) ? obj->mEventTag : obj->getDataBlock()->eventTag;
Vector<tagList*>::iterator i;
for (i=mEventList.begin(); i!=mEventList.end(); ++i) {
if (!strcmp((*i)->name, et))
{
found = true;
break; // *i now points to element containing the object
}
}
if (found)
{
for (Vector<GameBase*>::iterator j=(*i)->mEvents->begin(); j!=(*i)->mEvents->end(); ++j) {
if (*j == obj) { //a match
(*i)->mEvents->erase(j);
break;
}
}
if ((*i)->mEvents->empty() && !(*i)->users){
delete (*i);
mEventList.erase(i);
}
}
}
found = false;
if (strlen(obj->getDataBlock()->eventName) || strlen(obj->mEventName)) { // do name
Vector<tagList*>::iterator i;
for (i=mEventList.begin(); i!=mEventList.end(); ++i) {
if ((*i) == obj->getEventWatcher()) {
((*i)->users)--;
found = true;
break; // *i now points to element the object points to
}
}
if (found && (*i)->mEvents->empty() && !(*i)->users){
delete (*i);
mEventList.erase(i);
}
}
}Save and close.
Now open gameBase.cc
Around line 32, add:
GameBaseData::GameBaseData()
{
category = "";
className = "";
packed = false;
[b] eventName = "";
eventTag = "";[/b]
}Around line 71:
void GameBaseData::initPersistFields()
{
Parent::initPersistFields();
addField("category", TypeCaseString, Offset(category, GameBaseData));
addField("className", TypeString, Offset(className, GameBaseData));
[b] addField("event", TypeString, Offset(eventName, GameBaseData));
addField("tag", TypeString, Offset(eventTag, GameBaseData));[/b]
}Around line 156:
mNameTag = ""; mControllingClient = 0; [b] eventWatcher = NULL; mEventName = ""; mEventTag = "";[/b] }
Around line 191:
void GameBase::onRemove()
{
plUnlink();
[b] if (!isClientObject())
gServerProcessList.removeObject(this);[/b]
Parent::onRemove();
}Around line 546 (or really anywhere) add the following code:
void GameBase::event()
{
if (!eventWatcher)
return;
for (Vector<GameBase*>::iterator i=eventWatcher->mEvents->begin(); i!=eventWatcher->mEvents->end(); ++i)
Con::executef((*i)->getDataBlock(), 2, "onEvent", (*i)->scriptThis());
}
ConsoleMethod(GameBase, event, void, 2, 2, "Triggers the object's event.")
{
//GameBaseData* data;
//Sim::findObject(argv[2],data);
object->event();
}Around 588:
void GameBase::initPersistFields()
{
Parent::initPersistFields();
[b] addGroup("Event");
addField("Event", TypeString, Offset(mEventName, GameBase));
addField("Tag", TypeString, Offset(mEventTag, GameBase));
endGroup("Event");[/b]
addGroup("Misc");Save and close. Compile the project.
Use:
First of all, this system can only be used (obviously) in objects that are derived from gameBase. Here's a list:
# GameBase
* Debris
* Explosion
* fxLight
* Lightning
* ParticleEmitter
* ParticleEmitterNode
* PathedInterior
* Precipitation
* Projectile
* ShapeBase
o Camera
o Item
o MissionMarker
+ SpawnSphere
+ WayPoint
o PathCamera
o Player
+ AIPlayer
o StaticShape
o Vehicle
+ FlyingVehicle
+ HoverVehicle
+ WheeledVehicle
* Splash
* Trigger
There are two ways to use it, and each way is used for a different application. The first is setting Event and Tag in the mission editor. This is useful for things like particles, which may need to be triggered separately, but all do the same thing (ie. start emitting).
The second way is to set Event and Tag in the datablock. (Note that if you set both, the mission editor overrides the datablock.) This is less flexible in that all objects with that datablock have the same event/tag, but it has its uses. For example, it can be used with objects such as projectiles that aren't normally added to the mission file.
Another potentially more flexible way is setting the Event or Tag when an object is created in script. This can be used to give every object a unique tag. I haven't had much success with this, so tell me if you get it to work.
Example:
function DefaultTrigger::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.
[b]%trigger.event();[/b]
Parent::onEnterTrigger(%this,%trigger,%obj);
}datablock ParticleEmitterNodeData(RealFireBigNode)
{
timeMultiple = 0;
[b]tag = "emit2";[/b]
};
function RealFireBigNode::onEvent(%this, %obj)
{
%obj.timeMultiple = 1;
%this.schedule(1000, "unTrigger");
}
function RealFireBigNode::unTrigger(%this, %obj)
{
%obj.timeMultiple = 0;
}new Trigger(Pt2) {
canSaveDynamicFields = "1";
position = "# # #";
rotation = "1 0 0 0";
scale = "1 1 1";
dataBlock = "DefaultTrigger";
[b]event = "emit2";[/b]
polyhedron = "0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000
-1.0000000 0.0000000 0.0000000 0.0000000 1.0000000";
};Notes:
o The Event or Tag has to be set when the object is created or it won't work.
o If you're setting them in the mission editor, restaret the mission afterwards. Better yet, set them in the mission file directly.
o When using this system with particles, remember that the datablock the actual object uses is a ParticleNodeData, so set your function in there.
o Be creative!
About the author
#2
i'm unfamiliar with unrealed,
could you describe some more situations where this could be used ?
10/08/2007 (5:52 pm)
this is interesting.i'm unfamiliar with unrealed,
could you describe some more situations where this could be used ?
#3
10/12/2007 (12:55 pm)
You could have a trigger that activates a PathShape, spawns enemies, causes a bot to go somewhere, or more importantly, does several of these at the same time. Set in the datablock, it could be used for a weapon allowing the player to drop explosive charges and remotely detonate them with the weapon. It could also be used for AI that react to the death of a teammate, for example.
#4
10/25/2007 (5:00 am)
As an alternate solution, you could use the Subscription Based Message Router and create a queue for each tag and register each object to each tag queue. Using this method, anything that can receive the script callback from the router can register itself to be triggered for any tag.
#5
10/26/2007 (1:14 pm)
That's very neat. I was actually considering doing something like that, but I decided it would be too difficult to program.
#6
10/30/2007 (7:18 am)
isnt something like this already in the engine?
#7
Never heard about similiar things in the engine.
It looks like simple but powerful. Good! :-)
01/21/2008 (1:53 am)
@JoeyNever heard about similiar things in the engine.
It looks like simple but powerful. Good! :-)

Torque Owner Christian S
Oak-Entertainment
Such a cool way to do things in -cool stuff & thanks ;)