Game Development Community

dev|Pro Game Development Curriculum

Projectile aware triggers

by Bill Vee · 07/01/2008 (9:59 am) · 22 comments

Download Code File

This resource will make triggers "aware" of projectiles.

In projectile.h in the projectile class add
void checkTriggers();
   static void findCallback(SceneObject* obj,void * key);
To a protected section.

And add
bool buildPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere);
To a public section.

Then in projectile.cc/cpp
for TGEA add
#include "T3D/trigger.h"
And for TGE
#include "game/trigger.h"
to the includes.

Next add to the end of projectile.cc/cpp
static MatrixF IMat(1);

bool Projectile::buildPolyList(AbstractPolyList* polyList, const Box3F&, const SphereF&)
{
   // Collision with the item is always against the item's object
   // space bounding box axis aligned in world space.
   Point3F pos;
   mObjToWorld.getColumn(3,&pos);
   IMat.setColumn(3,pos);
   polyList->setTransform(&IMat, mObjScale);
   polyList->setObject(this);
   polyList->addBox(mObjBox);
   return true;
}

//----------------------------------------------------------------------------
/** Check collisions with trigger and items
   Perform a container search using the current bounding box
   of the main body, wheels are not included.  This method should
   only be called on the server.
*/
void Projectile::checkTriggers()
{
   //Box3F bbox = mConvex.getBoundingBox(getTransform(), getScale());
   gServerContainer.findObjects(mWorldBox,TriggerObjectType,findCallback,this);
}

/** The callback used in by the checkTriggers() method.
   The checkTriggers method uses a container search which will
   invoke this callback on each obj that matches.
*/
void Projectile::findCallback(SceneObject* obj,void *key)
{
   Projectile* projectile = reinterpret_cast<Projectile*>(key);
   U32 objectMask = obj->getTypeMask();

   // Check: triggers
   if (objectMask & TriggerObjectType) {
      Trigger* pTrigger = static_cast<Trigger*>(obj);
      pTrigger->potentialEnterObject(projectile);
	   }
}
Then in void Projectile::processTick(const Move* move) add
New code 8/6/08
[b]  if (isServerObject())[/b]
   checkTriggers();
after
if (bool(mSourceObject))
      mSourceObject->disableCollision();
Now recompile.

Now add a new script file named ProjectileTrigger.cs to you server/scripts folder
The add
datablock TriggerData(ProjectileTrigger)
{
   // 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 = 100; // 1 Sec
};

function ProjectileTrigger::onEnterTrigger(%this,%trigger,%obj)
{
   Parent::onEnterTrigger(%this,%trigger,%obj);
   
  if(%obj.getClassName() $= "Projectile")
   {
     error("ProjectileTrigger::onEnterTrigger");
   }
   }

function ProjectileTrigger::onLeaveTrigger(%this,%trigger,%obj)
{

   Parent::onEnterTrigger(%this,%trigger,%obj);
  
  if(%obj.getClassName() $= "Projectile")
    {
       error("ProjectileTrigger::onLeaveTrigger");
   }
}
To it an save the file.

Now add a
exec("./ProjectileTrigger.cs");
To the list of scripts started in function onServerCreated() in your server/scripts/game.cs file.

Now start your game.
Create a trigger with the ProjectileTrigger datablock.
Now simple add whatever custom code you need to the onEnterTrigger and onLeaveTrigger functions and your set.
Page «Previous 1 2
#1
07/01/2008 (11:04 am)
Ahhhhh, this will be very handy ;-)
Thx Bill.
#2
07/01/2008 (11:10 am)
This opens up all kind of opportunities for in game events....holy cow I can't wait to use this for something =)
#3
07/01/2008 (7:25 pm)
Hey Bill,
Just tried out your source and I have a couple Q's. If I shoot a projectile through the trigger and then set the projectile's transform let's say up 10 units up, should it not put the projectile up 10 units? I echoed the transform results before and after the I setTransform and the numbers do change correctly, just as they should, but the projectile just goes straight through it as if nothing happened.
Second question is, is it supposed to echo 2 results, one being the server id and the other the client id, I think that's the ghost.....is it not?
Anyways, appreciate any help.
Thx.
#4
07/01/2008 (8:31 pm)
@DALO- First the easy one. The double echo you see is the fact that you are running as both the client and server so you get one from the client object then one from the server object. If you hosted a multiplayer game the player connecting to you would only get the client echo.

As for the settranform problem you have to remember that the projectile is kind of a special case in regards to the position transfom. Follow the code in ProcessTick and you will see that the it sets the objects transform based off of this calculation
newPosition = oldPosition + mCurrVelocity * (F32(TickMs) / 1000.0f);

And then later on it uses this
mCurrDeltaBase = newPosition;
   mCurrBackDelta = mCurrPosition - newPosition;
   mCurrPosition = newPosition;

 MatrixF xform(true);
   xform.setColumn(3, mCurrPosition);
   setTransform(xform);
