Game Development Community

TGB turret behavior

by Shaderman · 04/25/2009 (9:45 am) · 13 comments

Hi,

I just want to share this small TGB turret behavior. The video below shows it in action with a slightly modified Behavior Shooter project.



This behavior uses functions written by Tom Ogburn and James Ford (thank you guys!). Some parameters can be tweaked to suit your needs. I didn't comment the code to avoid confusing myself :)

//---------------------------------------------------------------------------------------------
// TGB Turret behavior
// Put together by Shaderman
// This behavior uses code written by Tom Ogburn and James Ford
//---------------------------------------------------------------------------------------------

if (!isObject(TurretBehavior))
{
   %template = new BehaviorTemplate(TurretBehavior);
   
   %template.friendlyName = "Turret";
   %template.behaviorType = "AI";
   %template.description  = "Shoots automatically at a target";

   %template.addBehaviorField(target, "The target object to face and shoot at", object, "", t2dSceneObject);
   %template.addBehaviorField(angularSpeed, "The rotation speed", float, "45.0");
   %template.addBehaviorField(tickFreq, "The delay in ms between rotation updates", integer, "200");
   %template.addBehaviorField(range, "The activity range (rotate and shoot when the target is within this range). 0 means there's no limit.", integer, "20");

   %template.addBehaviorField(projectile, "The projectile to shoot", object, "", t2dSceneObject);
   %template.addBehaviorField(projectileLayer, "The projectile layer (0-31)", integer, "", "0");
   %template.addBehaviorField(projectileEff, "The projectile effect", object, "", t2dSceneObject);
   %template.addBehaviorField(speed, "The projectile speed", float, "10.0");
   %template.addBehaviorField(angle, "Objects within this angle will be shot", integer, "45");
   %template.addBehaviorField(numShots, "The number of shots fired in a row", integer, "3");
   %template.addBehaviorField(shotDelay, "The delay between shots fired in a row", integer, "3000");
   %template.addBehaviorField(reloadTime, "The reload time", integer, "5000");
}

function TurretBehavior::onLevelLoaded(%this, %scenegraph)
{
   if (!isObject(%this.target) || !%this.target.getVisible())
      return;

   %this.isReloaded = true;
   %this.rotationSchedule = %this.schedule(%this.tickFreq, "rotate");
}

function TurretBehavior::reload(%this)
{
   %this.isReloaded = true;
}

function TurretBehavior::rotate(%this)
{
   if (!isObject(%this.target) || !%this.target.getVisible())
   {
      %this.rotationSchedule = %this.schedule(%this.tickFreq, "rotate");
      return;
   }

   if(%this.range $= 0)
      %this.dist = %this.range;
   else
      %this.dist = t2dVectorDistance(%this.owner.getPosition(), %this.target.getPosition());

   if(%this.dist <= %this.range)
   {
      %rotateDir = shortestDirection(%this.owner.getRotation(), %this.target.getRotation());
      %directionVectorX = %this.target.getpositionX() - %this.owner.getpositionX();
      %directionVectorY = %this.target.getpositionY() - %this.owner.getpositionY();
      
      %angle = 180 - mRadtoDeg(maTan(%directionVectorX , %directionVectorY));
      
      %this.owner.rotateTo(%angle, %this.angularSpeed * %rotateDir, true, true, true, 2);
   }
   
   %this.rotationSchedule = %this.schedule(%this.tickFreq, "rotate");
}

function TurretBehavior::onRotationTarget(%this)
{
   cancel(%this.rotationSchedule);

   %this.shotsFired = 0;
   %this.schedule(%this.shotDelay, "shoot");
}

function TurretBehavior::shoot(%this)
{
   if(!%this.isReloaded)
   {
      %this.rotationSchedule = %this.schedule(%this.tickFreq, "rotate");
      return;
   }
   
   if(%this.shotsFired >= %this.numShots)
   {
      %this.shotsFired = 0;
      %this.isReloaded = false;
      %this.schedule(%this.reloadTime, "reload");
      %this.rotationSchedule = %this.schedule(%this.tickFreq, "rotate");

      return;
   } else {
      %origin = %this.owner.getPosition();
      %fvec = rotationToVector( %this.owner.getRotation() );
      %targetPos = %this.target.getPosition();
      
      %inRange = pointInSight( %origin, %fvec, %targetPos, %this.angle );
      if(!%inRange)
         return;

      %pro = %this.projectile.cloneWithBehaviors();
      %pro.position = %this.owner.getPosition();
      %pro.rotation = %this.owner.getRotation();
      %pro.scenegraph = %this.owner.getScenegraph();
      %pro.setLayer(%this.projectileLayer);
      
      if(isObject(%this.projectileEff))
      {
         %effect = %this.projectileEff.cloneWithBehaviors();
         %effect.scenegraph = %this.owner.getScenegraph();
         %effect.setLayer(%this.projectileLayer);
         %effect.mountToLinkpoint(%pro, 1, 0, true , true, true, true);
      }

      %force = VectorScale(%fvec, %this.speed);
      %pro.setConstantForce(getWord(%force, 0), getWord(%force, 1), false);
      
      %this.shotsFired++;
      %this.schedule(%this.shotDelay, "shoot");
   }
}

