Game Development Community

dev|Pro Game Development Curriculum

Camera Zoom in Torque

by Jared Schnelle · 11/16/2002 (3:17 pm) · 17 comments

Hello there. These are the results of my first attempt to modify the Torque engine and add something that I thought was quite necessary for my game
style. This is targeted toward the beginner, and I hope my comments will make what is going on quite clear.

Structure:
In the code snippets, anything in bold is new, and you need to add it yourself. Ignore the ..., as they are there for brevity.

Introduction:
To begin with, the Torque scripts already contain a value which "zooms in and out". The problem with this value, is that it is only checked once,
upon game startup, and any changes to that value after a game commences is meaningless. That value can be found here:

player.cs
datablock PlayerData(LightMaleHumanArmor)
{
  ... 
  cameraMaxDist = 3;
  ...
}

Remember the location of this line, because it will be your easy way to tweak the zoom once we place all the code. If we follow the path that
cameraMaxDist takes upon game startup, we end up in the engine code, specifically in:

shapeBase.cc
void ShapeBase::getCameraTransform(F32* pos,MatrixF* mat)
{
  ...
  vp.y = -(max - min) * *pos;
  ...
}

The value in max you see above is taken from the cameraMaxDist=3 that was specified in the previous player.cs file. I felt it would be too big
of an undertaking to mess with changing this variable, so I took a different route. To conclude the intro, I know what I wanted to do was press
'Home' to zoom in, and press 'End' to zoom out. I knew that adjusting the line in shapeBase.cc like the following would work:

vp.y = -(max +5 - min) * *pos;
//Zoom out by 5
vp.y = -(max -4 - min) * *pos;
//Zoom in by 4


------------------------------------------
The following are changes I made to the Engine
------------------------------------------
shapeBase.h
class ShapeBase : public GameBase
{
   typedef GameBase Parent;
   friend class ShapeBaseConvex;
   friend void physicalZoneFind(SceneObject*, S32);

   public:

[b]static S32 cameraZoomValue;  // Static Signed Int - Zoom Value that all derived classes will take
virtual void setZoomValue(S32); // Set value, this function should update the cameraZoomValue by the amount we request,
 //and since cameraZoomValue is public, there is no need for a get function[/b]

   enum PublicConstants 
  {
   ...
   ...
  }
}

The most important portion of the code above is the fact that cameraZoomValue is static. Since this variable is in the ShapeBase class, it is automatically
derived by sub-classes of ShapeBase, such as Camera. If this were not made static, there could(and would) be several instances of setZoomValue.
When we try to update the value through the script for the Camera Zoom, the fact that we're updating the Camera::setZoomValue means absolutly
nothing to the ShapeBase::setZoomValue. That's a big problem since our function which actually zooms in and out is a
Member function of ShapeBase, not Camera.

shapeBase.cc (most of our work)
ShapeBase::~ShapeBase()
{
...
}

[b]S32 ShapeBase::cameraZoomValue = 4;  // Init the static cameraZoomValue to 4[/b]

...

void ShapeBase::getCameraTransform(F32* pos,MatrixF* mat)
{
  ...
  [b]vp.y = -(max + cameraZoomValue - min) * *pos;[/b] // Make sure you replace the old line with this new one
  ...
}

...

static void cSetupInvincibleEffect(SimObject* obj, S32, const char** argv)
{
   ShapeBase* shape = static_cast<ShapeBase*>(obj);
   shape->setupInvincibleEffect(dAtof(argv[2]), dAtof(argv[3]));
}

[b]static void cSetZoomValue(SimObject *ptr, S32, const char **argv)
{
   ShapeBase* obj = static_cast<ShapeBase*>(ptr); // The script gives us a zoom value, such as "1", we need to turn it into
   obj->setZoomValue(dAtof(argv[2]));             // an integer, then call the update function setZoomValue(S32)
}[/b]

...
[b]
void ShapeBase::setZoomValue(S32 increment)
{
	S32 temp = cameraZoomValue + increment;  
	if (temp > -5 && temp < 20)
	    cameraZoomValue += increment; // Static / Public Data
	                                  // Zoom will carry over to any derived class
	// The zoom function actually moves the camera, so we dont want them to wander
	// really far off into the sky, so lets limit the zoom to be between -5 <--> 20
	// The -5 is necessary because the zoom is initilized to 4.  So, this lets us have
	// a max zoomin of -1, and max zoomout of 24
}
[/b]


