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.
#301
10/29/2006 (6:37 pm)
anyone got this working with 1.5, I can't seem to get it to work right, peopel said in 1.4 not to change certain files as the camera code is now part of the standard files so I figured same would be with 1.5 changed everything else but the camera never changes modes and if I try to run functions from the console says some part of them isn't found
#302
10/29/2006 (7:36 pm)
finally got it working nm
#303
10/31/2006 (6:11 am)
I also have problems to get it work. I took the clean source from the 1.5, added advancedCamera.cc and advancedCamera.h and compliled it. After that i added the script stuff without modifications like Grant Clarke wrote.

The bindings are called and the log dont give any errors, but moving the mouse do just nothing.

I added some debug output like that :
----
function rotateCameraHorizontal(%val)
{
echo("YAW: " @ $advCamera::Yaw);
$advCamera::Yaw = getMouseAdjustAmount(%val)*$cameraYawSpeed ;
}

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

function zoomCamera(%val)
{
echo("Zoom: " @ $advCamera::Zoom);
$advCamera::Zoom = getMouseAdjustAmount(%val)*$cameraZoomSpeed;
}
---
The value for yaw,pitch, zoom are allways "0" and i dont know what i did wrong.
Anybody have a suggestions, what i did wrong ?


EDIT:
i set some stoppoints to get an idea where maybe the problem is:

In void AdvancedCamera::writePacketData then mCameraPos have this values: {x=-6.9730053e+009 y=-6.9730053e+009 z=-6.9730053e+009 }. Seams to me out of range :-/


--- EDIT 2:

i found out it only wont work in ThirdPersonMode so i made a workarround adding :
Con::setFloatVariable( "$advCamera::Mode", mMode );
as a new Console parameter to get the current mode.
After that i added a case statement in default.bind.cs to get contol of the player again using the original $mvPitch/$mvYaw statements.

So i'am not really happy about it but it works for now.
#304
10/31/2006 (9:05 am)
Hi all,

I implemented this in TGE 1.4 and it worked GREAT! Thanks for making such an easy to use and easy to install resource!

--Amr
#305
10/31/2006 (9:33 am)
I'm having trouble getting this to work with TSE. I added the 2 files to the project, but when I came to compile, I received an error in advancedCamera.cc on line 6 saying that the compiler couldn't find the file dgl.h.

I commented out the #include and the program compiled fine, but after adding the script changes, I find that the advanced camera doesn't function - I am simply placed in a first person view of my player.

Any info people?

--Amr
#306
11/01/2006 (11:51 pm)
Hello I am a College Student and am involved in a game production class. We are trying to use this script for a top down view. The problem is we cannot get the optimization part presented by Shanon ScarWars to work. We have successfully added the advancedcamera.cc and h to the vc++ 2005 express project and compiled both a release and debug version.

When we compile with the original script it works fine. When we compile with the modifications it crashes when loading.. we are using the fps starter for testing purposes. Thank you in advance.



--DJ
#307
11/05/2006 (12:03 pm)
hello,
i did implement this like this:

function GameConnection::CreatePlayer(%this, %spawnPoint)
//----------------------------------------------------------------------------
// Create the player's avatar object, set it up, and give the player control
// of it.
//----------------------------------------------------------------------------
{   
   if (%this.player > 0)//The player should NOT already have an avatar object.
   {                     // if he does, that's a Bad Thing.
      Error( "Attempting to create an angus ghost!" );
   }
   
    // Create the player object
   %player = new Player() {
      dataBlock = MaleAvatar;   // defined in player.cs
      client = %this;           // the avatar will have a pointer to its
   };                           // owner's connection

   // Create a new RTS camera object.
   %this.advCamera = new AdvancedCamera()
   {
      dataBlock = AdvCameraData;
   };
   MissionCleanup.add( %this.advCamera );

   // Player setup...
   %player.setTransform(%spawnPoint); // where to put it

  %this.advCamera.setPlayerObject(%player);  
   %this.advCamera.setOrbitMode(); 
   %this.advCamera.setFollowTerrainMode(false);  
   %this.advCamera.setVerticalFreedomMode(false);  
   %this.setCameraObject(%this.advCamera);
   
   // Give the client control of the player
   %this.player = %player;
   %this.setControlObject(%player);

   
 
}

