Game Development Community

dev|Pro Game Development Curriculum

Guided or Seeker Projectiles.

by Derk Adams · 11/30/2004 (9:28 am) · 54 comments

Download Code File

Background:
I was surprised when I purchased Torque that there was not at least a rudimentary tracking system for projectiles. When I started looking, the only answer I received was that Beaver Patrol (www.gamebeavers.com) had a "seeker" missile. Upon further inspection, the seeker code was embedded within their game and getting the code was not an easy task. After a couple months, I was able to get a copy of the Beaver code and I used it as a starting point. I have modified the code sufficiently to be confident that it can stand alone as a separate resource. I have removed many of the "features" and modified the method of tracking.

Discussion:
The guided projectile uses a flag called "isGuided" to activate the tracking system. If activated and a valid target object id is provided, the projectile will align itself to the target location. There are two attributes of the tracking; "trackDelay" which allows for a delay from the weapon firing to the time the projectile begins to track the target, and "precision" which is a percentage of vector change each tick. By default, isGuided is set to false, the target is null, and trackDelay and precision are set to 0. This makes sure that it will seamlessly integrate into existing projects without requiring any script modifications.

IsGuided will work with isBallistic, but the tracking will work against gravity if necessary.

In the script file, I have saved the target object's id in "$Character::targetedObject" on the client's system. You will need to use one of the tracking resources to figure out how to get the object id to pass to the projectile system.

Acknowledgements:
The seeker projectile code from Game Beavers at http://www.gamebeavers.com was useful in pointing me in the right direction.

Key:
I use the same indication of modifications as a patch file. A line beginning with a "-" is to be removed and a line beginning with a "+" is to be added. Be sure to remove the "+" when you actually put the line into your code. A few lines around the changes are shown to give context to the changes. The triple dot "..." is showing that information has been removed for brevity purposes.

Development Environment:
November 2004 - February 2006
Head 1.2.2 - 1.4
Win32
Single Player - Multiplayer

Implementation:

EngineFile: /game/projectile.h
...
class ProjectileData : public GameBaseData
{
...
public:
...
   /// Should it arc?
   bool isBallistic;

+   /// Should it track target?
+   bool isGuided;
+   F32 precision;
+   S32 trackDelay;

