Game Development Community

dev|Pro Game Development Curriculum

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:
[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!

#1
10/08/2007 (2:47 pm)
I spent ages using UTEngine 436, scripting animations, triggers and all that.

Such a cool way to do things in -cool stuff & thanks ;)
#2
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
01/21/2008 (1:53 am)
@Joey
Never heard about similiar things in the engine.

It looks like simple but powerful. Good! :-)