Game Development Community

dev|Pro Game Development Curriculum

Using a Separate Camera Object

by Cory Osborn · 10/27/2003 (9:52 am) · 71 comments

Download Code File

For my game, I need a few different 3rd person cameras. There are a couple of other good tutorials here to show off different 3p modes (like 21-6's dynamic camera tutuorial), but in my research I found that GameConnection already has some code to use a separate camera - all it needs is some tweaking.

There are 4 parts to this:
  • exposing GameConnection::mCameraObject to the console
  • having GameConnection read/write camera packets just as it does for the control
  • having a class that operates your camera mode
  • setting script to use the new camera in the game

The first two parts of this process are required to make a separate camera work within the game. The second half is just an example of plugging a different camera into the game.

note: this code has been verified against the CVS HEAD as of 04/03/2004.


Exposing mCameraObject
The first thing you want to do is add console method's to access the GameConnection setCameraObject/getCameraObject methods. I added these to GameConnection.cc right after the console method for getControlObject:
ConsoleMethod( GameConnection, setCameraObject, bool, 3, 3, "(ShapeBase object)")
{
   ShapeBase *gb;
   if(!Sim::findObject(argv[2], gb))
      return false;

   object->setCameraObject(gb);
   return true;
}

ConsoleMethod( GameConnection, getCameraObject, S32, 2, 2, "")
{
   argv;
   SimObject* cp = object->getCameraObject();
   return cp ? cp->getId(): 0;
}

ConsoleMethod( GameConnection, clearCameraObject, void, 2, 2, "")
{
   object->setCameraObject(NULL);
}

After playing around with it, I also found you need some adjustments to the setCameraObject and setControlObject methods - otherwise the client connection can screw things up if you bounce the same object from your connection's control to camera or vice versa. Here is my GameConnection::setControlObject:
void GameConnection::setControlObject(ShapeBase *obj)
{
   if(mControlObject == obj)
      return;

   if(mControlObject && mControlObject != mCameraObject) 
      mControlObject->setControllingClient(0);
   
   if(obj)
   {
      // Nothing else is permitted to control this object.
      if (ShapeBase* coo = obj->getControllingObject())
         coo->setControlObject(0);
      if (GameConnection *con = obj->getControllingClient()) 
      {
         if (this != con) 
         {
            // was it controlled via camera or control?
            if (con->getControlObject() == obj)
               con->setControlObject(0);
            else
               con->setCameraObject(0);
         }
      }

      // We are now the controlling client of this object.
      obj->setControllingClient(this);
   }

   // Okay, set our control object.
   mControlObject = obj;
   if (mCameraObject.isNull())
      setScopeObject(mControlObject);
}

and here is my GameConnection::setCameraObject:
void GameConnection::setCameraObject(ShapeBase *obj)
{
   if(mCameraObject == obj)
      return;

   if(mCameraObject && mCameraObject != mControlObject) 
      mCameraObject->setControllingClient(0);
   
   if (obj) {
      // Nothing else is permitted to control this object.
      if (ShapeBase* coo = obj->getControllingObject())
         coo->setControlObject(0);
      if (GameConnection *con = obj->getControllingClient()) 
      {
         if (this != con) 
         {
            // was it controlled via camera or control?
            if (con->getControlObject() == obj)
               con->setControlObject(0);
            else
               con->setCameraObject(0);
         }
      }

      // We are now the controlling client of this object.
      obj->setControllingClient(this);
   }

   // Okay, set our camera object.
   mCameraObject = obj;

   if (mCameraObject.isNull()) {
      setScopeObject(mControlObject);
   } else {
      setScopeObject(mCameraObject);
      // if this is a client then set the fov and active image
      if(isServerConnection())
      {
         F32 fov = mCameraObject->getDefaultCameraFov();
         GameSetCameraFov(fov);
      }
   }
}


Camera read/write packets
None of this will work unless the client copy of the camera object gets packets updated. Here we're going to modify GameConnection::readPacket and GameConnection::writePacket.

In GameConnection::readPacket, find this block of code:
if (bstream->readFlag())
      {
         S32 gIndex = bstream->readInt(10);
         ShapeBase* obj = static_cast<ShapeBase*>(resolveGhost(gIndex));
         setCameraObject(obj);
      }
      else
         setCameraObject(0);
and change it to
if (bstream->readFlag()) 
      {
         S32 gIndex = bstream->readInt(NetConnection::GhostIdBitSize);
         ShapeBase* obj = static_cast<ShapeBase*>(resolveGhost(gIndex));
         setCameraObject(obj);
         obj->readPacketData(this, bstream);
      }
      else
         setCameraObject(0);

In GameConnection::writePacket, find this block of code:
if (!mCameraObject.isNull() && mCameraObject != mControlObject)
      {
         gIndex = getGhostIndex(mCameraObject);
         if (bstream->writeFlag(gIndex != -1))
            bstream->writeInt(gIndex, 10);
      }
      else
         bstream->writeFlag( false );
and change it to:
if (!mCameraObject.isNull() && mCameraObject != mControlObject) 
      {
         gIndex = getGhostIndex(mCameraObject);
         if (bstream->writeFlag(gIndex != -1)) {
            bstream->writeInt(gIndex, NetConnection::GhostIdBitSize);
            mCameraObject->writePacketData(this, bstream);
         }
      }
      else
         bstream->writeFlag( false );


Creating a camera object
Next we need an object to use as our camera. If you look at the code in GameConnection::getControlCameraTransform, you can see how GameConnection determines the position and rotation for rendering a frame. Based upon that code, any class descending from ShapeBase could theoretically be plugged in as the connection's camera.

The standard Camera class is one option. Another option is the TrackingCamera class attached to the article. TrackingCamera will change its orientation in order to track another object in the game. Without looking at its code, you'll just need to know that it exports a console method setTrackObject that allows you to specify which object will be tracked. One warning - the TrackingCamera will suffer from Gimbal Lock.


Putting it in the game
Here is the basic areas of code to change to see the example tracking camera in starter.fps. There are other areas where you'll need to tweak the script in order to unhook the camera and return to first-person mode, but I'll leave that for you as an exercise :)

