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.
#241
04/30/2006 (12:12 pm)
Is it just me or did this resource get cut off after

Exposing mCameraObject
The first thing you want to do is add console method
#242
04/30/2006 (12:46 pm)
Someone introduced a bugger in the website.

www.garagegames.com/mg/forums/result.thread.php?qt=43559
#243
04/30/2006 (1:05 pm)
Is there somewhere we can go to see what got cut off?
#244
05/01/2006 (10:17 am)
Hi all,

I have seen comments made in the forums that the advanced camera takes up a significant amount of network bandwidth. Is this true in all camera modes, or just the God View (or not at all)?

Thanks
Todd
#245
05/02/2006 (3:36 am)
@William: It's in all of the modes, but look at what Shannon "ScarWars" Scarvaci posted Nov 04, 2005 at (04:35)... That fixes it...
#246
05/02/2006 (7:53 am)
Great!

So, there really is no reason not to use this camera?

Thanks BurNing.
#247
05/03/2006 (1:13 am)
I have found a few small issues:
- Godcam still turning when looking from directly above
- Sometimes particles are not being rendered
- The player name Gui (the one that shows name over player) not always showing (same reason as the particles, i think)

Otherwise it's all good.
#248
05/03/2006 (5:04 am)
Tom found an issue and a correction that is discussed in this link:

http://www.garagegames.com/mg/forums/result.thread.php?qt=26875

I don't think I saw a reference to it above, so I thought I'd include here.
I wonder if this isn't related to the particles/player name issue?
#249
05/07/2006 (2:23 am)
Hi All,

This resource is great. I am having one issue using God Mode. I am using a different object as the view rather than the player. When it moves everything shakes. This is the code i use to control the moving the camera:

function MoveControlCam()
{
%xfrm = CamControl.getTransform();
%lx = getword(%xfrm,0); //get the current transform values
%ly = getword(%xfrm,1);
%lz = getword(%xfrm,2);
%lx += 1; //adjust the x axis position
CamControl.setTransform(%lx SPC %ly SPC %lz SPC "0 0 1 0");
schedule(100,0,MoveControlCam);
}

I'm very new to torque so not sure if i'm using the best approach here. Is there a better way of doing this so there is minimal shake/wobble? The only way to avoid it is moving the camera very slowly at the moment which is of no use to my project.

Can anyone help?

Thanks again
#250
05/18/2006 (11:42 pm)
I found a quick easy fix for the turning in Godview mode, if you don't mind it always looking directly down, regardless of offset. What's happenning is that the camera is calculating it's rotation based on the targetObject. When you move fast enough, the camera doesn't quite keep up, and falls behind the target, and begins calculating rotations that aren't really straight down or oriented right, in super stupid terms. What I'm doing is having the rotation of the camera in Godview mode be static- looking down the Z axis. This means that when you offset the camera to the side, the target object will actually be offset slightly, instead of the camera turning to look at the object from it's perch. Not quite as slick as before, but it definately doesn't turn. I'm still working out the exact math to make it work; the only problem is that the camera is twisted exactly 90 degrees from where it ought to be.

Here's the preliminary stuff, maybe you can help me make it work right:

In AdvancedCamera::setPosition, change
if (obj && mMode != StaticMode) {

to

if (obj && mMode != StaticMode && mMode != GodViewMode) {

And insert this after that if block:
else if(mMode == GodViewMode){
		transMat = MathUtils::createOrientFromDir(Point3F(0,0,-1));
#251
05/18/2006 (11:52 pm)
Oh, and Adam, about your problem. You're moving the camera manually, with it in GodViewMode? That'll cause problems. First, if you want it to focus on something other than the player, just set the player object to that object: advCamera.setPlayerObject(%myNonPlayerObjectToFollow);

Also, don't try to more the camera around by it's transform in Godview. For my project, I move the camera view the GodViewOffset values- very similar to what you're doing, but the camera isn't going to try and fight you. I think what's happenning to you is that you're moving the camera forcefully, and it's trying to gradually move to where it thinks it should be, creating that shake. With the GodViewOffset values, the camera thinks it's supposed to be right where you're telling it to be.

Last idea, if that doesn't work, is to check the physics of your objects. My modified flying vehicle physics get a little shaky at high speeds (from bumping into my speed limits, I hope), and without a terrain, it's kind of difficult to say whether the camera or the object itself is doing the shaking. Just something to be aware of.

Good luck!
#252
06/09/2006 (6:43 am)
I have the Torque Game Engine 1.4 and have been trying to get the advancedCamera to work but to no avail. I have been working through getting everything to compile and that is now working fine ( I had to leave GameConnection::setCameraObject as it was or it wouldn
#253
06/09/2006 (6:47 am)
Sorry my post got cut off...I hope this works:
I have the Torque Game Engine 1.4 and have been trying to get the advancedCamera to work but to no avail. I have been working through getting everything to compile and that is now working fine ( I had to leave GameConnection::setCameraObject as it was or it would not compile. ) However I am still getting an error in my console when I run my game. The error in the console reads: GameOne/server/game.cs (68): Unable to instantiate non-conobject class AdvancedCamera.

I am using :
visual c++2005 express edition and have downloaded the patch (VS2005) for 1.4.
Microsoft 2000 service pack 4
TGE 1.4

If any one can see where I have gone wrong I would greatly appreciate it.
#254
06/09/2006 (7:26 am)
Hi Jeff - #1 reason for this error: you forgot to add the advancedcamera.cc/.h files to your project

Also - as the comments suggest. You do not need to do any changes in 1.4. Just add the 2 adv cam cc and h files + recompile.

The other changes have made it into the engine
#255
06/10/2006 (5:13 am)
Somebody is able to start the game in third person with advanced camera?
I get a crash on mission loading.I tried to change mFirstPerson = false but game crash.

I resolved with a trick but I looking for a best solution.
I add this function in client/script/ServerConnection.cs
function StartThirdPerson()
{
ServerConnection.setFirstPerson( false );
}
and then I add schedule(150, 0, StartThirdPerson);
in function GameConnection::initialControlSet below Canvas.setContent.

Thanks in advance.
#256
06/12/2006 (7:29 am)
@Thomas.
Thanks, that has that problem fixed.
I am new to using a compiler. I'll just have to keep at it...

Jeff
#257
06/12/2006 (11:27 am)
Ok. Over one hurdle, now I have another question.
Has any one ever had the following appear in the console?
....
Object 'AdvCameraData' is not a member of the 'GameBaseData' block class
GameOne/server/game.cs (68): Registered object failed for object (null) of class AdvancedCamera.
Set::add Object "0" doesn't exist
GameOne/server/game.cs (70): Unable to find object: '0' attampting to call function 'scopeToClient'
....

Any help would be greatly appreciated.

Thanks,

Jeff
#258
06/14/2006 (1:10 pm)
Never mind my previous question. I am a fool. I am still figuring out the file structure and changed the wrong files. I mention this humbling piece of information so others might avoid this pitfall. For the absolute beginner it is best to copy and modify the starter.fps. The structure in that folder seems to be used in this and a few other tutorials.

Jeff
#259
06/15/2006 (6:57 am)
I just wanted to add a small correction to one of the suggestion by Shannon "ScarWars" Scarvaci. I've noticed that in the suggested changes, there were slight confusions with the a number of methods posted that may cause your code not to work, so I wanted to post the full version. In the first time I changed it, I made some mistakes in the writePacketData which caused the TGE to crash due to the difference between the write and read packet data and I think many of us did the same mistake :P I've listed the code in the function with the changes in bold along with the relationship with the old code:

void AdvancedCamera::writePacketData(GameConnection *connection, BitStream *bstream) {
	// Update client regardless of status flags.
	Parent::writePacketData(connection, bstream);

	Point3F pos;
	mObjToWorld.getColumn(3,&pos);
	bstream->setCompressionPoint(pos);

	mathWrite(*bstream, pos);
[b]
	/* - Old code that is not used
	mathWrite(*bstream, mCameraPos);
	mathWrite(*bstream, mCurrentLookAtOffset);
	mathWrite(*bstream, mCurrentThirdPersonOffset);
	mathWrite(*bstream, mCurrentGodViewOffset);
	mathWrite(*bstream, mCurrentOrbitOffset);   
	mathWrite(*bstream, mCurrentOrbitMinMaxZoom);
	mathWrite(*bstream, mCurrentOrbitMinMaxDeclination);
        */

	bstream->writeFlag(bCameraPos);
	if (bCameraPos)
		mathWrite(*bstream, mCameraPos);
	bCameraPos = false;

	bstream->writeFlag(bCurrentLookAtOffset);
	if (bCurrentLookAtOffset)
		mathWrite(*bstream, mCurrentLookAtOffset);
	bCurrentLookAtOffset = false;

	bstream->writeFlag(bCurrentThirdPersonOffset);
	if (bCurrentThirdPersonOffset)
		mathWrite(*bstream, mCurrentThirdPersonOffset);
	bCurrentThirdPersonOffset = false;

	bstream->writeFlag(bCurrentGodViewOffset);
	if (bCurrentGodViewOffset)
		mathWrite(*bstream, mCurrentGodViewOffset);
	bCurrentGodViewOffset = false;

	bstream->writeFlag(bCurrentOrbitOffset);
	if (bCurrentOrbitOffset)
		mathWrite(*bstream, mCurrentOrbitOffset);   
	bCurrentOrbitOffset = false;

	bstream->writeFlag(bCurrentOrbitMinMaxZoom);
	if (bCurrentOrbitMinMaxZoom)
		mathWrite(*bstream, mCurrentOrbitMinMaxZoom);
	bCurrentOrbitMinMaxZoom = false;

	bstream->writeFlag(bCurrentOrbitMinMaxDeclination);
	if (bCurrentOrbitMinMaxDeclination)
		mathWrite(*bstream, mCurrentOrbitMinMaxDeclination);
	bCurrentOrbitMinMaxDeclination = false;
[/b]
	U32 writeMode = mMode;

void AdvancedCamera::readPacketData(GameConnection *connection, BitStream *bstream) 
{
	Parent::readPacketData(connection, bstream);

	Point3F pos;
	mathRead(*bstream, &pos);
	bstream->setCompressionPoint(pos);

[b]
/*- old code that is not used
	mathRead(*bstream, &mCameraPos);
	mathRead(*bstream, &mCurrentLookAtOffset);
	mathRead(*bstream, &mCurrentThirdPersonOffset);
	mathRead(*bstream, &mCurrentGodViewOffset);
	mathRead(*bstream, &mCurrentOrbitOffset);   
	mathRead(*bstream, &mCurrentOrbitMinMaxZoom);
	mathRead(*bstream, &mCurrentOrbitMinMaxDeclination);
*/

	bCameraPos = bstream->readFlag();
	if (bCameraPos)
		mathRead(*bstream, &mCameraPos);
	bCameraPos = false;
	
	bCurrentLookAtOffset = bstream->readFlag();
	if (bCurrentLookAtOffset)
		mathRead(*bstream, &mCurrentLookAtOffset);
	bCurrentLookAtOffset = false;
	
	bCurrentThirdPersonOffset = bstream->readFlag();
	if (bCurrentThirdPersonOffset)
		mathRead(*bstream, &mCurrentThirdPersonOffset);
	bCurrentThirdPersonOffset = false;
	
	bCurrentGodViewOffset = bstream->readFlag();
	if (bCurrentGodViewOffset)
		mathRead(*bstream, &mCurrentGodViewOffset);
	bCurrentGodViewOffset = false;
	
	bCurrentOrbitOffset = bstream->readFlag();
	if (bCurrentOrbitOffset)
		mathRead(*bstream, &mCurrentOrbitOffset);   
	bCurrentOrbitOffset = false;
	
	bCurrentOrbitMinMaxZoom = bstream->readFlag();
	if (bCurrentOrbitMinMaxZoom)
		mathRead(*bstream, &mCurrentOrbitMinMaxZoom);
	bCurrentOrbitMinMaxZoom = false;
	
	bCurrentOrbitMinMaxDeclination = bstream->readFlag();
	if (bCurrentOrbitMinMaxDeclination)
		mathRead(*bstream, &mCurrentOrbitMinMaxDeclination);
	bCurrentOrbitMinMaxDeclination = false;
[/b]
	mMode = bstream->readRangedU32(CameraFirstMode, CameraLastMode);

By implementing this resource, it will lower the bandwidth requirement between the client and server. The original advanced camera resource will send the information on a regular basis adding alot of information that has to be exchanged. This resource will lower the bandwidth requirement as if there are no changes, the data of the advanced camera would not be sent. I've checked both versions with the netstat tool, and there is drastic improvements :)
#260
06/22/2006 (11:30 am)
hey great resource where do i add %this.advCamera.setGodViewMode(); it dosnt say sorry im put new to coding