Game Development Community

dev|Pro Game Development Curriculum

Advanced Camera

by Thomas \"Man of Ice\" Lund · 04/03/2008 (11:57 am) · 480 comments

Download Code File

Description
Over time a lot of people have released camera resources, but some do not work anymore, others are hard to implement etc.

I've tried to assemble a single class to add to the engine that implements them all in one go using the same basic architecture. This enables minimum code bloat and makes it way easier to keep the code up to date.

Change log
March 3rd, 2005
Manoel made some changes to the orbit camera. Works in multiplayer now and is much nicer by using console variables

February 3rd, 2005
Fixed small big in interpolation.

February 2nd, 2005
Major changes in this one with various contributors.
* Static camera mode
* Smooth interpolation and transition between modes
* Smooth orbit camera!!!
* Vertical freedom mode when in 3rd person
* Better collision check with terrain and interiors
* Mouse control of orbit camera
* Totally reworked codebase and lots of cleanups. Much more readable now

Manoel Neto contributed the new orbit camera and the interpolation
Zik Saleeba contributed the vertical freedom mode and better collision check

Thanks a lot!!!!

I have marked changes with a New in the text below for those who upgrade

January 23rd, 2005
Minor changes. Larger update soon with new functionality
* Now takes GameBase objects as target + player
* Removed debug message in orbit camera
* Added getters for player and target object

June 25th, 2004
Updated the bindings for orbit camera. Switched left+right.

June 23rd, 2004
A big thanks to Stephen Zepp for contributing with an orbit camera mode. Its added to the resource, and is perfect for RTS games and action adventures. It allows for a camera to rotate around a user as if placed on a sphere. The user can zoom in/out, rotate and tilt the camera.

June 9th, 2004
Added getter/setter for the 3 offset values accessible from script
Added a "follow terrain" mode for the third person camera, so the camera follows the terrain slope - doesnt work perfectly

April 5th, 2004
All camera modes now use a raycast to not get hidden behind terrain or interiors

April 3rd, 2004
This release is the first release, and might not be as "advanced" as the author would like, but its time to release it and get some feedback to further enhance it down the road.

Camera Modes Implemented
The resource currently implements the following camera systems:
* Track Mode
This is the same as the tracking camera resource posted by Cory Osborn. A stationary camera tracks the player and keeps him in focus.

* New Static Mode
Camera stays in its position and rotation. Useful for e.g. scene based adventures where the camera doesnt move with the players

* Third Person Mode
The camera is placed at an offset behind the player and rotates with the player. The camera itself is not controllable

* Third Person Track Mode
The camera is placed in third person mode but rotated so it always looks at a specified object, but with the player in full view

* God View
Camera is placed at an offset from the player and does not rotate with the player. This is your typical "Diablo" kind of camera.

* New Orbit Mode
Camera is placed on a sphere looking at the player, and can rotate/tilt and zoom controlled by the player. There are bindings to the mouse when in single player game (server and client on same machine)

Movie
Here is a small movie displaying the different camera modes (3 MB)
www.codejar.com/advancedcamerademo.wmv

How to Add
First off all you need to take the attached advancedCamera.cc/h files and add to engine\game and add them to the project.

Then you need to expose the camera object in GameConnection as described in Cory's resource www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=4720

I took the liberty to paste the relevant parts in here too

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 );

Recompile it all and the engine is ready to go.

Script

To use the camera you need to follow some of Corys resource, but with some modifications. Datablock and naming has changed

First, add a datablock for the tracking camera to /fps/server/scripts/camera.cs:

...
datablock AdvancedCameraData(AdvCameraData)
{
   lookAtOffset = "0 0 2";
   thirdPersonOffset = "0 -3 3";
   godViewOffset = "0 -20 20";
   maxTerrainDiff = 2;
   orbitMinMaxZoom = "5 100";
   orbitMinMaxDeclination = "10 80";
   damping = 0.25;

};
...

Next, add it to the connection just like is currently done with the base camera class. Add this inside GameConnection::onClientEnterGame (/fps/server/scripts/game.cs), right after %this.camera is set up:

...
   // create advanced camera
      %this.advCamera = new AdvancedCamera() {
      dataBlock = AdvCameraData;
   };
   MissionCleanup.add(%this.advCamera);
   %this.advCamera.scopeToClient(%this);
...


We'll need to clean it up after the client leaves the game, so add this to GameConnection::onClientLeaveGame

...
   if (isObject(%this.advCamera))

      %this.advCamera.delete();
...


We need to tell it what to do when added and assign the connection's camera object, so add this to the end of GameConnection::createPlayer:

...
  // We set the camera system to run in 3rd person mode around the %player
  %this.advCamera.setPlayerObject(%player);
  %this.advCamera.setThirdPersonMode();
  %this.advCamera.setFollowTerrainMode(false);
  %this.advCamera.setVerticalFreedomMode(false);
  %this.setCameraObject(%this.advCamera);
...


And we'll want to unhook it when the player dies. Insert this at the beginning of GameConnection::onDeath:

...
   // clear connections camera
   %this.advCamera.clearPlayerObject();
   %this.advCamera.clearTargetObject();
   %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.

Orbit mode
To use the orbit camera, one needs to add some key binds to manipulating the camera.

All you need to do, is add the following to your client\config.cs or better to your client\scripts\default.bind.cs

//------------------------------------------------------------------------------
// Advanced Camera Movement
//------------------------------------------------------------------------------

$cameraYawSpeed = -100.0;
$cameraPitchSpeed = -50.0;
$cameraZoomSpeed = -5.0;

function rotateCameraHorizontal(%val)
{
     $advCamera::Yaw = getMouseAdjustAmount(%val)*$cameraYawSpeed ;
}

function rotateCameraVertical(%val)
{
     $advCamera::Pitch = getMouseAdjustAmount(%val)*$cameraPitchSpeed;
}

function zoomCamera(%val)
{
     $advCamera::Zoom = getMouseAdjustAmount(%val)*$cameraZoomSpeed;
}

moveMap.bind( mouse, xaxis, rotateCameraHorizontal);
moveMap.bind( mouse, yaxis, rotateCameraVertical );
moveMap.bind( mouse, zaxis, zoomCamera );

Remember to comment out the mouse commands for the player movement, as these are overwritten by the orbit camera controls
//moveMap.bind( mouse, xaxis, yaw );
//moveMap.bind( mouse, yaxis, pitch );

Script API
To use the different camera modes you can use the following API
Selecting the camera mode is done with e.g.:
%this.advCamera.setTrackMode();
  %this.advCamera.setThirdPersonMode();
  %this.advCamera.setThirdPersonTargetMode();
  %this.advCamera.setGodViewMode();
  %this.advCamera.setOrbitMode();
  %this.advCamera.setStaticMode();

Prior to calling the above modes you have to set the PlayerObject using
setPlayerObject();

To use the 3rd person target mode you also need to set a TargetObject using
setTargetObject();

To use the static or tracking camera you need to set the position the camera should be placed suing
setCameraPosition(Point3F pos);

To use the follow terrain mode in 3rd person you give a bool to
setFollowTerrainMode(true/false);

To use the vertical freedom mode in 3rd person you give a bool to
setVerticalFreedomMode(true/false);
This only works if the player object is a Player, because it uses the head movement.

The offset values in the datablock can be changed for the camera object via script using
get/setLookAtOffset();
get/setThirdPersonOffset();
get/setGodViewOffset();
The setters take a Point3F as argument.

The orbit camera can be manipulated from script using
get/setOrbitMinMaxZoom()
get/setOrbitMinMaxDeclination()
The min/max take a Point2F

You can also manipulate the orbit camera directly by assigning values to the following variables
[code]
$advCamera::Yaw
$advCamera::Pitch
$advCamera::Zoom
$advCamera::azimuth
$advCamera::declination
$advCamera::zoomDistance
[code]

