Game Development Community

Advanced Camera

by Thomas \"Man of Ice\" Lund · 04/03/2008 (11:57 am) · 478 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.
Page «Previous 1 2 3 4 5 6 7 Last »
#1
04/04/2004 (10:15 am)
Great Thomas !!!!!

Christophe
#2
04/04/2004 (11:42 am)
This looks amazing :)

*strips out GodView resource*
#3
04/04/2004 (11:48 am)
Haven't tried this resource out yet but I think it sounds very interesting. I'll give it a show tonight.

Edit: Just looked at the sample movie and it looks GREAT! Would you mind if I implemented this into our current game build?
#4
04/04/2004 (11:50 am)
If you guys are using God-View or Track Modes - you may find my resource of translating moves relative to the camera will come in handy.
#5
04/04/2004 (5:12 pm)
Very nice.
#6
04/04/2004 (8:20 pm)
Oo, this is sweet. Thanks for the great stuff Tom
#7
04/05/2004 (3:09 am)
Code has now been updated with a small change. All 4 camera modes now cast a ray from the player to the camera position and tests for collisions with terrain or interiors. The camera will be moved so its placed at the point of collision (if any)

Its not perfect, as small clipping artifacts appear on screen. I'll try to work out those bugs later, but I'm having some problems with the point that is returned by the raycast.

In e.g. 3rd person mode with the camera straight behind the player, the collision point returned on a terrain slope is almost at the players feet. I am unsure what the problem is, so if anyone would care to take a look.

Next up is the 3rd person camera aligning itself with the terrain slope as well as smooth camera transistions when chaning modes.
#8
04/06/2004 (4:29 am)
@Thomas
Try using getEyeTransform not getPosition/getTransform :)
#9
04/06/2004 (6:01 am)
:-)
Yeah - during the night it came to me (long live subconsiousness), that the transform gets the player position from his feet - and thus the line to his feet -> camera position will intersect the terrain slope earlier.

Its actually the "lookAtOffset" I need to add to the transform for the starting point in the ray cast. I'll do that later today or tomorrow.
#10
05/02/2004 (1:21 am)
i've implemented this resource and it works exactly as i expected. i am now trying to expand the godview portion of it to include a rotation offset. it looked to me like this is already built into the class in the mRot variable, but when i adjust it there is no visual change. i've tried a couple other methods as well but with no luck. anyone have any ideas?
#11
05/26/2004 (8:07 am)
could you cast a ray straight down below the camera to determine the distance to terrain/other objects, and then pad that distance with the value you want the 3rd person camera to float above the terrain?
#12
06/08/2004 (1:45 pm)
Hmmm

A little progress and changes. I implemented a "follow terrain" mode as suggested by Shay using ray casts, and it works like a charm - and actually looks quite cool when going up/downhill.

I made it so that one can toggle the feature on/off in 3rd person from script, and it defaults to not follow the terrain for backward compatibility.

But it has a few problems, and thats why I havent released this yet.
* When the player jumps, the camera stays at the assigned height over terrain. That looks kind of dumb to be honest. I think its easy to fix by adding the player height over terrain to the camera height.

* The camera might suddenly drop 50 meters or so down if the player rotates on a bridge or on a hillside. I'll implement a safety buffer, so that if the terrain height suddenly changes, the camera stays where its at.

Once those are fixed I'll release it and update the resource.
#13
06/09/2004 (11:10 am)
OK - I decided to release it with unfinished business. Sorry.

I get really weird behaviour from the terrain following camera, so I think the code has to be approached from a different angle. When moving around the camera jumps all over the place (where it isn't supposed to) as if the raycast sometimes fails on terrain node border (or something).

Several minor improvements in the code.

One can now get/set the 3 offsets (god view, look at and third person) from script. I use it to e.g. modify the god view to look straight down on the player at a point where he has to jump down from a cliff. Once down the camera goes back to 45 degree view - perfect.

You use those with

get/setLookAtOffset()
get/setThirdPersonOffset()
get/setGodViewOffset()

Also the current terrain follow mode is included in the build.

Turn it on for 3rd person mode by writing
setFollowTerrainMode(true/false);

The way the terrain following works currently is:
If enabled {
  cast ray from camera position and down to the terrain or interior below
  if distance is less than a minimum value
     ignore the terrain (to avoid a jumpy camera - makes you sea sick)
  if distance is larger than maxTerrainDiff (defaulting to 2, settable in the datablock)
     cap the movement of the camera up down by maxTerrainDiff
  else
     set the height of the camera to terrain + the z value of the thirdPersonOffset
}

The effect is there, it's really cool, but doesnt work right. If someone wants a shot at it, then feel free to post corrections.

What I think it _should_ do is cast a ray down from the player, look at the normal of the terrain slope and then modify the camera height according to the angle of the terrain under the player. I'll work on that later - dont have more time this week for coding
#14
06/20/2004 (2:06 pm)
Works great! Thanks!
#15
06/23/2004 (2:23 pm)
Resource has been updated with an orbit camera, and the text above is updated with the relevant information. The orbit camera was written by Stephen Zepp, and all credits are due to him.

The orbit camera will need some massaging to get to work properly, as TGE doesnt support key repeats, and thus one cannot simply hold down a "zoom in" key. The solution is to create a function that schedules a repeat of the zoom command. Its all explained here:

www.garagegames.com/mg/forums/result.thread.php?qt=19282
#16
06/24/2004 (1:44 pm)
This is an awesome resource. Exactly what I've been looking for for ages.

I've hooked it into the missionregion resource and it works like a dream. The only thing I would ask is that the smooth transition between modes and locations was possible, maybe via a flag.. :o)
#17
06/24/2004 (11:26 pm)
Smooth transition is in the works over summer time - I would love to have it too for my own game, so I'll give it a shot in a month or so.

Unless someone has the balls to do it and send it to me for inclusion into the resource *wink* *wink*
#18
06/25/2004 (5:33 am)
I'm keeping a very keen eye on the developments of this..
Even with my test map, it has a very cinematic feel.
#19
06/25/2004 (3:43 pm)
I implemented this, and I when I run it, the loading bar fills up but I never get into the game. So I have to hit cancel to get out of it, but the game doesnt crash or anything. I have rechecked everything 3 times and I got everything in exactly as is directed. (I am running it in starter.fps mode). Please help, thanx.
#20
06/26/2004 (1:42 am)
What does the console log say?
Page «Previous 1 2 3 4 5 6 7 Last »