So it never uses it's own transform to set its new transform.

What you would need to do is create a console method that changes mCurrPosition.
#5
07/01/2008 (11:27 pm)
Interesting stuff... I can think of alot of ways this can be used.
#6
07/02/2008 (9:38 am)
why does the buildpolylist gives an unresolved external reference error ?

Thx
#7
07/02/2008 (12:15 pm)
@Associa- That usually happens when you define a function in a header then forget to put the code in the cc/cpp file.

You need to make sure you put this in the projectile.cc/cpp file
static MatrixF IMat(1);

bool Projectile::buildPolyList(AbstractPolyList* polyList, const Box3F&, const SphereF&)
{
   // Collision with the item is always against the item's object
   // space bounding box axis aligned in world space.
   Point3F pos;
   mObjToWorld.getColumn(3,&pos);
   IMat.setColumn(3,pos);
   polyList->setTransform(&IMat, mObjScale);
   polyList->setObject(this);
   polyList->addBox(mObjBox);
   return true;
}

//----------------------------------------------------------------------------
/** Check collisions with trigger and items
   Perform a container search using the current bounding box
   of the main body, wheels are not included.  This method should
   only be called on the server.
*/
void Projectile::checkTriggers()
{
   //Box3F bbox = mConvex.getBoundingBox(getTransform(), getScale());
   gServerContainer.findObjects(mWorldBox,TriggerObjectType,findCallback,this);
}

/** The callback used in by the checkTriggers() method.
   The checkTriggers method uses a container search which will
   invoke this callback on each obj that matches.
*/
void Projectile::findCallback(SceneObject* obj,void *key)
{
   Projectile* projectile = reinterpret_cast<Projectile*>(key);
   U32 objectMask = obj->getTypeMask();

   // Check: triggers
   if (objectMask & TriggerObjectType) {
      Trigger* pTrigger = static_cast<Trigger*>(obj);
      pTrigger->potentialEnterObject(projectile);
	   }
}
#8
07/02/2008 (1:07 pm)
This additional code will allow you to change the velocity on the fly.
When used with triggers it can produce some neat effects.

First open projectile.h and in the projectile class move
Point3F getVelocity() const;
To a public section.
Then add
void setVelocity(const VectorF& vel);
To a public section as well.

Then add this to the bottom of projectile.cc/cpp
ConsoleMethod( Projectile, getVelocity, const char *, 2, 2, "")
{
   const VectorF& vel = object->getVelocity();
   char* buff = Con::getReturnBuffer(100);
   dSprintf(buff,100,"%g %g %g",vel.x,vel.y,vel.z);
   return buff;
}

ConsoleMethod( Projectile, setVelocity, bool, 3, 3, "(Vector3F vel)")
{
   VectorF vel(0,0,0);
   dSscanf(argv[2],"%g %g %g",&vel.x,&vel.y,&vel.z);
   object->setVelocity(vel);
   return true;
}

void Projectile::setVelocity(const VectorF& vel)
{
   mCurrVelocity = vel;
   setMaskBits(BounceMask);

}

Re-compile.

Now for some script changes.
Change the ProjectileTrigger datablock to this
datablock TriggerData(ProjectileTrigger)
{
   // 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 = 100; // 1 Sec
};

function ProjectileTrigger::onEnterTrigger(%this,%trigger,%obj)
{
   Parent::onEnterTrigger(%this,%trigger,%obj);
   
   if( %obj.getClassName() $= "Projectile" )
   {    
    %vel = %obj.getVelocity();
    %x = getWord(%vel, 0);
    %y = getWord(%vel, 1);
    %z = getWord(%vel, 2);
 
    %x = %x * 0.1;
    %y = %y * 0.1;
    %z = %z * 0.1;
    
    %obj.setVelocity(%x SPC %y SPC %z);
    error("onEnterTrigger vel="@ %x SPC %y SPC %z ); 
   }
   }

function ProjectileTrigger::onLeaveTrigger(%this,%trigger,%obj)
{

   Parent::onEnterTrigger(%this,%trigger,%obj);
  
  if(%obj.getClassName() $= "Projectile" )
    {
     %vel = %obj.getVelocity();
    %x = getWord(%vel, 0);
    %y = getWord(%vel, 1);
    %z = getWord(%vel, 2);

    %x = %x * 10.0;
    %y = %y * 10.0;
    %z = %z * 10.0;
    
    %obj.setVelocity(%x SPC %y SPC %z);
    error("onEnterTrigger vel="@ %x SPC %y SPC %z ); 

   }
}

Now when the projectile enters this trigger it will slow to 1/10th its original speed then when it exits it will return to full speed.