void ShapeBase::initPersistFields()
{
...
}

void ShapeBase::consoleInit()
{
   Con::addVariable("SB::DFDec", TypeF32, &sDamageFlashDec);
   ...
   Con::addCommand("ShapeBase", "setDamageVector", cSetDamageVector, "obj.setDamageVector(vec)", 3, 3);
[b]   Con::addCommand("ShapeBase", "setZoomValue", cSetZoomValue, "obj.setZoomValue(value)", 3, 3);[/b]
}

Breakdown:
Con::addCommand("Class Type(ShapeBase), Function Name, Function to actually Call,
Script way to call the function we want, min parameters, max parameters);

When we actually call the equivilent of obj.setZoomValue(3) in the script, we're going to find this line. It knows to expect 1 parameter
{because there's always a minimum of 2, so to pass a function with no parameters we would do, 2, 2); We want 1 parameter so we do, 3, 3);}
When we find this line we call cSetZoomValue("3"). The script passes every parameter as a string,
and it's up to us to decide what to do with it once the code receives it. We decide to turn it back into an integer in the cSetZoomValue,
and toss it over to setZoomValue(3). Once setZoomValue has the int 3, it checks to
make sure we're not trying to zoom out too far. If everything is ok, it applies the number
and we zoom out!

----------------------------------------------
The following are changes I made in the script
----------------------------------------------

commands.cs
function serverCmdZoomInCamera(%client)
{
    %client.getControlObject().setZoomValue("-1");
}

function serverCmdZoomOutCamera(%client)
{
     %client.getControlObject().setZoomValue(1);
}
We create 2 server commands. It's necessary to do this so we know who's client is asking to zoom in. We use getControlObject()
to find out what the %client is actually controlling. Is it a person, a camera, a car?

default.bind.cs
//------------------------------------------------------------------------------
// Camera & View functions
//------------------------------------------------------------------------------
[b]
//Camera Zoom in and out, 3rd person view - Keyboard
function zoomIn(%val)
{
  if (%val)
     commandToServer('ZoomInCamera');
}

function zoomOut(%val)
{
  if (%val)
     commandToServer('ZoomOutCamera');
}


MoveMap.bind(keyboard, "home", zoomIn);
MoveMap.bind(keyboard, "end", zoomOut);

//Camera Zoom in and out, 3rd person view - Mouse Wheel
function handleMouseWheel(%val)
{
   // Check if the wheel scrolled up or down
   if( %val > 0 )
         commandToServer( 'ZoomInCamera' ); //Mwheel Up
   else
         commandToServer( 'ZoomOutCamera' );//Mwheel Down

}

moveMap.bind(mouse, "zaxis", handleMouseWheel );
[/b]

The first portion binds the zoom in and out to the Home and End keys on the keyboard. The 2nd part binds
it to the mouse wheel up and down. If you use mouse wheel for something else, I dont suggest adding this part.


config.cs
[b]
//Added Zoom In / Out
moveMap.bind(keyboard, "home", zoomIn);
moveMap.bind(keyboard, "end", zoomOut);
[/b]
moveMap.bind(mouse0, "xaxis", yaw);
moveMap.bind(mouse0, "yaxis", pitch);
[b]moveMap.bind(mouse0, "zaxis", handleMouseWheel);[/b]
moveMap.bind(mouse0, "button0", mouseFire);

This is everything you need to get camera zoom working. I hope my documentation was thorough and you understood everything I tried to say here.
In closing, I guess I'll make mention of a few points:

-I didnt add the Home/End/MouseWheel to the Options Dialog GUI screen.
-You can adjust the intialzoomed out amount through the maxZoomDist in the player.cs file
-There's another way to add console commands to the engine, but it is used for regular
functions, not member functions. Do a search for ConsoleFunction in your source code
-The camera code in general needs to be optimized. Currently the engine checks the camera position in a constant loop. If
you're sitting still and staring at the same spot for 10 minutes, then the camera position gets checked hundreds of thousands of times. Is this necessary?
Cant we just have it check for a new position when we make a move? Probably. Maybe I'll try to knock that one out too, eventually.

Hope you enjoyed it, any questions or comments are welcome.
-Jared

About the author

Recent Blogs


#1
10/27/2002 (10:06 pm)
Works like a charm, thanks!!
One question though: shouldn't this work in 1st person, too, theoretically? Did you find anything during your investigations that keeps the camera distance to a fixed rate in 1st person mode?
#2
10/28/2002 (6:32 am)
This resource moves the camera position to zoom, the alternative is to alter the FOV. Although this will give walleye in some cases, it may be preferable when you want a more realistic zoom (lens style). Try it, ~ setFOV(45); Sorry I didn't think of that one earlier Jafa.

Also, if anyone wants to support multimon (yes I know, not likely) they will probably need to add an accessor function to ShapeBase and remove the static from cameraZoomValue. This is because you will need several cameras, and a static zoom won't cut it.

Oh Jafa, does my comment about "the best place to hide something is out in the open" make more sense now? =)
#3
04/11/2003 (5:53 am)
This has been submitted to the GarageGames staff as a patch for inclusion in the HEAD source. We'll see if it gets accepted.
#4
03/08/2004 (12:12 pm)
Hello Jared.