All camera modes coexist, so you can set the position, player object and target object once and then switch camera modes around as you see fit. Switching mode will not clear the old objects/positions.
#61
12/05/2004 (9:07 am)
I patched in the advanced camera and it works awesome--love that RTS-style orbit camera! However, if I get into a vehicle the player doesn't actually get mounted onto the vehicle. He just stands there, at his "pre-mount" location while I drive the car around with wild abandon. The advanced camera follows the car fine as long as I remember to setPlayerObject() on the car when I get in, it is just that the player shape doesn't get put on the mount0 point in the car.

If I switch back to the normal camera player mouting works fine. I dunno why having different cameras would effect the player mounting functionality, though. Any suggestions on what to try for debugging this?
#62
12/06/2004 (1:51 pm)
I'm also working on this situation.
#63
12/07/2004 (12:04 am)
Hmmm - havent seen or heard about that before. Dont have any clue at the moment
#64
12/22/2004 (12:26 pm)
Help!! I need somebody... Help!!
I tried and tried and cant make this thing work!!!

First of all, thanks for your time!!
I got Torque version 1.3 with Indie license about a week ago so I am brand new to torque but I am good on c++ development.
My dev environment is ms dev studio 6 enterprise.

What I did is this:
Get your attached files into the torque project, torque core engince section.
Clean all object files and made all code plus new parts compile and build.
Applied changes to scripts up to the key binding part on starter.fps script code.
Search *.dso and Deleted precompiled bytecode so it builds again.

Run the torque EXE and all went good, no code errors on cosole.

Press the bound keys and nothing happens.
Press TAB then the bound keys and nothing happens.
Press ALT-C and again the keys and same as before.

On Console I see " .... Direction is x" messages where x is a number each time I press one of the bound keys.
(I am at work at the moment and cant recall the exact message but it didnt look like an error message)

What am I doing wrong? or what am I missing?
I didnt do the Script API section above as I felt it wasnt necesary to make this camera work, am I wrong?

Thanks in advance
#65
01/07/2005 (3:23 pm)
This resource could be just what I've been looking for. I've tried Cory's stationary camera and I've now tried your resource. Both seem to have a problem in 1.3. I have stared at the code for quite some time without finding an obvious transcription error.

Please confirm that this resource has been tested with 1.3 so I can either dig in deeper looking for a place I have screwed up or start to look for what has changed in 1.3.

