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.
#221
01/31/2006 (2:59 pm)
This worked for someone else so i posted it here too:

with tge 1.4 - I would get the grey screen when tabbing to the other camera.

it works for me after I did the following hacks....


In
function GameConnection::onClientEnterGame(%this)
after
%this.advCamera.scopeToClient(%this);

add:
$advCameraCCB = %this.advCamera; //ccbhack



in
function GameConnection::spawnPlayer(%this)
change player to a global var:
%this.advCamera.setPlayerObject($playerccb);


in
function GameConnection::createPlayer(%this, %spawnPoint)
after:
%player.setTransform(%spawnPoint);
add:
$playerccb = %player; //ccbhack
#222
02/03/2006 (12:14 am)
[EDIT]
I tried with clean 1.4 version. This time has no error. But the camera can't move to left or right. The player always looks forward. I had checked the default.bind.cs to make sure that i has all stuff and delete config.cs (.dso) to generate new file.

Anyone can help?
#223
02/14/2006 (7:36 am)
I decided that today is the last time retry to implement this camera.. Finally it works ... :D
I have to implement this resource to get it works
http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=5934
Now i can yaw/pitch..

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

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

to revert mouse when rotate left or right.. feel more comfortable...

Great resource..
#224
02/15/2006 (11:22 am)
can I use this resource somehow to switch to another camera's view that I have attatched to a static object ????
#225
02/17/2006 (8:54 pm)
Hi, would be great if someone can update this resource for 1.4. Lots of problems and crashes when using it. Those looking for a detached camera that works correctly with 1.4 you can check out one of the original resources: http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=4720 Note: just skip the section labled 'Exposing mCameraObject' since that has already been done in the new release.
#226
02/28/2006 (7:46 pm)
Can someone confirm that this doesn't work with 1.4?

EDIT: NVM, seems to work for me, although I haven't stress tested it or anything. One thing I am having trouble with is call the get/set*viewoffset functions. They all give me the error:

(0): Unable to find function setGodViewOffset

I am just typing this into the cosole:

setGodViewOffset(10, 10, 10);

Is that how you do it? This is my first week with the engine, and this is the first time I've used the console, so I'm sure I'm just doing something silly.

Thanks!
#227
03/19/2006 (9:17 pm)
Took me a long time to figure out how to compile it as I am new to TGE, however that IS brilliant.

~joshua
#228
03/19/2006 (9:19 pm)
@Faraz Ahmed regarding the "just skip the section labled 'Exposing mCameraObject' "

The code is different, replace the 1.4 code with the mod code. It works fine for me
#229
03/26/2006 (5:45 pm)
I got this working out-of-the-box under TGE 1.4, here's what I did:

1) At first I skipped the "Exposing mCameraObject" section of directions, but then noticed they were different, so I replaced them with the code above.

2) At first I skipped the "Camera read/write packets" section of direction, but again noticed the differences and replaced what was there with the code above.

3) Did everything in the "script" section of the directions.

4) Did everything in the "orbit mode" section of the directions because I wanted to use Orbit Mode.

5) I also did the sin/cos change recommended by Shane Anderson (Apr 21, 2005 at 13:50).

6) I also reduced the bitstream "weight" that was being generated by this code by following Shannon "ScarWars" Scarvaci (Nov 03, 2005 at 20:35) 's excellent tutorial. In fact, this tutorial can be used as the basis for reducing the network load for Many Many Torque systems -- it's there for some of them already, but not all of them. People interested in building an MMO (IMHO) would do well to understand what Shannon is doing here (IMHO).

7) Now comes the part where you need to really know TorqueScript :-) The rest of the directions aren't so much directions as they are general information about how to use what's in there. At this point, you'll need to call the various %this.advCamera.setWhateverModeYouWant(); functions listed in the "Script API" section as a result of whatever makes sense for your game.

Here's an example of 1 possible implimentation -- this is something I added to my default.bind.cs that will switch you to Orbit Mode when you click the middle mouse button:

function middleMouse(%val)
{
  if (!$firstPerson){
     if (%val){
        //Button Down
        //Camera Orbitting
        ClientGroup.getObject(0).getCameraObject().setOrbitMode();
        moveMap.bind( mouse, xaxis, rotateCameraHorizontal);
        moveMap.bind( mouse, yaxis, rotateCameraVertical );
        moveMap.bind( mouse, zaxis, zoomCamera );
      } else {
        //Button Up
        ClientGroup.getObject(0).getCameraObject().setThirdPersonMode();
        moveMap.bind( mouse, xaxis, yaw );
        moveMap.bind( mouse, yaxis, pitch );
        moveMap.bind( mouse, zaxis, zoomCamera );
      }
  } 
}