First, add a datablock for the tracking camera to /starter.fps/server/scripts/camera.cs:
...
datablock TrackingCameraData(TrackCameraData)
{
   trackAdjPos = "0 0 2";
};
...

Next, add it to the connection just like is currently done with the base camera class. Add this inside GameConnection::onClientEnterGame (/starter.fps/server/scripts/game.cs), right after %this.camera is set up:
...
   // create in-game stationary camera
   %this.trackCamera = new TrackingCamera() {
      dataBlock = TrackCameraData;
   };
   MissionCleanup.add(%this.trackCamera);
   %this.trackCamera.scopeToClient(%this);
...

We'll need to clean it up after the client leaves the game, so add this to GameConnection::onClientLeaveGame
...
   if (isObject(%this.trackCamera))
      %this.trackCamera.delete();
...

We need to tell it what to track and assign the connection's camera object, so add this to the end of GameConnection::createPlayer:
...
   // set the stationary camera to the same position
   %this.trackCamera.setPosition(%this.camera.getPosition());
   %this.trackCamera.setTrackObject(%player);
   %this.setCameraObject(%this.trackCamera);
...

And we'll want to unhook it when the player dies. Insert this at the beginning of GameConnection::onDeath:
...
   // clear connection's camera
   %this.trackCamera.clearTrackObject();
   %this.clearCameraObject();
...

The last thing to know, is if you're in first-person mode, GameConnection automatically uses the control object to render the engine rather than the camera object. So if you don't see this working when you first enter a mission, toggle out of first-person.

Hope it helps. - cory

