Player Target Locking
by Kurt · 04/01/2003 (10:50 am) · 25 comments
Okay so you want target locking? This tutorial should get you started. I have borrowed massive amounts of great code from a variety of sources so don't think for a minute that this is all my doing. The guys at tork.zenkel are awesome and are to be commended for their sharing of knowledge.
If I have omitted something please tell me and I will do my best to resolve the issue.
What does this code do? Well first you map a key which will be your target lock key. When this key is pressed the nearest bot is locked onto. Hitting the key again locks you to the next nearest bot and so on until there are no bots left to lock. If a bot is locked onto and leaves your predetermined locking distance he will automatically be unlocked. I will leave it up to the implementer to determine this distance. When a bot is locked in the code I am running currently, I place an emitter at mount1 of my bot model so i can see who is locked onto when there is a gang of bots. I marked this code as optional.
Issues:
At the moment dead bots can be locked onto. This is also a problem in Vice-City so I do not feel so bad. :) This should be easily solveable and I will post the fix later.
First things first, you must get some bots into your mission. To do this check out Stefan's excellent tutorial at:
Bots
For this tutorial the reader should know that I add my bots by using the ctrl + b keystroke. After the mission starts I manually add some bots that immediately start following waypoints. This is important because as bots are added I keep track of them by storing them in a sim group.
In my games.cs file I add the follwing to the
GameConnection::onClientEnterGame(%this) funcion:
$totalBots = new SimGroup(totalBots);
$lockedBots = new SimGroup(lockedBots);
$unlockedBots = new SimGroup(unlockedBots);
These will be explained as we proceed.
Okay so whereever you spawn bots you need to add the bot object to the totalBots Sim group. In my funtion that gets invoked whenever I manually spawn bots (ctrl+b in my case) you can see that I add the newly created bot to the group. See below:
Okay so now everytime a bot is spawned we have him stored in our totalBots array. Cool!
Now on to the locking code!
Take the time now to bind the lock funtion to the key of your liking. In My case I used the 'L' key. So in my config.cs I added:
moveMap.bindCmd(keyboard, "l", "", "lockOn();");
and in my default.bind.cs I added:
moveMap.bindCmd(keyboard, "l", "", "lockOn();");
Okay now on to the server side player.cs file.
The first funtion I am going to add is the lockOn funtion that gets invoked when the "L" key is pressed. Here is the funtion with added comments. Note: I mark a bot as locked in this code by placing halo emitter on his mount1 node which happens to be on top of his head. You do not have to do this. I just did it for giggles. I will mark this code as optional in the following funtion. *Remember totalBots is populated in the script where the bots are spawned. *
Now we need to look at the implementation for BotStillInRange(%this, %bot ). This is pretty simple. Just a call to the AiPlayer's isObjectInView() function follwed by a schedule. Again the unmountImage only applies if you are using a mount point to mark the bot as locked. If the bot leaves the fov, unlock.
And finally the getClosestBot function. This function was borrowed from some of Stefan's code. In here
we merely determine which bot is closest among those in the unlockedBots group and return that bot.
One other note, I modified the AIPlayer::isObjectInView() funtion to accept a distance parameter. So here is my version:
Whew! Now on to the engine code.
The only files modified in the engine are player.cc and player.h. Really all I am doing is moving some
of the aiplayer functionality to the player class.
As for player.h , these lines are added:
In player.cc:
Add these lines to :
Add these three funtions:
Since we are taking over the players movement when we are locked we need to change the behavior of
ProcessTick so it now looks like this:
We need an aiMove for the player function since he is being controlled when we are locked:
And finally, expose some method to the console:
That is it. I am sure I have missed something so let me know if you hit some snags. It would also be nice to hear what could be done to improve this code.
:)
If I have omitted something please tell me and I will do my best to resolve the issue.
What does this code do? Well first you map a key which will be your target lock key. When this key is pressed the nearest bot is locked onto. Hitting the key again locks you to the next nearest bot and so on until there are no bots left to lock. If a bot is locked onto and leaves your predetermined locking distance he will automatically be unlocked. I will leave it up to the implementer to determine this distance. When a bot is locked in the code I am running currently, I place an emitter at mount1 of my bot model so i can see who is locked onto when there is a gang of bots. I marked this code as optional.
Issues:
At the moment dead bots can be locked onto. This is also a problem in Vice-City so I do not feel so bad. :) This should be easily solveable and I will post the fix later.
First things first, you must get some bots into your mission. To do this check out Stefan's excellent tutorial at:
Bots
For this tutorial the reader should know that I add my bots by using the ctrl + b keystroke. After the mission starts I manually add some bots that immediately start following waypoints. This is important because as bots are added I keep track of them by storing them in a sim group.
In my games.cs file I add the follwing to the
GameConnection::onClientEnterGame(%this) funcion:
$totalBots = new SimGroup(totalBots);
$lockedBots = new SimGroup(lockedBots);
$unlockedBots = new SimGroup(unlockedBots);
These will be explained as we proceed.
Okay so whereever you spawn bots you need to add the bot object to the totalBots Sim group. In my funtion that gets invoked whenever I manually spawn bots (ctrl+b in my case) you can see that I add the newly created bot to the group. See below:
[b]function serverCmdAddBot(%client)
{
%npcName = "Bot" @ $botCounter;
%bot = AIPlayer::spawnPlayer(%npcName);
$totalBots.add(%bot);
//%bot.setScanningPlayers(true);
echo( %bot.getShapeName() );
}[/b]Okay so now everytime a bot is spawned we have him stored in our totalBots array. Cool!
Now on to the locking code!
Take the time now to bind the lock funtion to the key of your liking. In My case I used the 'L' key. So in my config.cs I added:
moveMap.bindCmd(keyboard, "l", "", "lockOn();");
and in my default.bind.cs I added:
moveMap.bindCmd(keyboard, "l", "", "lockOn();");
Okay now on to the server side player.cs file.
The first funtion I am going to add is the lockOn funtion that gets invoked when the "L" key is pressed. Here is the funtion with added comments. Note: I mark a bot as locked in this code by placing halo emitter on his mount1 node which happens to be on top of his head. You do not have to do this. I just did it for giggles. I will mark this code as optional in the following funtion. *Remember totalBots is populated in the script where the bots are spawned. *
[b]function lockOn( )
{
echo("LOCKING ON TO TARGET");
%client = ClientGroup.getObject(0);
// Move all unlocked bots to total bots so we can
// testfor their proximity and whether or not they are
// in the FOV. New bots may have been added
// since the last time we were called so we need to
// start from scratch when determining
// the closest bot.
%i = $unlockedBots.getCount();
while( %i > 0 )
{
$totalBots.add( $unlockedBots.getObject( %i-1 ) );
echo( "Moving total bots to unlocked bots");
echo( "Total bot count = " @ $totalBots.getCount() );
echo( "Locked bot count = " @ $lockedBots.getCount() );
echo( "Unlocked bot count = " @ $unlockedBots.getCount() );
%i--;
}
// is the bot in and close enough? If so add the bot to the unlocked
// group
for( %i=0; %i < $totalBots.getCount(); %i++)
{
if( AIPlayer::isObjectInView( %client.player, $totalBots.getObject( %i ), 20 ) )
{
echo( "Adding bots to FOV unlocked");
$unlockedBots.add($totalBots.getObject( %i ));
}
echo( "Total bot count = " @ $totalBots.getCount() );
echo( "Locked bot count = " @ $lockedBots.getCount() );
echo( "Unlocked bot count = " @ $unlockedBots.getCount() );
}
// get the closest bot from those in the unlockedBots
// array.
%bot = %client.player.getClosestBot( );
// unlock currently locked bot
// ****************************************************
// OPTIONAL! This removes the halo emitter from the
// bots head
// ****************************************************
//%i = $lockedBots.getCount();
//while ( %i > 0 )
//{
// %lockedbot = $lockedBots.getObject( %i-1 );
// %lockedbot.unmountImage(0);
// echo( "unlocking current Bot");
// echo( "Total bot count = " @ $totalBots.getCount() );
// echo( "Locked bot count = " @ $lockedBots.getCount() );
// echo( "Unlocked bot count = " @ $unlockedBots.getCount() );
// %i--;
//}
// if we have a valid object lock on and schedule a function that
// determines if the bot is still in range
if( isObject( %bot ) )
{
echo( "Locking on Bot");
// ***********************************************************
// OPTIONAL! This adds the halo emitter from the bots head
// ***********************************************************
//%bot.mountImage(%data.image, 0);
//%bot.use(Halo);
$lockedBots.add( %bot );
%client.player.setAimObject( %bot );
echo( "Total bot count = " @ $totalBots.getCount() );
echo( "Locked bot count = " @ $lockedBots.getCount() );
echo( "Unlocked bot count = " @ $unlockedBots.getCount() );
// we need to schedule updates to see if this bot is out of range
Player::BotStillInRange( %client.player, %bot );
}
else
{
// no more bots left to lock. add the bots back into the
// totalbots group and wait for another lockOn
echo( "No bots left to lock!");
%client.player.setAimObject( 0 );
// we have exhausted all locked bots reset all
%i = $lockedBots.getCount();
while( %i > 0 )
{
echo( "Adding locked bots back to total bots");
$totalBots.add( $lockedBots.getObject( %i-1 ) );
echo( "Total bot count = " @ $totalBots.getCount() );
echo( "Locked bot count = " @ $lockedBots.getCount() );
echo( "Unlocked bot count = " @ $unlockedBots.getCount() );
%i--;
}
}
}[/b]Now we need to look at the implementation for BotStillInRange(%this, %bot ). This is pretty simple. Just a call to the AiPlayer's isObjectInView() function follwed by a schedule. Again the unmountImage only applies if you are using a mount point to mark the bot as locked. If the bot leaves the fov, unlock.
[b]function Player::BotStillInRange(%this, %bot )
{
if ( AIPlayer::isObjectInView( %this, %bot, 20 ) )
{
%this.schedule( 500, "BotStillInRange", %bot );
echo( "Bot still in view" );
}
else
{
%this.setAimObject( "" );
// ***********************************************************
// OPTIONAL! This removes the halo emitter from the bots head
// ***********************************************************
//%bot.unmountImage(0);
echo( "Bot left view" );
}
}[/b]And finally the getClosestBot function. This function was borrowed from some of Stefan's code. In here
we merely determine which bot is closest among those in the unlockedBots group and return that bot.
[b]function Player::getClosestBot(%this)
{
%playerPos = %this.getPosition();
for(%i = 0; %i < $unlockedBots.getCount(); %i++)
{
echo("Iterating bots");
%bot = $unlockedBots.getObject(%i);
%botPos = %bot.getLocation();
%tempDist = VectorDist(%playPos, %botPos);
if(%i == 0) {
%dist = %tempDist;
%closebot = %bot;
}
else {
if(%dist > %tempDist) {
%dist = %tempDist;
%closebot = %bot;
}
}
}
return %closebot;
}[/b]One other note, I modified the AIPlayer::isObjectInView() funtion to accept a distance parameter. So here is my version:
[b]function AIPlayer::isObjectInView(%this, %object, %dist)
{
%player = %this;
// default sight range of AI:
%this.sightRange = %dist;
if (!(isObject(%player) && isObject(%object)))
{
return false;
}
%objPos = %object.getWorldBoxCenter();
%eyePoint = %player.getWorldBoxCenter();
%distance = VectorDist(%objPos, %eyePoint);
error("**************DISTANCE FROM PLAYER = " SPC %distance);
error("**************sightRange = " SPC %this.sightRange);
if (%distance <= %this.sightRange)
{
// if the object is within 1.5 meters sometimes it can
// fall out of the field of view due to the eye height
if (%distance > 2)
{
%eyeTransform = %player.getEyeTransform();
%eyePoint = firstWord(%eyeTransform)
SPC getWord(%eyeTransform, 1)
SPC getWord(%eyeTransform, 2);
error("**************distance is greater thatn two");
}
%eyeVector = VectorNormalize(%player.getEyeVector());
//make sure we're not looking through walls...
%mask = $TypeMasks::TerrainObjectType |
$TypeMasks::InteriorObjectType |
$TypeMasks::StaticShapeObjectType;
%losResult = containerRayCast(%objPos, %eyePoint, %mask);
%losObject = GetWord(%losResult, 0);
if (!isObject(%losObject))
{
error("**************LOS!!");
//create the vector from this client to the client
%objVector = VectorNormalize(VectorSub(%objPos, %eyePoint));
// dot product to determine field of view
%dot = VectorDot(%objVector, %eyeVector);
error("**************DOT" @ %dot);
// within field of view
return (%dot > 0.6);
}
}
return false;
}[/b]Whew! Now on to the engine code.
The only files modified in the engine are player.cc and player.h. Really all I am doing is moving some
of the aiplayer functionality to the player class.
As for player.h , these lines are added:
[b]class Player: public ShapeBase
{
....
....
protected:
SimObjectPtr<GameBase> mAimObject; // Object to point at, overrides location
bool mAimLocationSet; // Has an aim location been set?
Point3F mAimLocation; // Point to look at
bool mTargetInLOS; // Is target object visible?
....
public:
....
// Targeting and aiming sets/gets
virtual void setAimObject( GameBase *targetObject );
virtual GameBase* getAimObject() const { return mAimObject; }
virtual void setAimLocation( const Point3F &location );
virtual Point3F getAimLocation() const { return mAimLocation; }
virtual void clearAim();
};[/b]In player.cc:
Add these lines to :
[b]Player::Player()
{
....
....
AimObject = 0;
mAimLocationSet = false;
mTargetInLOS = false;
}[/b]Add these three funtions:
[b]
/**
* Sets the object the player is targeting
*
* @param targetObject The object to target
*/
void Player::setAimObject( GameBase *targetObject )
{
mAimObject = targetObject;
mTargetInLOS = false;
}
/**
* Sets the location for the Player to aim at
*
* @param location Point to aim at
*/
void Player::setAimLocation( const Point3F &location )
{
mAimObject = 0;
mAimLocationSet = true;
mAimLocation = location;
}
/**
* Clears the aim location and sets it to the player's
* current destination so he looks where he's going
*/
void Player::clearAim()
{
mAimObject = 0;
mAimLocationSet = false;
}[/b]Since we are taking over the players movement when we are locked we need to change the behavior of
ProcessTick so it now looks like this:
[b]
void Player::processTick(const Move* move)
{
PROFILE_START(Player_ProcessTick);
// If we're not being controlled by a client, let the
// AI sub-module get a chance at producing a move.
Move aiMove;
if( move )
getAIMove((struct Move*) move );
else
{
if( getAIMove( &aiMove ) )
move = &aiMove;
}
...
...
...
}[/b]We need an aiMove for the player function since he is being controlled when we are locked:
[b]
/**
* This method calculates the moves for the AI player
*
* @param movePtr Pointer to move the move list into
*/
bool Player::getAIMove(Move *movePtr)
{
//*movePtr = NullMove;
// Use the eye as the current position.
Point3F location = getPosition();
Point3F rotation = getRotation();
// Orient towards the aim point, aim object, or towards
// our destination.
if (mAimObject || mAimLocationSet )
{
// Update the aim position if we're aiming for an object
if (mAimObject)
mAimLocation = mAimObject->getPosition();
F32 xDiff = mAimLocation.x - location.x;
F32 yDiff = mAimLocation.y - location.y;
if (!isZero(xDiff) || !isZero(yDiff))
{
// First do Yaw
// use the cur yaw between -Pi and Pi
F32 curYaw = rotation.z;
while (curYaw > M_2PI)
curYaw -= M_2PI;
while (curYaw < -M_2PI)
curYaw += M_2PI;
// find the yaw offset
F32 newYaw = mAtan( xDiff, yDiff );
F32 yawDiff = newYaw - curYaw;
// make it between 0 and 2PI
if( yawDiff < 0.0f )
yawDiff += M_2PI;
else if( yawDiff >= M_2PI )
yawDiff -= M_2PI;
// now make sure we take the short way around the circle
if( yawDiff > M_PI )
yawDiff -= M_2PI;
else if( yawDiff < -M_PI )
yawDiff += M_2PI;
movePtr->yaw = yawDiff;
// Next do pitch. This should be adjusted to run from the
// eye point to the object's center position. Though this
// works well enough for now.
F32 vertDist = mAimLocation.z - location.z;
F32 horzDist = mSqrt(xDiff * xDiff + yDiff * yDiff);
F32 newPitch = mAtan( horzDist, vertDist ) - ( M_PI / 2.0f );
Point3F headRotation = getHeadRotation();
movePtr->pitch = newPitch - headRotation.x;
}
}
else {
return false;
// Level out if we're not doing anything else
Point3F headRotation = getHeadRotation();
movePtr->pitch = -headRotation.x;
}
// Test for target location in sight if it's an object. The LOS is
// run from the eye position to the center of the object's bounding,
// which is not very accurate.
if (mAimObject) {
MatrixF eyeMat;
getEyeTransform(&eyeMat);
eyeMat.getColumn(3,&location);
Point3F targetLoc = mAimObject->getBoxCenter();
// This ray ignores non-static shapes. Cast Ray returns true
// if it hit something.
RayInfo dummy;
if (getContainer()->castRay( location, targetLoc,
InteriorObjectType | StaticShapeObjectType | StaticObjectType |
TerrainObjectType, &dummy)) {
if (mTargetInLOS) {
//throwCallback( "onTargetExitLOS" );
mTargetInLOS = false;
}
}
else
if (!mTargetInLOS) {
//throwCallback( "onTargetEnterLOS" );
mTargetInLOS = true;
}
}
// Replicate the trigger state into the move so that
// triggers can be controlled from scripts.
// beffy - commented out for Jimomighty stuff!
//for( int i = 0; i < MaxTriggerKeys; i++ )
// movePtr->trigger[i] = getImageTriggerState(i);
return true;
}[/b]And finally, expose some method to the console:
[b]
/**
* Tells the Player to aim at the location provided
*/
ConsoleMethod( Player, setAimLocation, void, 3, 3, "ai.setAimLocation( \"x y z\" );" )
{
Player *ai = static_cast<Player *>( object );
Point3F v( 0.0f,0.0f,0.0f );
dSscanf( argv[2], "%f %f %f", &v.x, &v.y, &v.z );
ai->setAimLocation( v );
}
/**
* Returns the point the AI is aiming at
*/
ConsoleMethod( Player, getAimLocation, const char *, 2, 2, "ai.getAimLocation();" )
{
Player *ai = static_cast<Player *>( object );
Point3F aimPoint = ai->getAimLocation();
char *returnBuffer = Con::getReturnBuffer( 256 );
dSprintf( returnBuffer, 256, "%f %f %f", aimPoint.x, aimPoint.y, aimPoint.z );
return returnBuffer;
}
/**
* Sets the bots target object
*/
ConsoleMethod( Player, setAimObject, void, 3, 3, "ai.setAimObject( obj );" )
{
Player *ai = static_cast<Player *>( object );
// Find the target
GameBase *targetObject;
if( Sim::findObject( argv[2], targetObject ) )
ai->setAimObject( targetObject );
else
ai->setAimObject( 0 );
}
/**
* Gets the object the AI is targeting
*/
ConsoleMethod( Player, getAimObject, S32, 2, 2, "ai.getAimObject();" )
{
Player *ai = static_cast<Player *>( object );
GameBase* obj = ai->getAimObject();
return obj? obj->getId(): -1;
}
/**
* Stop the player aiming
*/
ConsoleMethod( Player, clearAim, void, 2, 2, "ai.clearAim();" )
{
Player *ai = static_cast<Player *>( object );
ai->clearAim();
}[/b]That is it. I am sure I have missed something so let me know if you hit some snags. It would also be nice to hear what could be done to improve this code.
:)
#22
10/07/2007 (3:46 pm)
#23
10/07/2007 (4:03 pm)
Instead of collecting the aimove inside the Player::ProcessTick instead place it GameConnection::getNextMove. This way you wont frigging stutter as it naturaly puts the information in the interpolation history.bool GameConnection::getNextMove(Move &curMove)
{
....
Player* plr = dynamic_cast<Player*>(getControlObject());
if (plr)
{
plr->getAIMove(&curMove);
}
curMove.clamp(); // clamp for net traffic
return true;
}
#24
Is there a way to only allow players to lock on to targets within a cone of vision in front of them?
I have an action/adventure game played in 3rd person and would like to implement some auto targeting and thought this resource would be a good place to start. I wouldn't want to be running to the left, fire my weapon, and have the player auto 180 on me to shoot the guy at my back... Imagine the gameplay in Lego Star Wars. If you aren't familiar, I am sure you can find videos easily with a Google search...
07/21/2008 (3:45 pm)
If a player pushes the "lock on" button and an enemy is within range but out of view (either obstructed, around a corner, or maybe the enemy is actually BEHIND the player), will the player still turn to face the locked on target?Is there a way to only allow players to lock on to targets within a cone of vision in front of them?
I have an action/adventure game played in 3rd person and would like to implement some auto targeting and thought this resource would be a good place to start. I wouldn't want to be running to the left, fire my weapon, and have the player auto 180 on me to shoot the guy at my back... Imagine the gameplay in Lego Star Wars. If you aren't familiar, I am sure you can find videos easily with a Google search...
#25
TGE 1.5.2
I'm using this when the Weapon::onFireHandtoHand is called (Server side melee resource), so when the attacking player swings at an enemy he is lined up correctly to preform combos. I'm building a 3d side scroller rpg similar to Dragonica(Dragon Saga) and the setAimObject function is not working. I have the advanced camera resource and Cory Osborne's separate control objects resources also... and in there, I believe, lies my problem.
Searching for answers I found a bit here: http://www.garagegames.com/community/forums/viewthread/109999, that leads me to think that the control object being the camera, but passing the move functionality to the player is where I'm having problems.(if I stated that correctly. Might be just a separate object and I'm not understanding the functionality) The setAimObject isn't passing the control object correctly.
Now... my rediculious lack of coding ability; I'm an artist and I might have ADD. I can't seem to correctly find where I would remedy this...
The first place I think I have a problem was in the processTick where implementing the above code broke player movement all-together. I've tried various implementations with no luck.
Any direction would be greatly appreciated.
J
12/14/2011 (12:36 pm)
Need some help...TGE 1.5.2
I'm using this when the Weapon::onFireHandtoHand is called (Server side melee resource), so when the attacking player swings at an enemy he is lined up correctly to preform combos. I'm building a 3d side scroller rpg similar to Dragonica(Dragon Saga) and the setAimObject function is not working. I have the advanced camera resource and Cory Osborne's separate control objects resources also... and in there, I believe, lies my problem.
Searching for answers I found a bit here: http://www.garagegames.com/community/forums/viewthread/109999, that leads me to think that the control object being the camera, but passing the move functionality to the player is where I'm having problems.(if I stated that correctly. Might be just a separate object and I'm not understanding the functionality) The setAimObject isn't passing the control object correctly.
Now... my rediculious lack of coding ability; I'm an artist and I might have ADD. I can't seem to correctly find where I would remedy this...
The first place I think I have a problem was in the processTick where implementing the above code broke player movement all-together. I've tried various implementations with no luck.
Any direction would be greatly appreciated.
J

Torque Owner Berserk
I uploaded a patch to apply on a clean 1.4.2 TGE install on my website here, and I left this resource's code commented out.
Can you give me some hints on what I done wrong? Thanks in advance for anything.
Bye, Berserk.
.