Game Development Community

dev|Pro Game Development Curriculum

[T3D1.2] Turrets Control Vehicles (for Tank Turrets, etc.)

by Henry Todd · 06/05/2012 (2:53 am) · 15 comments

Basically this allows a TurretShape (the included Turret class from T3D 1.2) to be a "Controlling Object," which means you can use %turretObject.setControlObject(%someVehicle); to make a Turret the "pilot" of another object.

Check out a quick video of the Cheetah with a fully mobile turret:
www.youtube.com/watch?v=oVuhlVgyTXw

Download:
dl.dropbox.com/u/45455626/turretShape.zip
Replace your existing T3D/turret/turretShape.cpp and .h with the included files.

Do-it-yourself instructions:
Relatively simple code changes. All the code used is copied from player.cpp since the desired functionality is identical. You could pop this code into ShapeBase if you wanted, but I couldn't think of any other object type that would need it.

A couple changes in T3DturretturretShape.h.
Add this pointer that holds the object the turret is controlling to class TurretShape:
// [HNT] Turret Control Object
   SimObjectPtr<ShapeBase> mControlObject; ///< Controlling object
   // [/HNT]
You can put it wherever you like in theory, but I put it in protected right after TurretShapeData* mDataBlock;

Now all the new functions. I placed these all at the end of the class declaration, right before "DECLARE_CONOBJECT(TurretShape);"
// [HNT] Turret Control Object
   // Object control
   void setControlObject(ShapeBase *obj);
   ShapeBase* getControlObject();
   /// Set which client is controlling this player
   void setControllingClient(GameConnection* client);
   void onCameraScopeQuery(NetConnection *cr, CameraScopeQuery *);
   // [/HNT]

Now changes in T3D/turret/turretShape.cpp. Be aware that the line numbers I've included assume a clean turretShape.cpp and that you're making the changes in order. I've also described the locations in case your line numbers don't match.

Initialize our new variable in TurretShape::TurretShape (about line 276).
// [HNT] Turret Control Object
   mControlObject = 0;
   // [/HNT]
Unset the control object if this object is deleted in TurretShape::Remove (about line 321).
// [HNT] Turret Control Object
   setControlObject(0);
   // [/HNT]