GlobalActionMap.bind( mouse, button2, middleMouse );

I had to use GlobalActionMap instead of MoveMap becaue in my implementation I'm also turning the cursor on when ever a mouse button is not being held down (my mouse/cursor functionality is identical to WoW or CoH/CoV)... but you can probably use MoveMap instead and it should work just fine.

Edit: Improved the code formatting
#230
03/28/2006 (2:08 pm)
Please can anyone write what he has done, so that it runned on Torque 1.4? It's a little bit complicated to follow the discussion. Thanks for doing this.( I try to compile this on MacOS X. Anyone succeded?)
Thanks for reply
#231
03/30/2006 (10:16 pm)
Yup, works fine under 1.4

I did none of the engine changes

- I Skipped exposing the Camera (1 Code block in Resource/tuturial)
- Skipped the GameConnection::setControlObject code (2nd Code block in Resource/tuturial)
- Skipped the GameConnection::setCameraObject code (3rd Code block in Resource/tuturial)
- Skipped the GameConnection::readPacket and WritePacket

Just add the resource files to the project and recompile

Then do the script changes.
#232
04/05/2006 (9:01 am)
@Thomas
Quick question: Does the LOD on DTS work with this Camera? Or has that got nothing to do with the Camera...
#233
04/14/2006 (12:10 am)
I know other posts have had this problem, but I cant get my head around it. I have followed what 'BurNing' said in the post above, but I dont understand what to do about the script changes.

I can get as far as the 'Script API' section of the resource, can somebody please take the time to explain this to me. What is the Script API and where do I implement it. I am trying this with the starter.fps kit.

I added the resource file to the project and compiled with no problems, but when I run the starter.fps, I get no control in the camera with the mouse in both first and third person view. I commented out the original binds of the mouse axis, and inserted the advanced camera binds but it isnt working...

@BurNing: What particular Script Changes did you do?
#234
04/14/2006 (5:24 am)
I am having immense diffculty implementing this resource into my project.

I have started with a clean TGE 1.4, made the engine changes, compiled fine, but I cant get the scripts to work.

Can somebody please talk me through the scripts to get this working in the starter.fps.
#235
04/16/2006 (6:33 am)
I poped in the code (from the zip) and integrated the scripts on 1.4. I was looking at it for a while until I realised you had to hit to toggle to the camera.

There are alot of comments, changes et al on this page...does somebody compile all of together or should another resource get started to 'implement' all the working changes....seems a bit like open source development gone wild.
#236
04/21/2006 (7:43 am)
Hmmm... post has ben swallowed... and the forums seem to be down or my PC is full of crap.

Try again....

@Louden
Skip the Script API section and get it working first.

In the player:create I have only this:
%this.advCamera.setGodViewMode();

But it should work... try a clean rebuild (Remember to add the files to the project befre compiling)
And delete DSO's

When the game is running press [b]tab[b] to go to advancedcam.

If that doesn't work look in the console fo any errors.
#237
04/24/2006 (2:03 pm)
Resource works fine in 1.4 w/ lighting kit for me- almost. I'm using the godview mode, and as I move the camera farther from the player (increasing the Z-axis value with setGodviewOffset()), at a certain point (depending on the y-value of the godview offset) the camera will rotate about 45 degrees. If I increase the y offset, the point where the rotation occurs increases, but eventually, if I move far enough away, it will rotate. Has anybody else experienced this, or have a fix for it? Currently, I have the camera slowly increment along Y as well as Z, which seems to work OK, but makes for a slightly odd rocking effect.

Other than that, EXCELLENT resource. Saved me a huge amount of time!
#238
04/25/2006 (1:12 am)
@Charlie: Also found that little quirck.
#239
04/28/2006 (7:27 pm)
Thanks tons 'Man of Ice' for this resource!! I was stumped getting this to work with AI for days till i listened to Ramen Sama above.
BTW You can also test the tracking cam mode by opening your mission editor and look for Kork's 4 digit id (mine's 1610). Then close mission editor, tab to third person mode, and open your console, type this into the console...

ClientGroup.getObject(0).getCameraObject().setTargetObject(1610);
ClientGroup.getObject(0).getCameraObject().setThirdPersonTargetMode();

all on one line is fine and hit return. Your player is now tracking Kork.
We used the script API by first setting the target object, then seting our advcam mode.
#240
04/29/2006 (10:22 pm)
Ok, I am very new to Torque, and I feel a bit silly asking this, but keep in mind I am very tired atm... I pulled all this code into my project and it compiled fine, but... I am so new to torque script right now, I can't seem to understand what the script changes I need to do to implement this? I have my mod/mission/game stuck in permo-third person so it's not that I am in third person mode. Where can I find the commands this adds and what do I need to do to change from old cam to advcam?