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.
#381
05/28/2007 (6:34 am)
I switch between the default Torque 3th person view and the orbit camera but I want to make this switch as smooth as possible. How can I position the orbit camera at the same place where the 3the person camera is when switching to orbit mode (and vice versa) so the user will not see that I change the camera?

Thanx,
Klaas
#382
06/07/2007 (8:56 am)
I'have found a way to center the camera behind the player when you switch to the orbit camera mode:

Replace the "case Orbitmode :" in the advanceTime function with this:

case OrbitMode :
		{
			if ( playerObj != NULL)
			{
				Point3F mConvertedOrbitOffset;
				F32 mOffsetX;
				F32 mOffsetY;
				F32 mOffsetZ;

				MatrixF objToWorld = playerObj->getRenderTransform();
				objToWorld.mulP(mCurrentOrbitOffset, &cameraPosWorld);
                
				//Added for camera reset
				bool resetCam = Con::getBoolVariable("$advCamera::resetOrbitCam", false);
				if (resetCam)
				{
					Con::setBoolVariable("$advCamera::resetOrbitCam", false);
					Player*	playerObjAsPlayer =	dynamic_cast<Player*>(playerObj);			
					Point3F	Rotation = playerObjAsPlayer->getRotation();
					mAzimuth = mAzimuth - (mAzimuth - ((-Rotation.z) + (1.5 * M_PI)));
				}
				//Added for camera reset

		                // use spherical to cartesian coord transforms to calculate offset
				mOffsetX = mZoomDistance * sin(mDeclination) * cos(mAzimuth);
				mOffsetY = mZoomDistance * sin(mDeclination) * sin(mAzimuth);
				mOffsetZ = mZoomDistance * cos(mDeclination);
				mConvertedOrbitOffset.set(mOffsetX, mOffsetY, mOffsetZ);
				cameraPosWorld += mConvertedOrbitOffset;
			}
			break;
		}

Put "$advCamera::resetOrbitCam = true;" in your script to reset the camera.

This aligns only the azimuth not the declination and zoomdistance. Can someone point me in the right direction for calculating the correct declination and zoomdistance?

Thanx,
Klaas
#383
06/12/2007 (8:01 am)
I have the same problem that DizzyDoo compiling with 1.5.2. Is there any method to avoid this errors?


Thanks,
HexDump.
#384
06/13/2007 (8:22 am)
I'm trying to put a function in default.bind.cs that needs the camera's transformation to perform a calculation.
I can't seem to figure out how to do this though. here's an example of me trying to get the transformation and echo it:


function gettrans(%val)
{
%camtrans = LocalClientConnection.advCamera.getTransform();
echo(" %camtrans =",%camtrans );
}



my function runs and doesn't give me any errors like it cant find getTransform... it just always gives me 0 0 0 1 0 0 0 . Any other functions i try with advCamera, like getPosition for example, give me either 0 or nothing at all. any idea what the problem is?
#385
06/18/2007 (7:57 am)
Hey there Mr. Man of Ice sir. Has this resource been updated for TGE 1.5? I would greatly appreciate an answer as I am doing a complete rebuild with 1.5.2 (stopping there for a long while, too many versions!!) to help my artists who are crazy over Constructor and want the new updates in the engine. What a blast!!!
Thanks in advance.
#386
06/20/2007 (5:16 am)
OK, Mr. Man of Ice no response, dead maybe definitely not here and definitely not doing this. Can anyone help me or am I on my own? Has this resource been updated for TGE 1.5? I would greatly appreciate an answer as I am doing a complete rebuild with 1.5.2 (stopping there for a long while, too many versions!!) to help my artists who are crazy over Constructor and want the new updates in the engine. What a blast!!!
Thanks in advance.
#387
06/20/2007 (5:40 am)
@Shon,

I compiled this resource in TGE 1.5.1 and last TGEA with no problems. Works great for me.
#388
06/20/2007 (6:44 am)
Outline Interactive Thank you much for your response. That works for me and now I will put it into TGE 1.5.2 and did I say thanks.
#389
06/20/2007 (9:21 am)
I have been using this resource in all versions of torque starting from 1.3. It works fine. There are VERY slight changes. Read the comments for that. But indeed, man of ice isn't responding. I emailed him a few months ago about something.. but i fixed it allready myself.
#390
06/20/2007 (9:21 am)
WOW how lucky can we get. I just implemented this in TGE 1.5.2 and all of the engine code that is here is already in the engine. In other words GameConnection.cc code is exactly the same.
Thanks GG
#391
06/20/2007 (3:42 pm)
@Shon: wow, it sounds like you could have figured it out for yourself, if you'd just lifted a finger and tried, eh?
#392
06/20/2007 (4:26 pm)
most of that was allready true for 1.4 anyway
#393
06/21/2007 (4:56 am)
Hey Lee: thanks for the input I saved a ton of time asking first. Always ask before you do, chances are some other genius has already done it and loves to toot their own horn. That my son is the key to success. Listen to the talkers.
Thanks I appreciate everyones help. Now I have to figure out how to use the different functions Har. I wanted to make it obvious to the others after me. No one posted that the code has been the same since TGE 1.4 (where we gave up for awhile, the days of 3DWS). We finally have what we all agree is a tighter version (profiles well) and can now get on with it. Once again thanks. This is the greatest forum in the world.
#394
06/23/2007 (12:37 am)
Hey guys

I am alive - but this is vacation time and "time away from computer", so please bear with me. I get lots of mails, so I can be slightly slow to respond.

If you have written me the last week or so - you will get a response in a few days (got hundreds of mails to go through after being on a conference this week), and if you have written me before that, then I apologize. Send me an email again please. Maybe I didnt see it, or my spamfilter caught you.

Glad you can use this resource - even with it not having been updated for a long time.

:-)
#395
06/23/2007 (3:33 am)
It was nearly half a year ago.. maybe even more. But it's ok. I got it all myself :) . The result is a few posts up.
#396
06/23/2007 (4:56 am)
No presssssssssure
#397
06/28/2007 (10:37 am)
i implemented the advanced camera but i can't get the camera to work on client side. They keep giving the error of cannot find the object. Is there any way to call the camera from client side or anyway to fix a certain camera view in the game on multiplayer mode?
#398
06/28/2007 (10:57 am)
The camera is server side for security reasons - so you need to do commands to the server part to control it.

The concept is described a few times above - e.g. in the posting from Chris Birmingham Sep 12, 2004
#399
07/07/2007 (7:20 pm)
i cant seem to get it to work, i put in the code to set it up and get rid of it when a player leaves and everything, but i go in and do setcameraobject, hit tab and all i see is sky, i do setplayerobject, and setgodviewmode and hit tab and i see sky then it locks up. i cant seem to figure it out cause it worked last night. any ideas?
#400
07/10/2007 (8:10 am)
Could somebody please explain to me how to set up and use the static camera aspect of this tutorial II