Game Development Community

Implementing an AI camera in T3D

by K. Scott Piel · in Torque 3D Professional · 04/28/2010 (10:30 am) · 10 replies

Let me start by apologizing for exposing my total noobness here, but I'm under a ton of pressure to deliver on a proof of concept by COB tomorrow (4/29) and I'm brand spanking new to Torque (less than a week into it) and a bit lost. I've been reading for hours, but am either too dense to "see" what I'm looking for or just looking in the wrong place.

In a nutshell... I need to set up an AI camera view. A third person free floating camera. I've set up a path as per an AIPlayer but form there I seem to be a little lost how to spawn the camera on the path and have it move through the nodes smoothly while controlling the view angle, etc. We want to allow the user to freely move an avatar through the scene, but we also want to set up an alternate camera that flies them through a predefined view of the scene.

Is this possible, or am I attempting something that can't really be done in T3D. Any guidance, or links to tutorials, etc., would be greatly appreciated.

Scott.

About the author

Senior Software Engineer and generic geek


#1
04/28/2010 (10:48 am)
Have a look in scripts/server/gamecore.cs, it has the commands for spawning either a camera or a player but also how the camera attaches to the player's eyenode and how control object is initially set. Also there's camera.cs in both scripts and core.

I've not tried mounting the camera on an AI's eyenode before ... but I've mounted it on a static waypoint.

I used a custom camera function in core camera.cs after commenting out most of scripts camera.cs.

function postDeceased(%client, %obj)
{
%client = %obj.getControllingClient();
if(isObject(controlnode))
{
%transform = controlnode.getTransform();
%obj.setOrbitMode(controlnode, %transform, 0.0, 0.0, 0.0, false, 0, true);
}
else
{
echo("No Control Node Present");
}
}
#2
04/28/2010 (10:55 am)
Thanks.

I tried mounting the camera on an AI but is appears the spline function for the path doesn't work correctly in that case... the AI moves to the next node and "jerks" around the "corner" at that node before continuing a straight line to the next node.

Perhaps the problem is in how I'm setting up the AI Player?

(points at his shiny, very pointy, "I'm a noob" hat ~smile~)
#3
04/28/2010 (11:06 am)
Try reducing AI speed/run force, increasing drag/mass, they might be missing the centre of the node as they sprint along.
#4
04/28/2010 (11:18 am)
Awesome -- will do and thank you for the feedback and patience.
#5
04/28/2010 (1:48 pm)
As I remember the radius for AI reaching their destination is low (0.1 or something) so they can miss it if they're going real fast (and default speed is pretty fast), so if they just miss the centre they end up spinning around until they get it. You'd need source code to change the radius.
#6
04/29/2010 (6:54 am)
If I may follow up on this from yesterday, is there a way to have a fluid camera animation play through a series of camera bookmarks?
#7
04/29/2010 (8:55 am)
Try this...

Put this script code in a .cs file, e.g. pathcam.cs, and place the file in your ./scripts/server folder.

//-----------------------------------------------------------------------------
// Path Camera
//-----------------------------------------------------------------------------

datablock PathCameraData(LoopingCam)
{
   mode = "";
};

function LoopingCam::onNode(%this,%camera,%node)
{
   if (%node == %camera.loopNode) {
      %camera.pushPath(%camera.path);
      %camera.loopNode += %camera.path.getCount();
   }
}

function PathCamera::followPath(%this,%path)
{
   %this.path = %path;
   if (!(%this.speed = %path.speed))
      %this.speed = 5;
   if (%path.isLooping)
      %this.loopNode = %path.getCount() - 2;
   else
      %this.loopNode = -1;

   %this.pushPath(%path);
   %this.popFront();
}

function PathCamera::pushPath(%this,%path)
{
   for (%i = 0; %i < %path.getCount(); %i++)
      %this.pushNode(%path.getObject(%i));
}

function PathCamera::pushNode(%this,%node)
{
   if (!(%speed = %node.speed))
      %speed = %this.speed;
   if ((%type = %node.type) $= "")
      %type = "Normal";
   if ((%smoothing = %node.smoothingType) $= "")
      %smoothing = "Linear";
   %this.pushBack(%node.getTransform(),%speed,%type,%smoothing);
}

new PathCamera(myCam) {
   dataBlock = LoopingCam;
   position = Path1.getObject(0).getPosition();
};

function startCam()
{
   myCam.reset(0);
   myCam.followPath(Path1);
   LocalClientConnection.setControlObject(myCam);
}

function stopCam()
{
   LocalClientConnection.setControlObject(LocalClientConnection.player);
   myCam.reset(0);
}

In T3D, create a path named Path1 (that happens to be hardcoded in the above script). Create your markers that define the path and add them to Path1. The camera will look down the positive y-axis of the marker, so rotate them to the desired orientation (changing the World Editor from World Transform to Object Transform will help see the local axis gizmo). Save the mission. Now we need to execute the above script when the game starts, so at the end of the GameConnection::onClientEnterGame() function in ./scripts/server/game.cs, add
exec("./pathcam.cs");
Save the file.