Additional Resources
  • My resource on more intuitive movement controls for a third person camara
  • Thomas Lund's work for additional camera modes
  • Thomas Lund's combination of camera flipping with mission regions

  • Edits
    1/11/2004 - Updated the attached TrackingCamera class to override ShapeBase::onCameraScopeQuery so the tracked object is always scoped to the client.
    4/3/2004 - Verified the code still works on the latest HEAD and changed the example file paths to refer to starter.fps
    4/4/2004 - Added additional resources links
    #41
    10/23/2004 (9:07 am)
    By the way, I've gotten this tutorial to work with TGE 1.3.0. However, I many of the tutorial's code snippets for the engine were half implemented. Just use a little intuition about what to code add and you should have similar results.
    #42
    10/23/2004 (10:34 pm)
    Just like to say this is a wonderful resource and is almost EXACTLY what I've been looking for the past couple days. Thanks Cory!
    #43
    01/11/2005 (9:39 am)
    I've been working my way through the various camera resources, particularly those by Cory Osborn and Thomas Lund. They are excellent and are proving to be very helpful and informative. I got the tracking camera up and running without a problem and am starting to study the next resource. Good work ... and thanks a lot!
    #44
    03/05/2005 (10:33 pm)
    I'm having the same situation as Brad Sweet, I've made all the changes everyone else makes, and I dont see a difference when I play the game.
    #45
    04/03/2005 (12:53 pm)
    press TAB,
    and check out game.cs change setverticalfreedoom(false) to setverticalfreedoom(true)

    This may help.
    #46
    04/09/2005 (12:28 pm)
    I have a problem like Emmanuel had. I typed all the code and everything but it is no different than the normal demo. How to do you change the path to the starter.fps?

    I am relatively new to Torque and have been working through the 3DGPAI1 book, but it doesn't cover a lot of what i need. I am going for a 3rd person action /adventure type game and am having a hell of a time getting there.

    Thanks,

    Adam
    #47
    12/26/2005 (12:30 pm)
    It appears that the changes required to GameConnection.cc in this resource are now part of the base Torque engine (1.4). Correct me if I'm wrong but you don't need to make these changes anymore, just add TrackingCamera.cc to your workspace and you're set. Thanks for the great resource.
    #48
    02/08/2006 (11:57 pm)
    You will also need to setup the script side of things to get this working.

    -Jase
    #49
    02/20/2006 (7:32 pm)
    One thing I noticed under the 1.4 HEAD (as of 2/20/2006) was that the tracking camera never moved from the position above where the player spawns. From reading all the comments and looking at the other resources Cory submitted in this series, I can't tell if this is what it was supposed to do or not.

    Of course, I could have just done Something Wrong (tm) too... but it does compile, and no script errors, but once I tab to 3rd person view, the tracking camera never moves.
    #50
    03/20/2006 (5:53 pm)
    I get a compilation error on the line in gameconnection.cs that contains "issserverconnection()" as an undeclared identifier. Any ideas? With it commented out, i just get a grey screen in 3rd person mode
    #51
    05/04/2006 (9:55 pm)
    I too am getting a lot of errors with the camera. Has anyone else tried this in TSE? The errors are all throughout gameConnection.cpp
    #52
    05/23/2006 (7:34 am)
    Works great for me. I'm using TGE 1.3.

    Thanks Cory.
    #53
    05/23/2006 (10:58 am)
    great resource...combined it with another one, but zoom doesn't work...
    #54
    05/23/2006 (2:19 pm)
    This works extremely well in TSE! Especially with the mission regions. In terms of your question Kaya, I really don't know. Try binding it to the default key which is E. If it works it's most likely a script error and not a problem in the code. I'm able to zoom fine.
    #55
    07/29/2006 (3:48 pm)
    Dungan ... did you manage to find out what was causing this problem? I had the same thing ... a compile error related to isserverconnection() ... amongst others :(

    Can't get this one to work with the lighting pack :( ... what i did think was odd was that the setControlObject and setCameraObject scripts were already in there! I haven't modified the engine, but the ones in there are identical to those posted on this resource.

    C:\buildFolder\SynapseGaming\contentPacks\LightingPack\engine\game\gameConnection.cc(1477): error C2371: 'cGameConnectionsetCameraObject' : redefinition; different basic types
    C:\buildFolder\SynapseGaming\contentPacks\LightingPack\engine\game\gameConnection.cc(1477): error C2556: 'S32 cGameConnectionsetCameraObjectcaster(SimObject *,S32,const char ** )' : overloaded function differs only by return type from 'bool cGameConnectionsetCameraObjectcaster(SimObject *,S32,const char ** )'
    \buildFolder\SynapseGaming\contentPacks\LightingPack\engine\game\gameConnection.cc(1282) : see declaration of 'cGameConnectionsetCameraObjectcaster'

    ... and a bunch of other errors :( ... I'm *sure* I'm copying everything in correctly - hmmm, maybe I need to spend more time figuring out how this works if the copy and pasting ain't workin ;)

    Anyone managed to get this working with 1.4 lighting pack?? Thanks!
    #56
    08/17/2006 (9:26 am)
    for all those that get the isServerConnection() error:

    in torque 1.4 its now called :

    isConnectionToServer()

    so just replace the error witht he new name and ur set:):)
    #57
    11/11/2006 (6:31 pm)
    I've been having problems getting this running under 1.4 as well. I replaced the isConnectionToServer function but it seems to be requiring some overloading modifications as well. A function signature changing or something?
    #58
    11/18/2006 (1:13 am)
    I didnt add the ConsoleMethods and it works. Looks like this is already pulling from included code in 1.4.2 just a heads up.
    #59
    01/18/2007 (9:56 pm)
    well done man!
    #60
    01/02/2008 (7:33 pm)
    Hopefully this is still looked at...

    Everything works fine - except I dont know how to set the transform of this to a given point.

    here is my code:
    %this.trackCamera.setPosition(1.4469, -415.773, 5.02113);

    I get "wrong number of parameters" I've searched for "Point3F pos", but I can't find what it is looking for.

    Please, any help?