   /// How HIGH should it bounce (parallel to normal), [0,1]
   F32 bounceElasticity;
...

class Projectile : public GameBase
{
   typedef GameBase Parent;
   ProjectileData* mDataBlock;

+    SimObjectPtr<ShapeBase> mTarget; 
+    S32 mTargetId; 

public:
   // Initial conditions
   enum ProjectileConstants {
      SourceIdTimeoutTicks = 7,   // = 231 ms
      DeleteWaitTime       = 500, ///< 500 ms delete timeout (for network transmission delays)
      ExcessVelDirBits     = 7,
      MaxLivingTicks       = 4095,
   };
   enum UpdateMasks {
      BounceMask    = Parent::NextFreeMask,
      ExplosionMask = Parent::NextFreeMask << 1,
-      NextFreeMask  = Parent::NextFreeMask << 2
+      GuideMask = Parent::NextFreeMask << 2,
+      NextFreeMask  = Parent::NextFreeMask << 3
   };
...

EngineFile: /game/projectile.cc
...
ProjectileData::ProjectileData()
{
...
   isBallistic = false;

+   isGuided = false;
+   precision = 0; 
+   trackDelay = 0;

    velInheritFactor = 1.0;
...

void ProjectileData::initPersistFields()
{
...
   addNamedField(isBallistic, TypeBool, ProjectileData);

+   addNamedField(isGuided, TypeBool, ProjectileData);
+   addNamedFieldV(precision, TypeF32, ProjectileData, new FRangeValidator(0, 100)); 
+   addNamedFieldV(trackDelay, TypeS32, ProjectileData, new FRangeValidator(0, 100000)); 

   addNamedFieldV(velInheritFactor, TypeF32, ProjectileData, new FRangeValidator(0, 1));
...

void ProjectileData::packData(BitStream* stream)
{
...
   if(stream->writeFlag(isBallistic))
   {
      stream->write(gravityMod);
      stream->write(bounceElasticity);
      stream->write(bounceFriction);
   }
+   if(stream->writeFlag(isGuided))
+   {
+      stream->write(precision);
+      stream->write(trackDelay);
+   }
   stream->writeFlag(doDynamicClientHits);
...

void ProjectileData::unpackData(BitStream* stream)
{
...
   if(isBallistic)
   {
      stream->read(&gravityMod);
      stream->read(&bounceElasticity);
      stream->read(&bounceFriction);
   }
+   isGuided = stream->readFlag();
+   if(isGuided)
+   {
+      stream->read(&precision);
+      stream->read(&trackDelay);
+   }
   doDynamicClientHits = stream->readFlag();
...

Projectile::Projectile()
{
...
   mMaintainThread    = NULL;

+   mTarget = NULL; 
+   mTargetId = -1; 

   mHidden           = false;
...

void Projectile::initPersistFields()
{
...
   endGroup("Source");
+   addField("target",            TypeS32,        Offset(mTargetId, Projectile)); 
}
...

bool Projectile::onAdd()
{
...
   if (isServerObject())
   {
      ShapeBase* ptr;
      if (Sim::findObject(mSourceObjectId, ptr))
         mSourceObject = ptr;
      else
      {
         if (mSourceObjectId != -1)
            Con::errorf(ConsoleLogEntry::General, "Projectile::onAdd: mSourceObjectId is invalid");
         mSourceObject = NULL;
      }

+      ShapeBase* tptr;
+      mTarget = NULL; 
+      if(mTargetId != -1)    
+      if(Sim::findObject(mTargetId, tptr))         
+          mTarget = tptr; 

      // If we're on the server, we need to inherit some of our parent's velocity
      //
      mCurrTick = 0;
   }
...

void Projectile::processTick(const Move* move)
{
...
   if(mDataBlock->isBallistic)
      mCurrVelocity.z -= 9.81 * mDataBlock->gravityMod * (F32(TickMs) / 1000.0f);
+   // Tracking updates
+   if(mDataBlock->isGuided) {
+     // Only process if there is a target and the projectile is locked on
+     if((bool)mTarget && mCurrTick > mDataBlock->trackDelay) {
+       // Be sure to update clients on changes
+       setMaskBits(GuideMask);
+       // Set up variables
+       F32 speed;
+       Point3F targetDir;
+       Point3F targetPos;
+       // Get target position
+       targetPos = mTarget->getPosition(); 
+       // Adjust z to hit middle of target's bounding box
+       targetPos.z += (mTarget->getObjBox().len_z()/2); 
+       // Remember current speed
+       speed = mCurrVelocity.len(); 
+       // Calculate direction change necessary to get to target
+       targetDir = targetPos - mCurrPosition;
+       // Normalize target direction
+       targetDir.normalize();
+       // Normalize current direction
+       mCurrVelocity.normalize();
+       // Adjust target direction based on precision
+       targetDir *= mDataBlock->precision;
+       // Adjust current direction based on precision
+       mCurrVelocity *= (100 - mDataBlock->precision);
+       // Combine directions
+       targetDir += mCurrVelocity;
+       // Normalize current direction
+       targetDir.normalize();
+       // Scale new velocity to remembered speed
+       targetDir *= speed;
+       // Set current velocity to new velocity
+       mCurrVelocity = targetDir;
+     }
+   } 

   newPosition = oldPosition + mCurrVelocity * (F32(TickMs) / 1000.0f);
...

U32 Projectile::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
{
...
   else if (stream->writeFlag(mask & BounceMask))
   {
      // Bounce against dynamic object
      mathWrite(*stream, mCurrPosition);
      mathWrite(*stream, mCurrVelocity);
   }
+   else if (stream->writeFlag(mask & GuideMask))
+   {
+      mathWrite(*stream, mCurrPosition);
+      mathWrite(*stream, mCurrVelocity);
+   }
   return retMask;
...

void Projectile::unpackUpdate(NetConnection* con, BitStream* stream)
{
...
   else if(stream->readFlag())
   {
      mathRead(*stream, &mCurrPosition);
      mathRead(*stream, &mCurrVelocity);
   }
+   else if(stream->readFlag())
+   {
+      mathRead(*stream, &mCurrPosition);
+      mathRead(*stream, &mCurrVelocity);
+   }
}
...

ScriptFile: /server/scripts/crossbow.cs
...
datablock ProjectileData(CrossbowProjectile)
{
...
   isBallistic         = true;
   gravityMod = 0.80;

+   isGuided            = true;
+   // Precision is how acurately the projectile tracks the target.
+   // 0 is no tracking (same as not guided)
+   // 100 is exact tracking
+   precision           = 5;
+   // TrackDelay is the number of milliseconds after firing to begin tracking
+   trackDelay          = 40;

   hasLight    = true;
...

function CrossbowImage::onFire(%this, %obj, %slot)
{
...
   %p = new (%this.projectileType)() {
      dataBlock        = %projectile;
      initialVelocity  = %muzzleVelocity;
      initialPosition  = %obj.getMuzzlePoint(%slot);
      sourceObject     = %obj;
      sourceSlot       = %slot;
      client           = %obj.client;
+      target           = $Character::targetedObject;
   };
Page «Previous 1 2 3 Last »
#1
11/30/2004 (9:33 am)
Hey this looks pretty shwankin! I'll have to check this out as soon as I get home from school!
#2
11/30/2004 (11:50 am)
Definitely adding this to my project.

You couldn't have posted this at a better time for me. =)

~Cheers
#3
11/30/2004 (12:52 pm)
Sounds great, I could find some usage for this.

-Jase
#4
11/30/2004 (1:39 pm)
thnx I can definetely use this I was waiting for something like this to come out.
#5
11/30/2004 (2:05 pm)
is there code that makes a projectile skim along the ground? I have no Idea how to do that except make each one a hover vehicle which would innihilate the fps.
#6
11/30/2004 (2:42 pm)
Treb,

What you are asking for is a ground following missile. I have plans to implement that in the future, but don't have time at the moment.

It involves detecting the distance above the terrain and adjusting the hight of the projectile. It shouldn't be too difficult, just add it to processTick. Or you can wait for me, but it could be up to a year from now.

Thanks.
#7
11/30/2004 (3:06 pm)
thanks a lot dirk I've been trying it for a while with no luck
#8
11/30/2004 (4:01 pm)
hi, Derk Adams:

Does it work in tge v1.3.0 ?
I use tge 1.3.0, followed your step and modified the "projectile.h/cc", and in CrossBow.cs, i changed to:
isGuided = true;
precision = 100;
trackDelay = 5;
but when i run the torqueDemo_DEBUG.exe, the Guided or Seeker Projectiles
effects does not display.
what's the problem?
Could you give me some advices?
Thanks!
#9
11/30/2004 (6:43 pm)
Ahulo,

Make sure you are sending it a valid target id. Without one, the guidance system is not activated. It is sent in the Crossbow::onFire script.

Thanks.
#10
11/30/2004 (9:32 pm)
The download does not work for me.

Anyone else able to download it?
#11
11/30/2004 (10:22 pm)
Adams:

Thanks!
I'm a newie to tge. I have no ideas to do so. Can you give some cs examples to show how getting the target id and sending it to the projectile system.

And, the gg link doesn't work.
#12
12/01/2004 (2:59 am)
When I implement this, I will add the following to it:

TargetID PackUpdate -- So the client will be warned he is being followed.

Decoys -- So the missile can be averted.

Spin -- So the missile spins along its Z axis, for cool effect.
#13
12/01/2004 (6:21 am)
Greetings,

I have fixed the upload, but it is just a text file of this resource.

Ahulo: I modified the Player Target Locking to get the ids of the bots in the mission.

Mike: Glad to hear it. I specifically kept the modifications simple because different people will have different uses for their missiles. I have added a spread option to keep the missiles from hitting the same place and plan to add ground following in the future.

Thanks.
#14
12/01/2004 (1:45 pm)
I decided to rate this 5 because of it's good usage.
#15
12/07/2004 (2:16 am)
that... is soooo awesome
#16
12/19/2004 (2:09 am)
Extremely useful! Rated it 5! :-)
#17
12/21/2004 (9:28 am)
I created the ground following missiles that Master Treb wanted. The resource is here.

Thanks, Derk. I don't want homing missiles in my game, but I learned a lot by studying your work.
#18
12/27/2004 (7:38 am)
thanks a lot Erik It's just what I wanted.
#19
01/24/2005 (12:09 am)
It's great!how could it works in network?
#20
01/24/2005 (7:16 am)
Tyq.fk,

It should work over the network, just be sure to have the target correctly selected (see comments above).

Thanks.
Page «Previous 1 2 3 Last »