Add these new functions (wherever you like -- I put them at about line 558, right before TurretShape::processTick as that's where they were in player.cpp):
// [HNT] Turret Control Object
//----------------------------------------------------------------------------

void TurretShape::setControllingClient(GameConnection* client)
{
   Parent::setControllingClient(client);
   if (mControlObject)
      mControlObject->setControllingClient(client);
}

void TurretShape::setControlObject(ShapeBase* obj)
{
   if (mControlObject == obj)
      return;

   if (mControlObject) {
      mControlObject->setControllingObject(0);
      mControlObject->setControllingClient(0);
   }
   if (obj == this || obj == 0)
      mControlObject = 0;
   else {
      if (ShapeBase* coo = obj->getControllingObject())
         coo->setControlObject(0);
      if (GameConnection* con = obj->getControllingClient())
         con->setControlObject(0);

      mControlObject = obj;
      mControlObject->setControllingObject(this);
      mControlObject->setControllingClient(getControllingClient());
   }
}

void TurretShape::onCameraScopeQuery(NetConnection *connection, CameraScopeQuery *query)
{
   // First, we are certainly in scope, and whatever we're riding is too...
   if(mControlObject.isNull())// || mControlObject == mMount.object)
      Parent::onCameraScopeQuery(connection, query);
   else
   {
      connection->objectInScope(this);
      if (isMounted())
         connection->objectInScope(mMount.object);
      mControlObject->onCameraScopeQuery(connection, query);
   }
}

ShapeBase* TurretShape::getControlObject()
{
   return mControlObject;
}
// [/HNT]

Right below there in processTick, add this bit of code right before "Parent::processTick(move);" at about 657:
// [HNT] Turrets can control other objects
   // Manage the control object and filter moves for the player
   Move pMove,cMove;
   if (mControlObject) {
      if (!move)
         mControlObject->processTick(0);
      else {
         pMove = NullMove;
         cMove = *move;
         //if (isMounted()) {
            // Filter Jump trigger if mounted
            //pMove.trigger[2] = move->trigger[2];
            //cMove.trigger[2] = false;
         //}
         if (move->freeLook) {
            // Filter yaw/picth/roll when freelooking.
            pMove.yaw = move->yaw;
            pMove.pitch = move->pitch;
            pMove.roll = move->roll;
            pMove.freeLook = true;
            cMove.freeLook = false;
            cMove.yaw = cMove.pitch = cMove.roll = 0.0f;
         }
         mControlObject->processTick((mDamageState == Enabled)? &cMove: &NullMove);
         move = &pMove;
      }
   }
   // [/HNT]

Then in TurretShape::InterpolateTick put this at the start of the function (~719):
// [HNT]
   if (mControlObject)
   mControlObject->interpolateTick(dt);
   // [/HNT]
Similar bit in TurretShape::advanceTime, this time right before Parent::advanceTime at about 763:
// [HNT] Turret Control Object
   if (mControlObject)
      mControlObject->advanceTime(dt);
   // [/HNT]

This goes at the end of TurretShape::writePacketData (~1131):
// [HNT] Turret Control Object
   if (mControlObject) {
      S32 gIndex = connection->getGhostIndex(mControlObject);
      if (stream->writeFlag(gIndex != -1)) {
         stream->writeInt(gIndex,NetConnection::GhostIdBitSize);
         mControlObject->writePacketData(connection, stream);
      }
   }
   else
      stream->writeFlag(false);
   // [/HNT]
And at the end of TurretShape::readPacketData (~1157):
// [HNT] Turret Control Object
   if (stream->readFlag()) {
      S32 gIndex = stream->readInt(NetConnection::GhostIdBitSize);
      ShapeBase* obj = static_cast<ShapeBase*>(connection->resolveGhost(gIndex));
      setControlObject(obj);
      obj->readPacketData(connection, stream);
   }
   else
      setControlObject(0);
   // [/HNT]
And to finish off this file put these new console functions at the end of turretShape.cpp:
// [HNT] Turret Control Object
DefineEngineMethod( TurretShape, setControlObject, bool, ( ShapeBase* obj ),,
   "@brief Set the object to be controlled by this player\n\n"

   "It is possible to have the moves sent to the Player object from the "
   "GameConnection to be passed along to another object.  This happens, for example "
   "when a player is mounted to a vehicle.  The move commands pass through the Player "
   "and on to the vehicle (while the player remains stationary within the vehicle).  "
   "With setControlObject() you can have the Player pass along its moves to any object.  "
   "One possible use is for a player to move a remote controlled vehicle.  In this case "
   "the player does not mount the vehicle directly, but still wants to be able to control it.\n"

   "@param obj Object to control with this player\n"
   "@return True if the object is valid, false if not\n"

   "@see getControlObject()\n"
   "@see clearControlObject()\n"
   "@see GameConnection::setControlObject()")
{
   if (obj) {
      object->setControlObject(obj);
      return true;
   }
   else
      object->setControlObject(0);
   return false;
}

DefineEngineMethod( TurretShape, getControlObject, S32, (),,
   "@brief Get the current object we are controlling.\n\n"
   "@return ID of the ShapeBase object we control, or 0 if not controlling an "
   "object.\n"
   "@see setControlObject()\n"
   "@see clearControlObject()")
{
   ShapeBase* controlObject = object->getControlObject();
   return controlObject ? controlObject->getId(): 0;
}

DefineEngineMethod( TurretShape, clearControlObject, void, (),,
   "@brief Clears the player's current control object.\n\n"
   "Returns control to the player. This internally calls "
   "Player::setControlObject(0).\n"
   "@tsexample\n"
		"%player.clearControlObject();\n"
      "echo(%player.getControlObject()); //<-- Returns 0, player assumes control\n"
      "%player.setControlObject(%vehicle);\n"
      "echo(%player.getControlObject()); //<-- Returns %vehicle, player controls the vehicle now.\n"
	"@endtsexample\n"
   "@note If the player does not have a control object, the player will receive all moves "
   "from its GameConnection.  If you're looking to remove control from the player itself "
   "(i.e. stop sending moves to the player) use GameConnection::setControlObject() to transfer "
   "control to another object, such as a camera.\n"
   "@see setControlObject()\n"
   "@see getControlObject()\n"
   "@see GameConnection::setControlObject()\n")
{
   object->setControlObject(0);
}
// [/HNT]
You can remove the original usage info if you like, or at least update it to not reference Player.

And you can now make a turret control a vehicle (or anything else in theory) using "%someTurret.setControlObject(%someVehicle);"

I may update the resource with some basic example implementation later, but for now if you want to try out the functionality you will need: A vehicle with a mount point, and a TurretShape object with an "eye" node (or else it'll use the origin). Load a mission and follow these steps:
-Create a Vehicle and a TurretShape (not an AITurretShape).
-Mount the TurretShape to the Vehicle like this: vehicleID.mountObject(turretID, mountPoint);
-Set the TurretShape's control object to be the vehicle: turretID.setControlObject(vehicleID);
-Set your client's control object to be the turret: playerID.client.setControlObject(turretID);

You should now be looking out from the TurretShape's eye node but controlling the vehicle. If you want to move the turret, use the freelook key (default 'V') and move the mouse.

A couple known issues I'll try to solve (or maybe someone else can spot them):
-Wheels don't spin for you when you're driving from the turret. SOLVED
-Turret rotation is a little stuttery if you have vsync on, even if you're at a stable 60fps. Not really related to this code but kind of an annoying issue with the turrets in general.
-Turrets themselves kind of stutter when mounted to an object that another object is controlling (ie a player is driving the vehicle a turret is mounted to). This is also not related to this code but needs to be fixed, so I'll be looking into it.
-Turret/Vehicle combos look like they're using big spikes of bandwidth. This appears to be connected to the second issue above (stutter w/ vsync) and isn't actually an issue with excessive bandwidth usage. Apparently with vsync on something is causing the turret's updates (network or tick processing, don't know) to stack up and happen all at once. So instead of calling a network update every tick, it's saving up like 10 of them and calling them 3-4 times a second. So still not an issue with this code, but again needs to be fixed.

And finally one note about the old Paul Dana turrets and this code: I think it should work. Just find equivalent spots in that code to slot in the bits above and I imagine it'll work just fine.

#1
06/05/2012 (4:38 am)
Cool stuff, Henry. :)
#2
06/05/2012 (11:46 am)
Thanks. I'm working on fixing the couple existing issues, after which it should be ready to actually use (hopefully).

