Using a Separate Camera Object
by Cory Osborn · 10/27/2003 (9:52 am) · 71 comments
Download Code File
For my game, I need a few different 3rd person cameras. There are a couple of other good tutorials here to show off different 3p modes (like 21-6's dynamic camera tutuorial), but in my research I found that GameConnection already has some code to use a separate camera - all it needs is some tweaking.
There are 4 parts to this:
The first two parts of this process are required to make a separate camera work within the game. The second half is just an example of plugging a different camera into the game.
note: this code has been verified against the CVS HEAD as of 04/03/2004.
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:
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:
and here is my GameConnection::setCameraObject:
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:
In GameConnection::writePacket, find this block of code:
Creating a camera object
Next we need an object to use as our camera. If you look at the code in GameConnection::getControlCameraTransform, you can see how GameConnection determines the position and rotation for rendering a frame. Based upon that code, any class descending from ShapeBase could theoretically be plugged in as the connection's camera.
The standard Camera class is one option. Another option is the TrackingCamera class attached to the article. TrackingCamera will change its orientation in order to track another object in the game. Without looking at its code, you'll just need to know that it exports a console method setTrackObject that allows you to specify which object will be tracked. One warning - the TrackingCamera will suffer from Gimbal Lock.
Putting it in the game
Here is the basic areas of code to change to see the example tracking camera in starter.fps. There are other areas where you'll need to tweak the script in order to unhook the camera and return to first-person mode, but I'll leave that for you as an exercise :)
First, add a datablock for the tracking camera to /starter.fps/server/scripts/camera.cs:
Next, add it to the connection just like is currently done with the base camera class. Add this inside GameConnection::onClientEnterGame (/starter.fps/server/scripts/game.cs), right after %this.camera is set up:
We'll need to clean it up after the client leaves the game, so add this to GameConnection::onClientLeaveGame
We need to tell it what to track and assign the connection's camera object, so add this to the end of GameConnection::createPlayer:
And we'll want to unhook it when the player dies. Insert this at the beginning of GameConnection::onDeath:
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.
Hope it helps. - cory
Additional Resources
My resource on more intuitive movement controls for a third person camara Thomas Lund's work for additional camera modes Thomas Lund's combination of camera flipping with mission regions
Edits
1/11/2004 - Updated the attached TrackingCamera class to override ShapeBase::onCameraScopeQuery so the tracked object is always scoped to the client.
4/3/2004 - Verified the code still works on the latest HEAD and changed the example file paths to refer to starter.fps
4/4/2004 - Added additional resources links
For my game, I need a few different 3rd person cameras. There are a couple of other good tutorials here to show off different 3p modes (like 21-6's dynamic camera tutuorial), but in my research I found that GameConnection already has some code to use a separate camera - all it needs is some tweaking.
There are 4 parts to this:
- exposing GameConnection::mCameraObject to the console
- having GameConnection read/write camera packets just as it does for the control
- having a class that operates your camera mode
- setting script to use the new camera in the game
The first two parts of this process are required to make a separate camera work within the game. The second half is just an example of plugging a different camera into the game.
note: this code has been verified against the CVS HEAD as of 04/03/2004.
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 toif (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 );Creating a camera object
Next we need an object to use as our camera. If you look at the code in GameConnection::getControlCameraTransform, you can see how GameConnection determines the position and rotation for rendering a frame. Based upon that code, any class descending from ShapeBase could theoretically be plugged in as the connection's camera.
The standard Camera class is one option. Another option is the TrackingCamera class attached to the article. TrackingCamera will change its orientation in order to track another object in the game. Without looking at its code, you'll just need to know that it exports a console method setTrackObject that allows you to specify which object will be tracked. One warning - the TrackingCamera will suffer from Gimbal Lock.
Putting it in the game
Here is the basic areas of code to change to see the example tracking camera in starter.fps. There are other areas where you'll need to tweak the script in order to unhook the camera and return to first-person mode, but I'll leave that for you as an exercise :)
First, add a datablock for the tracking camera to /starter.fps/server/scripts/camera.cs:
...
datablock TrackingCameraData(TrackCameraData)
{
trackAdjPos = "0 0 2";
};
...Next, add it to the connection just like is currently done with the base camera class. Add this inside GameConnection::onClientEnterGame (/starter.fps/server/scripts/game.cs), right after %this.camera is set up:
...
// create in-game stationary camera
%this.trackCamera = new TrackingCamera() {
dataBlock = TrackCameraData;
};
MissionCleanup.add(%this.trackCamera);
%this.trackCamera.scopeToClient(%this);
...We'll need to clean it up after the client leaves the game, so add this to GameConnection::onClientLeaveGame
...
if (isObject(%this.trackCamera))
%this.trackCamera.delete();
...We need to tell it what to track and assign the connection's camera object, so add this to the end of GameConnection::createPlayer:
... // set the stationary camera to the same position %this.trackCamera.setPosition(%this.camera.getPosition()); %this.trackCamera.setTrackObject(%player); %this.setCameraObject(%this.trackCamera); ...
And we'll want to unhook it when the player dies. Insert this at the beginning of GameConnection::onDeath:
... // clear connection's camera %this.trackCamera.clearTrackObject(); %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.
Hope it helps. - cory
Additional Resources
Edits
1/11/2004 - Updated the attached TrackingCamera class to override ShapeBase::onCameraScopeQuery so the tracked object is always scoped to the client.
4/3/2004 - Verified the code still works on the latest HEAD and changed the example file paths to refer to starter.fps
4/4/2004 - Added additional resources links
About the author
#22
just put this in my build. Works great, thankyou Cory, I personally think this should be put in the head.
Anyway, one question: can multiple players use the same camera object as their, er, cameraObject?
Ian
03/09/2004 (10:59 am)
Hey,just put this in my build. Works great, thankyou Cory, I personally think this should be put in the head.
Anyway, one question: can multiple players use the same camera object as their, er, cameraObject?
Ian
#23
I have used it, and everything is working nicely.
I am trying to do something with the camera that I thought would be a trivial task, but so far it has been a nightmare. I have been trying to find a way to control the height and distance of the default 3rd person camera for a vehicle as the player during gameplay.
I want to be able to switch through multiple 3rd person views. The only difference of these views would be their height and distance from the vehicle.
Can I do this using your trackingcamera? Your comments in TrackingCamera.h mention a tethered mode. Is this something you have implemented or are still working on?
Anyway, thank you again.
03/10/2004 (11:10 am)
Thank you Cory for this excellent resource!I have used it, and everything is working nicely.
I am trying to do something with the camera that I thought would be a trivial task, but so far it has been a nightmare. I have been trying to find a way to control the height and distance of the default 3rd person camera for a vehicle as the player during gameplay.
I want to be able to switch through multiple 3rd person views. The only difference of these views would be their height and distance from the vehicle.
Can I do this using your trackingcamera? Your comments in TrackingCamera.h mention a tethered mode. Is this something you have implemented or are still working on?
Anyway, thank you again.
#24
03/10/2004 (11:12 am)
Ian, I see you are currently using this resource. Maybe we will be able to help each other out. Best wishes, Ryan
#25
@Ryan - yes I have implemented several tethered modes in my own version of the camera class. Unfortunately, it has so many dependencies to other modifications I've done to the engine (additions to ShapeBase, changes to the Move structure, ability to send independent Moves just to the camera) that posting it would require writing a completely separate resource. I WISH I had the time to do that, but for the last 2 months I've had very little time to even work on my game.
I can give you a basic approach I use when in tether mode. There are a few limitations I place on the camera - a min and max pitch, and a min/max distance - both in relationship to the tracked/tethered object. With every tick (the camera should process after the tracked object), I check the current pitch angle and distance to see if the camera needs to move. If it does need to move I determine the ideal position and then create a velocity vector that would move me to that point in single tick. Once the velocity vector is determined, I run through some collision detection similar to what is found in the Player class - the velocity is altered if we would run into things but attempts to keep the camera moving in the desired direction. I then apply the altered velocity to the current position to determine the new position. After you've determined the position, you'll also want to reset the rotation angles so that the camera still points at the tracked object.
03/10/2004 (11:39 am)
@Ian - no, multiple players cannot use the same camera object because in GameConnection I treat this similar to the control object. Whenever you set the an individual connection's cameraObject it will look to see if another object was previously controlling it, and if so, it will remove it from the previous connection.@Ryan - yes I have implemented several tethered modes in my own version of the camera class. Unfortunately, it has so many dependencies to other modifications I've done to the engine (additions to ShapeBase, changes to the Move structure, ability to send independent Moves just to the camera) that posting it would require writing a completely separate resource. I WISH I had the time to do that, but for the last 2 months I've had very little time to even work on my game.
I can give you a basic approach I use when in tether mode. There are a few limitations I place on the camera - a min and max pitch, and a min/max distance - both in relationship to the tracked/tethered object. With every tick (the camera should process after the tracked object), I check the current pitch angle and distance to see if the camera needs to move. If it does need to move I determine the ideal position and then create a velocity vector that would move me to that point in single tick. Once the velocity vector is determined, I run through some collision detection similar to what is found in the Player class - the velocity is altered if we would run into things but attempts to keep the camera moving in the desired direction. I then apply the altered velocity to the current position to determine the new position. After you've determined the position, you'll also want to reset the rotation angles so that the camera still points at the tracked object.
#26
I will continue working on the camera to try to make it a simple task to adjust the height of the current torque camera during game play.
The wheeledVehicle datablock in the car.cs script has two settings which determine the x and z position of the camera, or maxdistance and cameraOffset.
These allow me to get different 3rd person camera views at the start of the game, but then I cannot change those settings during the game.
I found a resource that allows me to zoom the camera, but unfortunately adjusting the offset for the camera has been a very difficult task. I believe as I become more familiar with the Engine code I will find a way to do it. But if you have any ideas I'd appreciate it.
If I figure it out I will let you know how I did it.
Best wishes, Ryan
03/11/2004 (6:34 am)
Thank you for the quick reply Cory! And thank you for the description of how to do tether.I will continue working on the camera to try to make it a simple task to adjust the height of the current torque camera during game play.
The wheeledVehicle datablock in the car.cs script has two settings which determine the x and z position of the camera, or maxdistance and cameraOffset.
These allow me to get different 3rd person camera views at the start of the game, but then I cannot change those settings during the game.
I found a resource that allows me to zoom the camera, but unfortunately adjusting the offset for the camera has been a very difficult task. I believe as I become more familiar with the Engine code I will find a way to do it. But if you have any ideas I'd appreciate it.
If I figure it out I will let you know how I did it.
Best wishes, Ryan
#27
I have tried creating a console method for the vehicle class, adding a console variable, all kinds of things. It seems like it should not be this difficult. Any ideas?
Thanks again.
03/11/2004 (6:37 am)
here is what i am planning on trying. Let me know if this is possible. I would like to make the cameraOffset field available to my scripts to be changed after the car is made.I have tried creating a console method for the vehicle class, adding a console variable, all kinds of things. It seems like it should not be this difficult. Any ideas?
Thanks again.
#28
when using a vehilcle with the car.cs script there is a field in the car datablock called cameraOffset.
I was able to change the camera height during gameplay by writing a function that would reset this value like so:
function foo{
%this.player.dataBlock.cameraOffset = 3.0;
}
Thanks again Cory for your help. I will be using the cool tracking camera as well!
best wishes, ryan
03/12/2004 (12:58 pm)
Hey guys. I figured out what i needed. It turned out to be very simple.when using a vehilcle with the car.cs script there is a field in the car datablock called cameraOffset.
I was able to change the camera height during gameplay by writing a function that would reset this value like so:
function foo{
%this.player.dataBlock.cameraOffset = 3.0;
}
Thanks again Cory for your help. I will be using the cool tracking camera as well!
best wishes, ryan
#29
Here is the End of my console.log
Ghost Always objects received.
Mapping string: MissionStartPhase3 to index: 10
Client Replication Startup has Happened!
fxFoliageReplicator - Client Foliage Replication Startup is complete.
*** Phase 3: Mission Lighting
Successfully loaded mission lighting file: 'starter.fps/data/missions/stronghold_4b248e80.ml'
Mission lighting done
Mapping string: MissionStartPhase3Ack to index: 2
Mapping string: MissionStart to index: 11
Mapping string: SyncClock to index: 12
Could not locate texture: starter.fps/data/shapes/player/crossbow
Could not locate texture: starter.fps/data/shapes/player/clip
*** ENDING MISSION
CDROP: 1439 IP:5.0.0.0:0
Exporting server prefs...
Exporting client prefs
Exporting client config
Exporting server prefs
Exporting client prefs
Exporting server prefs
Shutting down the OpenGL display device...
Making the GL rendering context not current...
Deleting the GL rendering context...
Releasing the device context...
03/24/2004 (8:32 pm)
I am having problems getting this to work. Everything compiles fine but when I run he game and start a mission the loading object bar fills up and just sits there and nothing happens. After waiting I click cancel and exit. The game never locks up. Any help would be appreciated.Here is the End of my console.log
Ghost Always objects received.
Mapping string: MissionStartPhase3 to index: 10
Client Replication Startup has Happened!
fxFoliageReplicator - Client Foliage Replication Startup is complete.
*** Phase 3: Mission Lighting
Successfully loaded mission lighting file: 'starter.fps/data/missions/stronghold_4b248e80.ml'
Mission lighting done
Mapping string: MissionStartPhase3Ack to index: 2
Mapping string: MissionStart to index: 11
Mapping string: SyncClock to index: 12
Could not locate texture: starter.fps/data/shapes/player/crossbow
Could not locate texture: starter.fps/data/shapes/player/clip
*** ENDING MISSION
CDROP: 1439 IP:5.0.0.0:0
Exporting server prefs...
Exporting client prefs
Exporting client config
Exporting server prefs
Exporting client prefs
Exporting server prefs
Shutting down the OpenGL display device...
Making the GL rendering context not current...
Deleting the GL rendering context...
Releasing the device context...
#30
03/25/2004 (3:50 am)
I got it working now! Great resource!
#31
I've used the tracking camera code as a base and added (for now)
Tracking (unmodified), third person (uncontrollable third person camera that follows the player) and god view (same as third person but doesnt rotate).
I'm working with Eric Miller on this, and if anyone out there have mode "modes" that they would like to add to this, then contact me please. Proper credits will naturally be added to the sources, and it will all be released as a "advanced camera" resource.
Plans for now are for a third person mode with a spring system for controlling the camera more fluidly, a copy/paste from the regular camera class to gain first person and orbit views as well as a target tracking mode, where the camera is in 3rd person around the player but looks in the direction of the target object.
03/28/2004 (5:05 am)
I'm working on a replacement for the tracking camera class. It has several "modes" that can be switched during gameplay independantly on each other.I've used the tracking camera code as a base and added (for now)
Tracking (unmodified), third person (uncontrollable third person camera that follows the player) and god view (same as third person but doesnt rotate).
I'm working with Eric Miller on this, and if anyone out there have mode "modes" that they would like to add to this, then contact me please. Proper credits will naturally be added to the sources, and it will all be released as a "advanced camera" resource.
Plans for now are for a third person mode with a spring system for controlling the camera more fluidly, a copy/paste from the regular camera class to gain first person and orbit views as well as a target tracking mode, where the camera is in 3rd person around the player but looks in the direction of the target object.
#32
Also, I would be very interested in switching camera modes as described by Thomas above. I look forward to your "advanced camera" resource.
03/31/2004 (12:20 pm)
Cory and others: I played around with dropping the new tracking camera where the player is, and it works nicely. I have not yet figured out how to switch back and forth between the tracking camera and the default third person camera. Could someone please post how to go about this.Also, I would be very interested in switching camera modes as described by Thomas above. I look forward to your "advanced camera" resource.
#33
If by the default you're referring to the behind the head view that works by default in the engine, you can just clear out the connection's camera object.
Here's a couple of script entrys you could add the to .../server/scripts/commands.cs
Then you could just map a key to toggle the two on the client side and do a commandToServer('ClearCamera'); or commandToServer('UseTrackCamera'); to flip between the two. There are several other ways to handle the toggle in the same method or use server-side flipping between map areas, etc.
BTW - for those that are interested Eric Miller, Thomas Lund and I are looking at doing a series of tutorials/resources on converting Torque code more suitable for Adventure/RPG games starting with this camera resource. I'm working on the resource to add a separate move manager and doing some more custom camera work as we speak.
Cory
03/31/2004 (12:35 pm)
If you haven't checked it out already - Thomas's resource on flipping camera positions may be useful.If by the default you're referring to the behind the head view that works by default in the engine, you can just clear out the connection's camera object.
Here's a couple of script entrys you could add the to .../server/scripts/commands.cs
function serverCmdClearCamera(%client) {
%client.clearCameraObject();
}
function serverCmdUseTrackCamera(%client) {
%client.setCameraObject(%client.trackCamera);
}Then you could just map a key to toggle the two on the client side and do a commandToServer('ClearCamera'); or commandToServer('UseTrackCamera'); to flip between the two. There are several other ways to handle the toggle in the same method or use server-side flipping between map areas, etc.
BTW - for those that are interested Eric Miller, Thomas Lund and I are looking at doing a series of tutorials/resources on converting Torque code more suitable for Adventure/RPG games starting with this camera resource. I'm working on the resource to add a separate move manager and doing some more custom camera work as we speak.
Cory
#34
Soon! :-)
03/31/2004 (12:46 pm)
I plan to integrate some of the stuff Cory coded this weekend (when I get a little time) into the camera code, and then release a first version of it. It already works fine (the basics), but RL work has kept me from adding the last touches + porting Corys work :-(Soon! :-)
#35
03/31/2004 (1:23 pm)
Thanks Thomas. I'll continue familiarizing myself with the code until then.
#36
03/31/2004 (1:24 pm)
Thank you Cory! I'll give it a try.
#37
04/04/2004 (9:18 am)
I've added some notes on additional resources to the end of the tutorial - player movements, more cameras and flipping the camera position for mission regions.
#38
EDIT: that was the TSE head from June 23 2004
06/25/2004 (4:55 am)
Just thought I'd check in to say that I got this working with the HEAD from the TSE CVS source with no problems at all. This would be a great resource for anyone wanting to do demos or cut scenes especially.EDIT: that was the TSE head from June 23 2004
#39
Great resource:)
Nick
06/25/2004 (5:05 am)
I wish there was a way to record the cut-scenes though. The built-in recorder has some problems.Great resource:)
Nick
#40
06/28/2004 (4:24 am)
Hey guys I have tried to implement 21-6's dynamic camera and I believe I have done all of the engine code correctly because it compiles for me. So then in starter.fps I changed the neccessary files but none of them take place when I load up the game. It still defaults to first person, and there isn't the added camera control. Is there something that I am missing as far as why the files aren't being loaded? Any help on this would be greatly appreciated. 
Torque 3D Owner Emmanuel Nwankwo