Enjoy :)
#9
08/05/2008 (7:24 pm)
When I create the projectile the code is this.
// Create the projectile object
   %p = new (%this.projectileType)() {
      dataBlock        = %projectile;
      initialVelocity  = %muzzleVelocity;
      initialPosition  = %obj.getMuzzlePoint(%slot);
      sourceObject     = %obj;
      sourceSlot       = %slot;
      client           = %obj.client;
   };
   MissionCleanup.add(%p);
   return %p;

But when the projectile enters the trigger it's lost the client var...
function ProjectileTrigger::onEnterTrigger(%this,%trigger,%obj)
{
   Parent::onEnterTrigger(%this,%trigger,%obj);
   
  if(%obj.getClassName() $= "Projectile")
   {
     
     error("ProjectileTrigger::onEnterTrigger");
     messageClient(%obj.client, 'MsgClient', 'Trigger Hit With Projectile'); // <---- error - no client var
   }
}

Any ideas how I can retain the client var so I know who shot the trigger?
#10
08/05/2008 (7:51 pm)
Also - the trigger doesn't always pickup the projectile passing through it. Is there a way to increase the frequency of checks?
#11
08/05/2008 (8:40 pm)
@ Andy - I just tried your messageClient addition and it seems to work fine.
One thing you have to remember is there is a client projectile and a server projectile.
Both will set off the trigger.
I think the client projectile only passes variables coded into the packupdate and unpackupdate functions.
While the server projectile has access to both networked variables and dynamic fields assigned by script.
I could be wrong but it would explain why the clients %obj.client would equal null.

As for increasing the frequency of checks you could change tickPeriodMS to a lower value.

Also projectiles that travel very fast can pass thru a trigger and may never actually be "in" the trigger to ... well trigger it.
#12
08/05/2008 (8:51 pm)
So how do I deal with both client and server projectiles because in a multiplayer match I wll have both.
#13
08/05/2008 (8:52 pm)
I've reduced the tick down to 1ms here...
datablock TriggerData(ProjectileTrigger)
{
   // 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 = 1; // 1 Sec
};
#14
08/05/2008 (8:59 pm)
Hmmm - I've noticed something. The projectile fired from the crossbow is actually below the particles. So I was aiming the wrong thing. How do I adjust the projectile up so it lines up with the particle trail - so my aim looks right?
#15
08/06/2008 (3:57 am)
I will check to see if anything can be done about the client/server issue.

As for the particle alignment , at a guess maybe the particle is being ejected up to start.
#16
08/06/2008 (6:03 am)
It appears I forgot to make the checkTriggers function a server side only event.
Easily fixed.
In projectile::processTick() change

checkTriggers();

To
if (isServerObject())
   checkTriggers();

This will stop the client from triggering the trigger..

This should also stop the %obj.client from being null as the server object will have all the correct info.

I tested the messageClient(%obj.client, 'MsgClient', 'Trigger Hit With Projectile'); and i am getting consistent "hits" to the trigger with a properly defined %obj.client now.

Again I must stress that fast projectiles may fail to set off a trigger.
#17
08/07/2008 (5:04 am)
Sounds good. I have to try it...
#18
05/14/2009 (1:31 pm)
EDIT: The following is a list of errors you get if you put the very first 2 steps in the wrong spot. :P

In projectile.h, I had stuck the code here:

class ProjectileData : public GameBaseData

When it should have gone here:

class Projectile : public GameBase

Subtle difference that was not described well in the instructions, but I figured it out. :)

Anyway, here is the error:


Hmmm.... getting compile errors:

Using TGEA 1.8.1

..........enginesourceT3Dprojectile.cpp(776) : error C3861: 'checkTriggers': identifier not found
..........enginesourceT3Dprojectile.cpp(1148) : error C2509: 'buildPolyList' : member function not declared in 'Projectile'
        c:TorqueTGEA_1_8_1enginesourceT3D/projectile.h(113) : see declaration of 'Projectile'
..........enginesourceT3Dprojectile.cpp(1166) : error C2039: 'checkTriggers' : is not a member of 'Projectile'
        c:TorqueTGEA_1_8_1enginesourceT3D/projectile.h(113) : see declaration of 'Projectile'
..........enginesourceT3Dprojectile.cpp(1169) : error C2065: 'mWorldBox' : undeclared identifier
..........enginesourceT3Dprojectile.cpp(1169) : error C2065: 'findCallback' : undeclared identifier
..........enginesourceT3Dprojectile.cpp(1169) : error C2673: 'checkTriggers' : global functions do not have 'this' pointers
..........enginesourceT3Dprojectile.cpp(1176) : error C2039: 'findCallback' : is not a member of 'Projectile'
        c:TorqueTGEA_1_8_1enginesourceT3D/projectile.h(113) : see declaration of 'Projectile'
#19
08/23/2009 (3:16 am)
dose it work well in TGEA 1.7.0?
#20
08/23/2009 (12:58 pm)
Yes it works for 1.7.+.
Page «Previous 1 2