I tried this resource. Followed all the instructions. Everything compiled fine. But I'm not getting any zooming in or out with either the mouse or home and end keys. Has anyone else had a problem with this resource? Any help would be apreciated.

Thanks.
#5
08/16/2004 (8:12 pm)
It seems HEAD isn;t matching up with this resource.. for those that want to implement it, do teh following.

1. Ignore the "cSetZoomValue" function ... yes the one that STARTS with "c" .. it was necessary for the older way of setting up console functions. Basically.. don't add this function.

2. Change the "Con::addCommand" call to the following..
ConsoleMethod( ShapeBase, setZoomValue, void, 3, 3, "(int increment)")
{
   object->setZoomValue(dAtof(argv[2])); // an integer, then call setZoomValue(S32)
}
#6
08/01/2005 (1:51 pm)
the camera is locked to the eye node in first person so fov is the best way to go. I think it would be preferable anyway because this method you could zoom through walls and fog.
#7
02/20/2006 (3:52 pm)
I just added this to the 1.4 SDK (not the 1.4 HEAD) and it works fine with Cameron Aycock's change. Great work Jared - still works in 1.4 :-)
#8
02/20/2006 (5:59 pm)
To add the zoom in and zoom out options to the Option Dlg GUI, simply add these lines to /client/scripts/client/optionsDlg.cs in between the applyGraphics function and the restoreDefaultMappings functions with the other $Remap array definitions (right around line 237):


$RemapName[$RemapCount] = "Camera Zoom In";
$RemapCmd[$RemapCount] = "zoomIn";
$RemapCount++;
$RemapName[$RemapCount] = "Camera Zoom Out";
$RemapCmd[$RemapCount] = "zoomOut";
$RemapCount++;
#9
03/11/2006 (7:03 pm)
If you copy your code from tutorial.base and start from scratch you have to change main.cs (inside your folder that you copied) to exec commands.cs like so

function initServer()
{
echo("\n--------- Initializing TTB: Server ---------");

// The common module provides the basic server functionality
initBaseServer();

// Load up game server support scripts
exec("./server/game.cs");


echo("\n\n -----------LOADING SCRIPT-----------\n");
exec("./server/commands.cs");
}
#10
05/25/2006 (4:11 am)
Great resource! Thanks! :) I found that the zoom is not very smooth, so I changed the code a bit to make it smoother. I made the following changes:

Find the following line in ShapeBase.h:
static S32 cameraZoomValue;  // Static Signed Int - Zoom Value that all derived classes will take

Change it to this:
static F32 cameraZoomValue;  // Static Float - Zoom Value that all derived classes will take

Now search for this line in ShapeBase.cc:
S32 ShapeBase::cameraZoomValue = 4;  // Init the static cameraZoomValue to 4

Change it to:
F32 ShapeBase::cameraZoomValue = 4.0;  // Init the static cameraZoomValue to 4

In ShapeBase.cc (void ShapeBase::setZoomValue(S32 increment)) change this:
S32 temp = cameraZoomValue + increment;
	if (temp > -5 && temp < 20)
	    cameraZoomValue += increment; // Static / Public Data

Like this:
F32 temp = cameraZoomValue + increment;
	if (temp > -5 && temp < 20)
	    cameraZoomValue += ((F32)increment/10); // Static / Public Data

Now the zoom function should increment by 0.1 rather than 1 which results in a much smoother zoom behaviour. I tried to divide increment by 20 - that's even smoother, but the player would have to turn the mousewheel too much, so I think 10 is a good value.
#11
07/30/2006 (12:53 am)
Have you noticed, that you get 'inside' the character mesh when zooming in? Here's one way to get rid of that effect:

// At first we check whether we are close enough to the character when in 3rd person mode.
	// If so, snap the camera to 1st person view.
	if(cameraZoomValue <= -1.2 && increment < 0)
	{
		increment = 0;
		cameraZoomValue = -5;
	}
	// We are in 1st person view already and player is trying to zoom out?
	// Snap the camera to 3rd person view then.
	else if(cameraZoomValue == -5 && increment > 0)
	{
		increment = 0;
		cameraZoomValue = -1.2;
	}

Just add it at the beginning of the ShapeBase::setZoomValue method in ShapeBase.cc and you're set :)
#12
08/27/2006 (8:45 pm)
Thanks a lot Jared. This was exactly what I was looking for. I added this resource to TGE 1.4 using Cameron's update and everything worked perfect. I did notice how choppy the zoom in/out was. As Kaya mentioned, the increment values are only integers, so its not going to be very smooth. However Kaya's edits don't actually make the camera any smoother, they just increase the precision. To implement a smooth camera you need to blend between the current camera zoom and the desired zoom. If you've successfully added in the original resource, along with Cameron and Kaya's edits, you can then make the following modifications for an interpolated smooth camera. Modifications are in bold.

Add the following in shapeBase.h:
static F32 cameraZoomValue;  // Static Float - Zoom Value that all derived classes will take
[b]static F32 cameraZoomSmooth;  // Static Float - Smoothed Zoom Value used for interpolation
static F32 cameraZoomRate;  // Static Float - Blend rate used for interpolation[/b]
virtual void setZoomValue(S32); // Set value...

Add the following in shapeBase.cc:
F32 ShapeBase::cameraZoomValue = 4.0;  // Init the static cameraZoomValue to 4
[b]F32 ShapeBase::cameraZoomSmooth = 4.0;  // Init the static cameraZoomSmooth to same as cameraZoomValue
F32 ShapeBase::cameraZoomRate = 0.01; // Init the rate of zoom (0.01 = slow | 1.0 = no blend | do not go above 1)[/b]

Added interpolation to blend smoothly to the next zoom increment.
// Use the eye transform to orient the camera
VectorF vp,vec;
vp.x = vp.z = 0;
[b]cameraZoomSmooth -= (cameraZoomSmooth - cameraZoomValue)*cameraZoomRate; // interpolate zoom
vp.y = -(max + cameraZoomSmooth - min) * *pos; // take zoom value into account[/b]
eye.mulV(vp,&vec);

We can remove the divide by 10 since the interpolation will handle this.
F32 temp = cameraZoomValue + increment;
if (temp > -5 && temp < 20)
[b]cameraZoomValue += ((F32)increment); // Static / Public Data[/b]

Edit the command.cs functions to a larger increment value:
function serverCmdZoomInCamera(%client)
{
    [b]%client.getControlObject().setZoomValue("-4");[/b]
}

function serverCmdZoomOutCamera(%client)
{
     [b]%client.getControlObject().setZoomValue(4);[/b]
}

Now the camera will be both smooth and able to increment by large distances. Without this addition, every mouse wheel movement resulted in a distinct zoom step (ie it will merely snap to the next location). With these few changes it will blend smoothly between the current camera zoom position and the desired position. I hope some people find this useful.
#13
10/22/2006 (10:29 pm)
I modified the FPSStarter scripts according to the provided scripts and fixes. Yet I cant get it to work, in the console it says "common/server/commands.sc unkown command setZoomValue"

I just want to be able to zoom in/out with my mouse wheel.
#14
12/26/2006 (2:02 pm)
Jared,

can you point out how to change the code that this zoom is working with a dedicated server/client model?

Like in a related forumpost, the problem is obv. that the zoomvalue isnt populated over the network?
Any hint/help?
#15
02/05/2007 (5:09 pm)
I am not sure why this is a server command? Why would the server care if the player is zoomed in or not?
#16
10/10/2007 (12:44 pm)
can someone remake a clear tuto with all those info please ?
im not a really a programmer, i just want to do a gameplay demo for my gamedesign and i want to add this zoom fonction but there are a lot of modification so i wonder what to keep, what to trash ?

thx through to all.
#17
07/22/2009 (8:20 pm)
Thanks, @Jared for making this a resource, quite useful!
And thank you @Andres for the smooth camera track~~~