The system blows in GameConnection::readPacket on the last line from this code snippet:
if (bstream->readFlag())
{
if(bstream->readFlag())
{
// the control object is dirty...
// so we get an update:
mLastClientMove = mLastMoveAck;
bool callScript = false;
if(mControlObject.isNull())
callScript = true;

S32 gIndex = bstream->readInt(NetConnection::GhostIdBitSize);
ShapeBase* obj = static_cast(resolveGhost(gIndex));
if (mControlObject != obj)
setControlObject(obj);
obj->readPacketData(this, bstream);


Thanks again for this bit of work - it could save me a considerable amount of time!
Tim
#66
01/08/2005 (1:58 am)
@Timothy: I'll try to apply it to a stock 1.3 later today and post the results.

It _should_ as I'm pretty sure that I early December updated a project to latest head and that worked flawless.

Results will be posted, and if necessary code will be updated
#67
01/08/2005 (10:00 am)
Thanks. FWIW, I purchased the new RTS pack to take a look at what they have done as well. It turns out that your advanced camera would be even better for my purposes than the RTS pack so I look forward to hearing how it goes.

Thanks,
Tim
#68
01/08/2005 (11:35 am)
Thomas,
I found a transcription error in my work. I think I'm set for the moment as the system doesn't blow up anymore. I can now trace through and see how this camera works! It works great.

Sorry for putting an extra unnecessary to-do on your list.
BFN
Tim
#69
01/16/2005 (3:58 am)
I've just put the resource into a clean 1.3 myself without any hitch. Everything still works.

Also I'm going to put a little work into the resource again very soon, and thus I'm calling out for any additions/changes/fixes/new ideas you might have. Anything goes. Read more here www.garagegames.com/mg/forums/result.thread.php?qt=25025
#70
01/16/2005 (9:27 pm)
Thomas,
I have found the camera to be invaluable to my work. I am also using 1.3.

There is one feature that would make it even better for my AeroJudge applciation - I don't know if anybody else would find it useful.

It would be nice to target two objects with one being the primary and the other being the secondary. As the camera tracked the primary target object it would try and keep the secondary object in view as long as it could. For instance, consider the camera's behavior if we have a flying object as the primary target object and a stationary object on the ground as the secondary object. The heristic would be that the camera would keep the flying object centered until the flying object's flight path threatened to hide the secondary object from view. At that point the camera would allow the primary object to move out of center up to some percentage distance from the edge of view - thus keeping the secondary object in view as long as possible.

This would be very handy as the speed and flight path of flying objects become difficult to see once you lose view of stationary surroundings like terrain. By specifying a stationary object on the ground, the ground would stay in view longer and provide the viewer with more perspective of the flying object.

If more than one secondary object could be specified, all kinds of interesting viewing behavior would become possible. Also, if secondary objects were not stationary then we could get some really nice effects in games where you have multple combat vehicles in play at the same time and want to keep as many in the players field of view as possible.

BFN
Tim
#71
01/16/2005 (10:25 pm)
@Tim - try the 3rd person tracking mode

It does some of what you need. With a little script magic for determining distances and view angles to your targets, you could make it switch
#72
01/20/2005 (10:52 am)
@Thomas - your list of additions and improvements for this resource sound great.

@Timothy - the idea of primary/secondary targets is exactly what I've been thinking about. Any chance of making email contact?
#73
01/20/2005 (3:14 pm)
My email is in my profile - feel free to hook up anytime
#74
01/23/2005 (2:05 am)
Minor update of the sources.

I plan to work on feature enhancements next weekend. Depending on the amount of changes in the current API etc., I might release the new code as a separate resource. But lets see.
#75
02/01/2005 (8:14 am)
@Drew -> Your client side method for smoothing the orbit cam sounds ideal. Would you mind posting your code? Thanks!

Also, the new adv cam says in a comment "Orbit camera can run in server or client mode". Does anyone know how to operate this?
#76
02/02/2005 (7:42 am)
OK - major upgrade.

Some feedback on the orbit camera would be great, and contributions to make it work in multiplayer.

I only need it for single player games, so I wont work on it

Test away!!!
#77
02/02/2005 (12:31 pm)
The orbit camera seems to work okay except for a kind of skipping when moving the player character around, at any zoom level/horizontal/vertical.

Other than that, it compiles just fine, even just copying the newer version over the older one I had.
#78
02/02/2005 (12:46 pm)
@Thomas,

I added the update to my code base. The new orbit camera code worked flawlessly until I moved close to the village area of the original stronghold mission. There are two things that seems to be broken due to the update.

1. Items such as, bow, med kits, ammo are not visible but can be picked up. This happens only in the vicinity of the village. The med kit on the docks, and the ammo/bow out side the tower are all visible. I guess this has something to do with items stored inside interiors.

2. When clicking the fire button, the bow animation plays but it does not eject a projectile every time. If the bow is aimed at a distant target (ie. terrain) there is no projectile but an explosion does appear at the impact site.

In fear of conflicts with other resources I've implemented, I reverted back to a clean TGE 1.3 revision with only the advanced cam resource added. The problem persists after applying the update.

Nathan.
#79
02/02/2005 (1:54 pm)
Seems there is some problem with a small skipping every tick. The client and server are out of sync by a small amount - or maybe the interpolation code isnt 100% perfect.

I'll investigate.

@Nathan
Sounds weird. Maybe objects inside interiors are not visible before you enter through the portal? Just guessing. I'll see if I can replicate, but even if, then I cannot from top of my head see where the camera resource can be recoded. It must be something deeper down so that items get culled out.
#80
02/03/2005 (11:01 am)
OK - fix for jittering is quite easy. The resource is updated in 2 secs. For those that want to patch it find this line:

F32 tInterpolation = 0.3;

and change to

F32 tInterpolation = 0.3333;

It was missing the last 0.1 (interpolation runs 3 times to catch up) and when the server forced the position, it jumped those last 0.1 to have reached the interpolated position.