Now when you start the game, you can type "startCam()" in the console and your pathcamera will take control. Simply type "stopCam()" to return control to your player. Obviously you can control the starting and stopping via script in repsonse to triggers, etc. The default speed is 5, which is somewhat fast. You can give each marker its own speed by defining a dynamic parameter named, "speed", for each marker you want different than default.
#8
04/29/2010 (9:21 am)
Ryan -- that was 100% perfect... worked like a charm. Thank you.

I'd offer to have your children and stuff... but... yeah... nevermind.

~lol~

Sincerely, thank you both for your help.
#9
04/29/2010 (10:01 am)
Great, glad that worked for you. I didn't catch this at first, but I would suggest adding the following to the stopCam() function:

function stopCam()
{
   LocalClientConnection.setControlObject(LocalClientConnection.player);
   myCam.reset(0);   // <-- Added this!
}

Otherwise the pathcam will still be computing in the background when you toggle back to your player. Just a little cleaner and more efficient this way. :) I'm going to change this in my original post above in case anyone else wants to try this.
#10
05/03/2010 (5:51 am)
Let me start this post by, again, saying thank you to you both for your input. Everything worked out well and we're on our way. I have soooooooo much to learn about how to effectively use Torque, but I'm looking forward to the challenge.

For the benefit of those who may be reading over our shoulders, or who come across this at a later date and time, I wanted to post the final code I used.

There were a few minor issues with the code posted (or there seemed to be). First, I wanted to be able to have multiple paths the cam could follow and to specify the path in a call. Second, I needed the ability to pause the camera at any point on the path and allow the player to assume control from that point and then be able to resume from that point. Third, I found that the camera was actually stopping the path one node early. Forth, the camera would not change speed between nodes, it seemed to run with whatever the default speed was at all times. Finally, and this one was somewhat more serious, I found that the original code crashed after 40 some odd nodes had been executed (regardless of how many were in a single loop) allowing the camera to just drift off to a random point in space, so I modified the code to reset the camera at the first node in each iteration so that as long as the path does not exceed 40 nodes, the camera won't crash.

What follows is the resulting code for the AI pathed camera:

//-----------------------------------------------------------------------------
// Path Camera
//-----------------------------------------------------------------------------

datablock PathCameraData(LoopingCam)
{
   mode = "";
};

function LoopingCam::onNode( %this, %camera, %node )
{
   %nodeCfg      = %camera.path.getObject(%node);
   %nodeSpeed    = %nodeCfg.speed;   
   %camera.speed = %nodeSpeed ? %nodeSpeed : %camera.speed;
   
   if( %node == %camera.loopNode ) 
   {
      %camera.reset( 0 );      
      %camera.pushPath( %camera.path );
      %camera.loopNode = %camera.path.getCount();
   }
}

function PathCamera::followPath( %this, %path )
{
   %this.path  = %path;
   %this.speed = %path.speed ? %path.speed : 2;

   if (%path.isLooping) %this.loopNode = %path.getCount() - 1;
   else                 %this.loopNode = -1;

   %this.pushPath( %path );
   %this.popFront();
}

function PathCamera::pushPath( %this, %path )
{
   for( %i = 0; %i < %path.getCount(); %i++ )
      %this.pushNode( %path.getObject(%i) );
}

function PathCamera::pushNode( %this, %node )
{
   %speed     = %node.speed ? %node.speed : %this.speed;
   %type      = %node.type ? %node.type : "";
   %smoothing = %node.smoothingType ? %node.smoothingType : "";      

   %this.pushBack( %node.getTransform(), %speed, %type, %smoothing );
}

new PathCamera( autoCam ) 
{
   dataBlock = LoopingCam;
   position  = autoCam.path.getObject(0).getPosition();
};

//------------------------------------------------------------------------------
// interface methods -- called from playGui() and default.bind.cs
//------------------------------------------------------------------------------

$autoCamMode = false;

function startCam( %path )
{
   $autoCamMode = true;
   
   autoCam.reset(0);
   autoCam.followPath( %path );
   LocalClientConnection.setControlObject( autoCam );
}
 
function stopCam()
{   
   $autoCamMode = false;
 
   LocalClientConnection.player.setTransform( autoCam.getTransform() );
   LocalClientConnection.setControlObject( LocalClientConnection.player );
   
   autoCam.setState( "Stop" );
}

function resumeCam()
{
   $autoCamMode = true;
 
   LocalClientConnection.setControlObject( autoCam );
   
   autoCam.setState( "Forward" );
}

function togglePathCam( %val )
{
   if( %val )
   {
      if( $autoCamMode == true )
      {
         stopCam();
      }
      else
      {
         resumeCam();
      }
   }
}