// This function was written by Tom Ogburn
// http://www.garagegames.com/community/forums/viewthread/37261
function shortestDirection(%currentAngle, %targetAngle)
{
	//Default to clockwise if equi-distant or if it is shorter
	%direction = 1;

	%angleDifference1 = mabs(%currentAngle - %targetAngle);
	%angleDifference2 = mabs((%currentAngle + 360) - %targetAngle);

	if (%angleDifference1 > %angleDifference2)
	{
		//Go counter-clockwise
		%direction = -1;
	}

	return %direction;
}

// The following code was written by James Ford
// http://www.garagegames.com/community/forums/viewthread/77232
//
// origin@ The position of the agent (t2dVector)
// fvec@ The forward vector of the agent (t2dVector)
// point@ The point to test (t2dVector)
// maxAngle@ The agent's max view angle in degrees (F32)
function pointInSight( %origin, %fvec, %point, %maxAngle )
{
   %toPoint = t2dVectorNormalise( t2dVectorSub( %point, %origin ) );
   %dot = t2dVectorDot( %toPoint, %fvec );
   %angle = mRadToDeg( mAcos( %dot ) );

   return %angle < %maxAngle;  
}

function rotationToVector( %rotation )
{
   // "0 -1" is the vector straight up in TGB eg. it corresponds to a rotation of zero, 
   // so rotate it by your objects rotation and you get your objects forward vector.

   %fvec = rotateVector( "0 -1", %rotation );
   return %fvec;
}

function rotateVector( %vector, %angle )
{
   %angle = mDegToRad(%angle);

   %x = getWord(%vector,0);
   %y = getWord(%vector,1);

   %nx = mCos(%angle) * %x - mSin(%angle) * %y;
   %ny = mSin(%angle) * %x + mCos(%angle) * %y;

   return %nx SPC %ny;
}

#1
04/25/2009 (10:40 am)
Hi Stefan,

Thank you for sharing this! It works fantastic!

One thing I noticed is that if the target starts or exits outside of the turret range, it won't start tracking, when the target enters that range.

I'm trying to see if I can figure out how to make it pick the target up again, upon entering the range.

Thanks again, this is GREAT stuff!

#2
04/25/2009 (5:55 pm)
very nice sman as always!
#3
04/25/2009 (11:48 pm)
@Johnny sorry, never noticed that. Just change the code between the lines 56-65 from

if(%this.dist > %this.range)
      return;

   %rotateDir = shortestDirection(%this.owner.getRotation(), %this.target.getRotation());
   %directionVectorX = %this.target.getpositionX() - %this.owner.getpositionX();
   %directionVectorY = %this.target.getpositionY() - %this.owner.getpositionY();

   %angle = 180 - mRadtoDeg(maTan(%directionVectorX , %directionVectorY));

   %this.owner.rotateTo(%angle, %this.angularSpeed * %rotateDir, true, true, true, 2);

to

if(%this.dist <= %this.range)
   {
      %rotateDir = shortestDirection(%this.owner.getRotation(), %this.target.getRotation());
      %directionVectorX = %this.target.getpositionX() - %this.owner.getpositionX();
      %directionVectorY = %this.target.getpositionY() - %this.owner.getpositionY();
      
      %angle = 180 - mRadtoDeg(maTan(%directionVectorX , %directionVectorY));
      
      %this.owner.rotateTo(%angle, %this.angularSpeed * %rotateDir, true, true, true, 2);
   }

I've updated the original code above as well.

@Andrew thanks as always ;)
#4
04/27/2009 (7:39 am)
Cool behavior ;) If you have time you should add it to the TDN behavior list.
#5
04/27/2009 (9:14 am)
Works like a charm! Thanks again, Shaderman!
#6
04/27/2009 (9:32 am)
@Joe thanks, I was only able to add a link on TDN to this resource page. AFAIK Michael Perry and others are still working on TDN and uploads / TDN templates don't seem to work at the moment.

@Johnny you're welcome :)
#7
10/03/2009 (8:11 am)
hi,
cool resource!
BTW is there any way to shoot at different targerts? if this comes across as a stupid q then pls xcuse as am a noob!
#8
10/03/2009 (11:08 am)
Quote:BTW is there any way to shoot at different targerts?

Sorry, not with this behavior - you'd need to extend it.
#9
10/05/2009 (12:49 am)
hmmm...thought as much...thanks anyways.
#10
11/26/2009 (10:20 pm)
If you create a behavior or class that adds the owner object to a globally accessible simset, you can modify this behavior to pick its targets from the simset.

Create Simset

$globalset = new SimSet(TargetList);

Example, you tell the objects you want as a target

$globalset.add(%this.owner);

change the behavior's target to

%this.target = $globalset.getobject(n);

Plus, you can have it cycle through potential targets by

n++

Once I work out all of the kinks I'll post up the modified behavior. BTW, this behavior rocks.



#11
11/27/2009 (7:32 am)
I've made a small change to the behavior because there was a static setLayer(3) call used for the projectiles. I've added a behavior field projectileLayer which affects the projectile (and its effect if there is one assigned) and defaults to 0.

For multiple targets I'd either add a behavior field holding a classname of targets or write a separate turret target behavior and assign it to the target objects (something like the deal damage / take damage behavior).
#12
08/31/2011 (3:07 pm)
Is there any way to use this with the platformer starter kit? I have been trying without much success.
#13
10/09/2011 (10:49 pm)
I'm trying to figure out how I get objects in the behavior drop down. For instance how do I add an object I made: $pball to be tracked by the turret? and added to the drop down of objects.

thanks!
-nic