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
EngineFile: /game/projectile.cc
ScriptFile: /server/scripts/crossbow.cs
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;
};About the author
Recent Blogs
• Damage Texture with Transparency• Projectile Spread
• Plan for Derk Adams
• Continuous Laser
• Plan for Derk Adams
#22
[edit]
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;
target = %obj.client.getSelectedObj(); // my change
};
...
note: I modify the Object selection in Torque
get the ID of the target in the mission.Now it works over the network.
01/26/2005 (2:33 am)
I had figured out the question,Thanks Derk![edit]
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;
target = %obj.client.getSelectedObj(); // my change
};
...
note: I modify the Object selection in Torque
get the ID of the target in the mission.Now it works over the network.
#23
02/07/2005 (11:27 am)
i wonder how can we pass a target id to the missile, i've seen player target locking, but it seems to work with bots only. does anyone have an idea on how to make it work for other players and some objects?
#24
Here is what I do:
/server/scripts/game.cs
You will then need to pass the info back and forth from the server to client.
HIH.
02/07/2005 (2:25 pm)
Ricardo,Here is what I do:
/server/scripts/game.cs
function startGame()
{
...
// Setup Tracking for Targeting
$allPlayers = new SimSet(allPlayers);
...
}
...
function GameConnection::createPlayer(%client, %spawnPoint)
{
...
$allPlayers.add(%player);
...
}/server/scripts/aiPlayer.csfunction AIPlayer::spawn(%file)
{
...
$allPlayers.add(%player);
...
}Be sure to use the following where ever you eliminate a player or bot so they are not selectable (mine are in non-standard places):$allPlayers.remove(%this);Then use something like this to run through the list to select the targets.
// Find current target's index
for(%i=0;%i<$allPlayers.getCount();%i++) {
if (%player.targetedObject == $allPlayers.getObject(%i)) {
%start = %i;
}
}You will then need to pass the info back and forth from the server to client.
HIH.
#25
i added the following locking code and maded it so it is called by the engine code when you aim at a shape that has a name (defined in the guiCrosshairHud.cc).
the function gets called, as the console prints the echo functions i've put there but the missile still doesn't track. i assume i have to pass that info to the server as you told me, but how is that done? i haven't seen any documentation on torque's networking so far..
thanks for your help,
-nd
02/13/2005 (10:02 am)
thanks for the info derk,i added the following locking code and maded it so it is called by the engine code when you aim at a shape that has a name (defined in the guiCrosshairHud.cc).
function lockOn() {
echo("locked on");
// Find current target's index
for(%i=0;%i<$allPlayers.getCount();%i++) {
if (%player.targetedObject == $allPlayers.getObject(%i)) {
%start = %i;
}
}
$Character::targetedObject = %start;
}the function gets called, as the console prints the echo functions i've put there but the missile still doesn't track. i assume i have to pass that info to the server as you told me, but how is that done? i haven't seen any documentation on torque's networking so far..
thanks for your help,
-nd
#26
targetPos = mTarget->getPosition();
printf("%f %f %f", targetPos.x, targetPos.y, targetPos.z);
this means, it is getting a valid target object, but when it calls mTarget->getPosition(), it gets the old one.
my target object moves around in script. i also checked that the pointer isn't being lost - if it was it wouldn't display the same position either. my masks are being set too - any ideas?
08/25/2005 (6:40 pm)
I have a weird problem - this only outputs the same position all the time:targetPos = mTarget->getPosition();
printf("%f %f %f", targetPos.x, targetPos.y, targetPos.z);
this means, it is getting a valid target object, but when it calls mTarget->getPosition(), it gets the old one.
my target object moves around in script. i also checked that the pointer isn't being lost - if it was it wouldn't display the same position either. my masks are being set too - any ideas?
#27
I have no idea. My suggestion, what I do, is to put in echo and print statements everywhere to track everything that is happening. From your description I would guess that either your target id is valid, but not the right object, or your target object isn't being updated on the server. Those are just guesses though.
Thanks.
08/26/2005 (2:14 pm)
Lineage,I have no idea. My suggestion, what I do, is to put in echo and print statements everywhere to track everything that is happening. From your description I would guess that either your target id is valid, but not the right object, or your target object isn't being updated on the server. Those are just guesses though.
Thanks.
#28
08/26/2005 (3:02 pm)
thanks for the reply - i must have edited the projectile file previously for some other mod. after getting a clean copy, and making the appropriate changes from this resource again, it worked perfectly :)
#29
I am considering now how to make the missle more smooth when it cycles around something. I have some "stupid" guided missles, but they make rigid turns that are polygonal, a smooth curve would be cooler. Any ideas? I'll post anything I figure out.
10/01/2005 (7:43 pm)
Pretty awesome. Took me a while to realize that the guidance by default in this resource was set so low that I couldn't see the effect (might have been because of my weird game), but once I turned up precision and track delay some, I got what I was looking for.I am considering now how to make the missle more smooth when it cycles around something. I have some "stupid" guided missles, but they make rigid turns that are polygonal, a smooth curve would be cooler. Any ideas? I'll post anything I figure out.
#30
I read in this thread something about target id's... is that what I'm missing??
if so can someone explain how I would implement a target id to my bots??
thanks
11/01/2005 (8:02 am)
I've followed all the set above and the missiles still just go in a straight line... I've tried changing the trackDelay and percision and they still wont track after my carbotsI read in this thread something about target id's... is that what I'm missing??
if so can someone explain how I would implement a target id to my bots??
thanks
#31
About a quarter of the way up this page, I have shown the code to track player (and bot) ids and select them. If you don't pass an object id to the missiles, they will not track.
Thanks.
11/02/2005 (3:16 pm)
540,About a quarter of the way up this page, I have shown the code to track player (and bot) ids and select them. If you don't pass an object id to the missiles, they will not track.
Thanks.
#32
I added teh projectile changes, rebuilt the executable
made the changes to the crossbow, including adding some methods to find the best target.
When I fire the bolt, I echo out the target (it is the proper id of the player it should hit "1340")
but the bolt doesnt seek. I have it set to no delay and 100 % precision.
11/20/2005 (1:30 pm)
This isn't working for me. I added teh projectile changes, rebuilt the executable
made the changes to the crossbow, including adding some methods to find the best target.
When I fire the bolt, I echo out the target (it is the proper id of the player it should hit "1340")
but the bolt doesnt seek. I have it set to no delay and 100 % precision.
#33
11/20/2005 (3:38 pm)
I got it. I put the isGuided block under the wrong isBallistic block.
#34
02/22/2006 (8:37 pm)
So is this resource working with TGE 1.4 and does it work in Multiplayer?
#36
03/10/2006 (12:21 pm)
A note, if anyone uses the guiaAdvancedCrosshair resource, it comes with some great ObjectSelection functions.
#37
Just in case. Works awsome!
04/20/2006 (12:42 am)
I just put this into 1.4 with the lighting kit, and there's one small discrepancy in your patch notes that may screw up non-coders. In Projectile::PackUpdate and Projectile::UnpackUpdate, your patch shows a series of else-ifs, while in 1.4 these are just simple ifs (at least in my copy). For 1.4, it looks more like this:U32 Projectile::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
{
...
if (stream->writeFlag(mask & BounceMask))
{
// Bounce against dynamic object
mathWrite(*stream, mCurrPosition);
mathWrite(*stream, mCurrVelocity);
}
+ if (stream->writeFlag(mask & GuideMask))
+ {
+ mathWrite(*stream, mCurrPosition);
+ mathWrite(*stream, mCurrVelocity);
+ }
return retMask;
...
void Projectile::unpackUpdate(NetConnection* con, BitStream* stream)
{
...
if(stream->readFlag())
{
mathRead(*stream, &mCurrPosition);
mathRead(*stream, &mCurrVelocity);
}
+ if(stream->readFlag())
+ {
+ mathRead(*stream, &mCurrPosition);
+ mathRead(*stream, &mCurrVelocity);
+ }
}
...Just in case. Works awsome!
#38
05/17/2006 (4:30 am)
I'm not sure if these work because I havn't had time to test but for the onfire I worked up 2 waysfunction LRPistolImage::onFire(%this, %obj, %slot)
{
%projectile = %this.projectile;
%targetobject = %obj.client.player.getAimObject();
// Decrement inventory ammo. The image's ammo state is update
// automatically by the ammo inventory hooks.
//%obj.decInventory(%this.ammo,1);
// Determin initial projectile velocity based on the
//gun's muzzle point and the object's current velocity
%muzzleVector = %obj.getMuzzleVector(%slot);
%objectVelocity = %obj.getVelocity();
%muzzleVelocity = VectorAdd(
VectorScale(%muzzleVector, %projectile.muzzleVelocity),
VectorScale(%objectVelocity, %projectile.velInheritFactor));
// Create the projectile object
%p = new (%this.projectileType)() {
dataBlock = %projectile;
initialVelocity = %muzzleVelocity;
initialPosition = %obj.getMuzzlePoint(%slot);
sourceObject = %obj;
sourceSlot = %slot;
client = %obj.client;
target = %targetobject;
};
MissionCleanup.add(%p);
return %p;
}function LRPistolImage::onFire(%this, %obj, %slot)
{
%searchMasks = $TypeMasks::playerObjectType;
%projectile = %this.projectile;
%pos = %obj.getEyePoint();
// Start with the shape
%eye = %obj.getEyeVector();
%eye = vectorNormalize(%eye);
%vec = vectorScale(%eye, %selectRange);
//
%end = vectorAdd(%vec, %pos);
%scanTarg = ContainerRayCast (%pos, %end, %searchMasks);
if(%scantarg)
{
%targetObject = firstWord(%scanTarg);
}
// Decrement inventory ammo. The image's ammo state is update
// automatically by the ammo inventory hooks.
//%obj.decInventory(%this.ammo,1);
// Determin initial projectile velocity based on the
//gun's muzzle point and the object's current velocity
%muzzleVector = %obj.getMuzzleVector(%slot);
%objectVelocity = %obj.getVelocity();
%muzzleVelocity = VectorAdd(
VectorScale(%muzzleVector, %projectile.muzzleVelocity),
VectorScale(%objectVelocity, %projectile.velInheritFactor));
// Create the projectile object
%p = new (%this.projectileType)() {
dataBlock = %projectile;
initialVelocity = %muzzleVelocity;
initialPosition = %obj.getMuzzlePoint(%slot);
sourceObject = %obj;
sourceSlot = %slot;
client = %obj.client;
target = %targetobject;
};
MissionCleanup.add(%p);
return %p;
}Would these methods work?
#39
I have the problem with this resource...
I would the my projectile target a my helicopter....
But never success....Why?
Any help my with a code? THANKS....
05/21/2006 (8:36 am)
Hi at all...I have the problem with this resource...
I would the my projectile target a my helicopter....
But never success....Why?
Any help my with a code? THANKS....
#40
05/24/2006 (10:00 am)
Using a modified version of the Advanced Crosshair resource, I got this in and working great. Thanks for the info. 
Torque Owner David Tan