but all i get is a 1st person view:-(

can someone tell me how i activate the orbit mode?
i did use this
%this.advCamera.setOrbitMode();
but nothing seems to happen
#308
11/05/2006 (12:08 pm)
You've forgotten a command. In the example, they had the following line:

%this.advCamera.scopeToClient(%this);

Add this after you add the advanced camera to MissionCleanup

--Amr
#309
11/05/2006 (11:42 pm)
oh ya i added that line

but there is no change
#310
11/06/2006 (12:41 am)
@Wolfgang - try to press the tab button. If that works, then you forgot to switch the camera away from firstperson in your code.
#311
11/06/2006 (9:34 am)
@Thomas

I implemented the Toggle like this:

function Toggle1stPPOV(%val){  
   if(%val){      
      $firstPerson = !$firstPerson;
   }
}
playerKeymap.bind(keyboard, tab, Toggle1stPPOV);

but when i hit tab nothing happens

Edit:

never mind i got this bit of code from a book but then i checked in the starter.fps and found the missing line

ServerConnection.setFirstPerson($firstPerson);

i added this and now it works
thx a lot
#312
11/09/2006 (2:29 am)
Hi,

First off, I'd like to say that this resource is amazing. I'm using the orbital camera w/ TGE 1.4 and it's simply beautiful.

I'm running into the same problems that other people are though, specifically The scoping and jittering problems mentioned by matt and marcel a bit earlier. Has anyone found fixes for these issues?

The scoping issue is that when a client enters a DIF interior, all objects inside that interior aren't rendered (This includes other clients/ai players/items). I know for a fact this has to do with the portals, and I can only assume that it's a result of the camera not knowing which zones should be rendered.

I applied the fix by Manoel from Mar 07, 2005, and now objects render inside DIFs correctly. However, the camera shakes every time the client moves. Obviously I'd prefer my objects to render inside buildings, but I'd also prefer my camera not give the player a headache...

Any thoughts/suggestions? Anyone recreate this problem?

For reference, here are the posts from earlier referring to this same issue:

>>

Matt - Fix for Scope Problem (Sep 03, 2006):
The Fix post by Manoel Neto, dated Mar 07, 2005. Seems to fix the problem and works fine for my needs :D.

Marcel - Jitter problem using fix found by Matt:
Hello Matt, we had the same problem.

The fix from Manoel worked but it made everything shaking strangely, we had to take it off. The only way we found to fix our problem was to increase the visible area to 1400 in order to cover almost the whole mission area (my mission area is 1024 square, so 1448 was the cercle to cover everything).
Did you have the same problem ?

<<
#313
11/16/2006 (11:11 pm)
Has someone any idea why advanced camera randomly crashes at start?
#316
11/22/2006 (6:58 am)
Hello to all,

Am use TGE 1.4.2 and need to know if the Adv Camera has alread in 1.4.2? Becuase of the Console Method above are alread in 1.4.2.
#317
11/23/2006 (11:06 am)
This code doesn't work running in debug mode with visible bounding boxes!!!
Can someone check this? Am I doing something wrong?

I did the mods and got the engine crashing, running the starter.racing in debug mode with visible bounding boxes
$GameBase::boundingBox = true;

It's ever thing ok when running release mode, or debug without bounding boxes. Nothing suspicious in Console.log file.

This is a great resource, but I don't want to add code that make the engine crash in this situation.
Does anyone know how to fix it?

Thanks
#318
11/23/2006 (1:27 pm)
Hey, what are the exact values that the orbit mode uses for its rotations and zoom?

The way I want to use it in my game is to have a single button move the rotation to an exact place, rather than have a manual analog adjustment. For example, I might want the horizontal movement be restricted to say eight specific angles, N, NE, E, SE, etc, and then have two keys bound to adjusting it to move "left" and "right" to the next respective slot, and then a button that cycles between preset zoom values, things of that sort.
I can work it to move gently to the new slot later, but for the moment I want to know what kind of values the yaw, pitch, and zoom use so I can know what kind of number to feed to them.
#319
11/30/2006 (11:01 pm)
I have problem with camera movement in third person mode. When the player is walking slowly upstairs, camera shaking terrible each step. Any ideas ?
#320
12/20/2006 (7:32 pm)
Could I trouble someone to define these different modes, by any chance? I've implemented the resource in 1.5, and certainly things are happening, but it's not what I was expecting, but I'm not even sure whether to whine or not! For example, in oribital mode, my guy runs around and the camera tracks him, staying in the same orientation. I expected it to, like, orbit around him. Or in God mode, I had the idea that I could independently move the camera (although I'm not clear how that would be done, interface-wise).

So I'm not sure if it's working or not! Kinda like not being able to tell if your TV is on or not.