**Big edit:
Tracked the issue to what technically seems to be a bug with Player's setControlObject implementation that I ended up just copying over.

When you set an object as controlling another, the second object's tick processing is disabled. The object controlling it is then expected to handle its tick processing itself (as we see in cases like mControlObject->processTick). For some reason, advanceTime is never included.

To be honest I have no idea why the animation and sounds *ever* work properly with this bug in here. You should never see wheel animations when driving a vehicle through player control (and this was the case in an earlier version of T3D). So the only thing I'm slightly concerned about is if processTick, advanceTime, etc. are getting doubled up when this code is correct *and* something else starts the controlled object processing its ticks on its own. I may revisit that later just out of curiousity and for the sake of optimization.

Anyway, the resource has been updated with this fix (just 2 lines) and a couple other minor fixes that weren't causing any issues but were bad form (not initializing mControlObject, not doing interpolateTick for the controlled object even though this seems to make no difference to a client's control object).
#3
06/08/2012 (7:51 am)
can't download that file.....
#4
06/08/2012 (10:32 am)
Just tried it here, works fine. Here's an alternative link you can try:
ubermonkey.org/torque/turretShape.zip
#5
06/09/2012 (8:00 pm)
that is super good!!!!
#6
06/15/2012 (5:11 am)
Ditto, this is good. I never could figure out why the Cheetah had a fixed turret (kind of lame) as well as why the Alien Weapons use the same ammo and projectiles as the Soldier Weapons.

Made engine code changes and am hitting the wall on the creation of the TurretShape....

Care to share your script .cs file used to create TurretShape.

I have my vehicle with a seperate turret with an eye node but cannot find any examples for creating a TurretShape (AITurretShape yes)

Thanks !
#7
06/15/2012 (1:36 pm)
Here's the datablock I used in that example video:

datablock TurretShapeData(CheetahTurret)
{
   category = "Turrets";
   shapeFile = "art/shapes/cheetah/temp_turret.dae"; //replaceme

   maxDamage = 1000;
   destroyedLevel = 1000;
   explosion = GrenadeExplosion;
   
   simpleServerCollision = false;

   zRotOnly = false;
   
   // Rotation settings
   minPitch = 35;
   maxPitch = 35;
   maxHeading = 360; // full rotation
   headingRate = 90; // max turn rate
   pitchRate = 90;

   // Weapon mounting
   numWeaponMountPoints = 1;

   weapon[0] = AITurretHead; // replace these if you have a weapon you want to use
   weaponAmmo[0] = AITurretAmmo;
   weaponAmmoAmount[0] = 10000;

   maxInv[AITurretHead] = 1;
   maxInv[AITurretAmmo] = 10000;
   
   startLoaded = true;
   numMountPoints = 1;
};

You'll need to replace the shapeFile with something you have. I think I'll create a simple example turret model and upload it with the code since the one that comes with the engine isn't really appropriate for this.

To set up your own, check out this: http://docs.garagegames.com/torque-3d/reference/classTurretShape.html

Also, if you just use this datablock you'll probably find that the turret won't fire straight. You need to go to AITurretHead and set "correctMuzzleVector = false;"
#8
06/20/2012 (7:26 pm)
HNGamers file mirror
abighole.hngamers.com/1HfJa
#9
09/02/2012 (10:19 am)
OMG - the WheeledVehicleData had useEyePoint = true, once set to false this now works (I can't believe this took this many hours)....
The next logical step would be to eliminate the "V" so that the vehicle is controlled by WASD and the turret by the mouse (damn eyepoint!)


Henry, (nevermind, solved above)

Any chance you left out some camera related code?

Everything works except when freelook to rotate the turret the camera does not follow. I have attached the cam and eye node to the turret and made them a child of heading (no joy), a child of pitch (no joy) - and every possible combination (cam and eye, just cam, just eye), the camera/view remains pointing straight ahead.

I also integrated the changes into the PHDana turret.cc and have the same results - turret turns but camera/view remains straight ahead.

I have to be doing something not quite right, just have no idea what it is.

Has anyone else implemented this ?
#10
10/01/2013 (3:22 pm)
Anybody get this working?

I've rigged a turret with all the requisite nodes, and can control the vehicle with it and it fires correctly, but I can't get the turret to pivot. Any ideas?

FWIW I don't see where the 'v' key works in 1.2+ ...
Even in a fresh 'Full' template the command doesn't do anything...
#11
10/02/2013 (8:23 pm)
So I've traced the 'v' key issue to hardware - now I can mount the turret, it controls the cheetah, all the vehicle funtionality works, but it doesn't seem as if $mvFreeLook is working to pivot the turret...
#12
10/04/2013 (10:29 am)
@gibby,
i tried this resource 7/8 month ago.
that time,i failed to rotate turret head(as shown in the video) with my mouse.
can u rotate your turret ?
#13
10/05/2013 (8:07 am)
@ahsan:

I have it all working, controlling the vehicle via the turret, but the turret won't rotate...
#14
01/27/2014 (4:52 pm)
A question for you guys.

Has anyone successfully made a turret mount point (proxy) that can uses different objects?

And depending on the object type will change how that mount point acts. Thus if you mount a fixed cannon or missile launcher it only points straight ahead or the direction the object model has been made, yet mount a turret, it has a set of rotational limited based on the turret's type.

And to add to this, has anyone made a turret with interchangeable weapon mounting points? Not seen anything in search, I ask as looking at designing weapon hard-points on ships, and looking at having options available for players to mix and match what they can mount within limits for both the hard-points and also the turret hard points.
#15
05/31/2014 (1:31 pm)
I'll be trying to fix the stuttering/excessive network usage bug soon. It's apparently an issue with the packet checksum causing writePacketData to always be called when you're moving the turret.

It seems like a lot of people had trouble with their turrets, but I have to think that this is an issue with the turret models in use (don't have eye point, just not set up properly, etc) as I've been using this code for a long time now without issue. I'll still make sure to go through and check for issues while I'm trying to fix the writePacketData issue.

---

EDIT (1/9/2015)
I did fix the stuttering issue, forgot to mention it here. Applies to all turrets, not just ones on vehicles.

www.garagegames.com/community/forums/viewthread/130721