Game Development Community

GameCamera: a third person, aiming camera (that works!)

by Michael Bacon · in Torque Game Engine · 09/13/2007 (6:44 am) · 81 replies

Update: I have fixed the jitter and the issues I had with the network code. I think everything is working as it should be.


Well, I've been working on a new third person camera. I wanted the player to aim where my third person camera is looking.

This has been sought after by quite a few people including myself. I didn't find what I was looking for elsewhere so here is my attempt.

I've got the basics working. This camera was built on a HUGELY modified Advanced Camera but has pretty much grown into its own being. It is now called the GameCamera.

Features:
Third person camera
Player fires where camera is looking
The server controls where the camera should be


Caveats:
No server side interpolation is done (for instance if you switch targets the client side will interpolate the difference in like one tick but the server will have already been there. I hope to add this later.


Download here and add gameCamera.cc and gameCamera.h to your project in under the engine\game folder.

Video here shows the camera in action while fighting a bot.

------------------------------------------------

If you follow all the directions here (especially the one about setting the 'correctMuzzleVector = true' value on your weapon datablock) you will have a third person camera and a player that fires at the center of your screen.

------------------------------------------------

In order to implement the camera you need to make a few changes...
Page «Previous 1 2 3 4 5 Last »
#1
09/13/2007 (6:44 am)
First, we alter our Game Process to give the connection's Camera Object a chance to act on all input.

In GameProcess.cc ProcessList::advanceClientTime() make these changes:
...
         [b]ShapeBase* camera = connection->getCameraObject();[/b]
         while (m < numMoves)
         {
            [b]control->processTick(&movePtr[m]);

            // give camera a chance to look at movement
            if (camera != control)
                if (camera && camera->mProcessTick)
                   camera->processTick(&movePtr[m]);

            m++;[/b]
         }
         connection->clearMoves(m);

In GameProcess.cc replace ProcessList::advanceObjects() with:
void ProcessList::advanceObjects()
{
   PROFILE_START(AdvanceObjects);

   // A little link list shuffling is done here to avoid problems
   // with objects being deleted from within the process method.
   GameBase list;
   GameBase* obj;
   
   list.plLinkBefore(head.mProcessLink.next);
   head.plUnlink();
   while ((obj = list.mProcessLink.next) != &list) {
      obj->plUnlink();
      obj->plLinkBefore(&head);

      // Each object is either advanced a single tick, or if it's
      // being controlled by a client, ticked once for each pending move.
      if (obj->mTypeMask & GameBaseObjectType) {

         GameBase *pGB = static_cast<GameBase *>(obj);
         GameConnection* con = pGB->getControllingClient();

         if (con && con->getControlObject() == pGB) {
            Move* movePtr;
            U32 m, numMoves;

            con->getMoveList(&movePtr, &numMoves);

            [b]ShapeBase* camera = con->getCameraObject();[/b]

            for (m = 0; m < numMoves && pGB->getControllingClient() == con; )
            {
               [b]obj->processTick(&movePtr[m]);

               // give camera a chance to look at movement
               if (camera != obj)
                   if (camera && camera->mProcessTick)
                      camera->processTick(&movePtr[m]);

               m++;[/b]
            }

            con->clearMoves(m);

            continue;
         }
      }
      [b]if (obj->mTypeMask & CameraObjectType ) {
         GameConnection* con = obj->getControllingClient();
         // if the current object is the active camera for it's client then it
         // will receive it's processTick along with the control object so we
         // can skip it here.
         if (con && con->getCameraObject() == obj)
            continue;
      }[/b]
      if (obj->mProcessTick)
         obj->processTick(0);
   }
   PROFILE_END();
}
We can thank Entropy for the idea behind most of that one.
#2
09/15/2007 (5:12 am)
Then we alter a method of ShapeBase, getCorrectedAim, which normally when asked to will adjust the muzzle vector to point where the shape base's own eye is looking. We modify it so that it will use the current connection's camera object's render position instead when correcting for the current connection's control object (that was a mouthful).

In shapeImage.cc ShapeBase::getCorrectedAim() should look like this:
// Modify muzzle if needed to aim at whatever is straight in front of eye.  Let the
// caller know if we actually modified the result.
bool ShapeBase::getCorrectedAim(const MatrixF& muzzleMat, VectorF* result)
{
   const F32 pullInD = 6.0;
   const F32 maxAdjD = 500;

   VectorF  aheadVec(0, maxAdjD, 0);

   MatrixF  eyeMat;
   Point3F  eyePos;

   [b]// Correct the aim based on where the camera is looking.
   // This only happens for the control object iff they are the camera
   // objects active target AND the camera is a GameCamera.
   bool set = false;
   if (GameConnection * gc = getControllingClient())
      if (!gc->isFirstPerson())
         if (gc->getControlObject() == this )   // only for the control object
            if (ShapeBase* camera = gc->getCameraObject())
               if (GameCamera* gameCamera = dynamic_cast<GameCamera *>(camera))
                  if (gameCamera->getTargetObject() == this)   // only for a game camera's target object
                  {
                     eyeMat = camera->getRenderTransform();
                     set = true;
                  }

   if (!set) {
      getEyeTransform(&eyeMat);
   }[/b]

   eyeMat.getColumn(3, &eyePos);
   eyeMat.mulV(aheadVec);
   Point3F  aheadPoint = (eyePos + aheadVec);

   ...

Don't forget to include the GameCamera header in ShapeImage.cc
#include "math/mathUtils.h"
#include "sim/netObject.h"
[b]#include "game/gameCamera.h"[/b]

Then again in ShapeImage.cc[b] on [b]lines 942 and 965 - yes two IDENTICAL lines that need to both be modified from:
if (gc->isFirstPerson() && !gc->isAIControlled())
to..
if ([b]/*gc->isFirstPerson() &&*/[/b] !gc->isAIControlled())
#3
09/18/2007 (4:47 am)
Now for the scripts your scripts..


In server\scripts\camera.cs create a datablock that looks like this:
datablock GameCameraData(MyGameCameraData)
{
   lookAtOffset = "0 5 3";
   thirdPersonOffset = "0 -10 5";
   minLookAngle = -75;
   maxLookAngle = 89;
};

and make sure that in your weapon datablock you:
correctMuzzleVector = [b]true[/b];
If you are using the starter.fps as a base then you can find correctMuzzleVector at about line 838 in \example\starter.fps\server\scripts\crossbow.cs

In server\scripts\game.cs: GameConnection::onClientEnterGame below the camera creation add:
// create advanced camera
   %this.advCamera = new GameCamera() {
      dataBlock = MyGameCameraData;
   };

   MissionCleanup.add(%this.advCamera);
   %this.advCamera.scopeToClient(%this);

Furthur down in GameConnection::createPlayer add
%this.setControlObject(%this.player);
   %this.setCameraObject(%this.advCamera);
   %this.advCamera.setTargetObject(%player);
   %this.setFirstPerson(false);
#4
09/22/2007 (6:56 am)
I think the camera works. Any comments?
#5
09/22/2007 (7:54 am)
Maybe you could post some screenshots and videos on this in action.
#6
09/22/2007 (7:34 pm)
I fixed some minor issues with the camera collision check (shoulda subtracted a vector where I added).

Heres a VERY small (200kb) video that simply shows the player shooting at where the reticle is. I'm using a free Geocities host right now and I don't have much bandwidth.

The crosshair will stay fixed in the middle of the screen and the bullet will always hit the target (assuming the target isn't moving!).

I really wanted to post some video of a fight but the best I could get was about 1.5mb and that would kill the Geocities account...
#7
09/23/2007 (3:10 am)
Hi Brian,

this looks great.

I followed your instructions, but when i compile i get these error messages:

7>..\engine\game\gameCamera.cc(207) : error C3892: 'Player::getHeadRotation' : you cannot assign to a variable that is const
7>..\engine\game\gameCamera.cc(239) : error C3892: 'Player::getHeadRotation' : you cannot assign to a variable that is const

What did i do wrong? I'm using AFX 1.2

Thanks.
Michael
#8
09/23/2007 (6:21 am)
I'm getting a lot more errors than Michael is. I can't seem to find "correctMuzzleVector = true;" anywhere in player.cc and I'm not sure where to include gameCamera.cc and gameCamera.h in the project. It might that it's too early for me to even be thinking about code, but Brian I would suggest a more detailed description, if possible.

Thanks in advance,
Chris
#9
09/23/2007 (6:26 am)
"correctMuzzleVector = true;" is in script. You will find it in your weapon's image datablock.
#10
09/23/2007 (6:30 am)
Thanks very much Tim, I've written it down. Like I said, it's obviously too early for me and I don't have enough coffee in me yet.

Edit: For anyone who's looking for this as well, it's at about line 838 in \example\starter.fps\server\scripts\crossbow.cs
#11
09/24/2007 (5:31 am)
@Michael: See my second instructional post (at the end), I have updated it to include the change you need. I forgot I had to do this. Basically just remove the const keyword from the definition of getHeadRotation().

@Chris: The gameCamera.cc/.h files are currently setup to go in the engine\game folder. I have mine in a subfolder for our project but if you do that you will have to change the include line in gameCamera.cc (line 17).
#12
09/24/2007 (5:35 am)
Thanks Brian. I realized last night after all was said in done what I did wrong, I forgot to do the #include's. Like I said I guess I now know better to even begin coding before I've had enough caffine, none the less post on forums.

I haven't had a chance to put this in again, but I will later today and let you know if everything worked out.
#13
09/26/2007 (4:50 am)
Bug and Fix

I realized that the changes in Player.cc affect AIPlayers from being able to look/aim up. So I devised a better solution to the head pitch problem.

I altered it so that when the target object is a player object is uses the player datablock's minLookAngle and maxLookAngle so that the player could just pitch its own head using the move data. If the object is not a player it will use the angles provided by the camera datablock.

With these modifications you no longer need to modify player.h or player.cc (which I like much better).

In GameCamera::setTargetObject() add the snippet below:
...
   if(bool(mTargetObject)) {
      mTargetAsPlayer = dynamic_cast<Player*>(obj);
		processAfter(mTargetObject);
		deleteNotify(mTargetObject);
	}
   [b]if (bool(mTargetAsPlayer)) {
      PlayerData* pDB = dynamic_cast<PlayerData*>(mTargetAsPlayer->getDataBlock());
      if (bool(pDB)) {
         mMinLookAngle = pDB->minLookAngle;
         mMaxLookAngle = pDB->maxLookAngle;
      }
   } else {
      mMinLookAngle = mDataBlock->minLookAngle;
      mMaxLookAngle = mDataBlock->maxLookAngle;
   }[/b]
}

and remove the following lines from both GameCamera::processTick() and GameCamera::interpolateTick():
...
   // set the player's head pitch
   //if (mTargetAsPlayer)
   //{
   //   mTargetAsPlayer->getHeadRotation().x = mPitch;
   //}
   ...

You could refactor the code to exclude the use of mTargetAsPlayer entirely now as it is only referenced from within the setTargetObject function.

edit: Okay, I went ahead and uploaded the newest version. This version is completely refactored to remove mTargetAsPlayer.
#14
09/26/2007 (5:07 am)
@Brian West: I can't download the newest version of gameCamera.cc and gameCamera.h. I get a message from Yahoo that the page does not exist.

Just thought you should know.
#15
09/26/2007 (5:27 am)
It just disappeared... I SAW it there after I uploaded it but it was gone when I just checked. It has been fixed.

Oh and Chris did you ever get this working?
#16
09/26/2007 (6:53 am)
Brian, no I didn't have a chance yet. I'm disabled and my health hasn't been the greatest lately, but this is a priority for me to get done. I'm working on a Halloween mini game for our forum community and it's one of the many features I need to add.

I've redownloaded the files and I'll try to get this done late today. I will post an update once it's done.
#17
09/27/2007 (5:31 am)
Hello Brian,

Thanks for sharing this great stuff. I've watch the video on this in action and found that's what I need.

But, I got a problem.

The camera can't move in vertical axis when I moved a mouse up or down. but the character aim direction depends on the mouse.
move the mouse down
move the mouse up

And from your instructions
Quote:
In GameProcess.cc ProcessList::advanceClientTime() make these changes:

...
         ShapeBase* camera = connection->getCameraObject();
         while (m < numMoves)
         {
            control->processTick(&movePtr[m++]);

            // give camera a chance to look at movement
            if (camera && camera->mProcessTick)
               camera->processTick(&movePtr[m]);
         }
         connection->clearMoves(m);

In my GameProcess.cc ProcessList::advanceClientTime() doesn't have this line
ShapeBase* camera = connection->getCameraObject();
when I compiled I got an error that compiler doesn't know 'camera'. So, I've added this line manaully then compile fine.

Did I do something wrong?
Thanks
#18
09/27/2007 (7:07 am)
I got this working, but the problems C. Chotigavanich pointed out are there.

I think the
ShapeBase* camera = connection->getCameraObject();
was something Brian forgot to Bold as a change.

I also do not have the thrid person targeting rectile like in your video, Brian. C. Chotigavanich screen shots do not show the recticle either. Is this something you added in script and could you please post that as well?

Edit* I just wanted to add, Brian, when this is all worked out you should add this as a resource. I will also test this later for you in TGEA as well.
#19
09/27/2007 (4:27 pm)
Yup thats one that I forgot to Embolden.

To get the reticle to show up open game\fps\guiCrossHairHud.cc and make the following change:
void GuiCrossHairHud::onRender(Point2I offset, const RectI &updateRect)
{
   // Must have a connection and player control object
   GameConnection* conn = GameConnection::getConnectionToServer();
   if (!conn)
      return;
   ShapeBase* control = conn->getControlObject();
   if (!control || !(control->getType() & ObjectMask) [b]/*|| !conn->isFirstPerson()*/[/b])
      return;


I'm not sure why the camera wouldn't be moving vertically. Make sure that you have your mouse Y axis bound the 'pitch' method.

For exmple in default.bind.cs there should be a line that looks something like this:
moveMap.bind( mouse, yaxis, pitch );
#20
09/27/2007 (6:09 pm)
Brian, I tried doing what you suggested for the crosshair, but it still does not show up. I've seen a few older threads on this, but I'm wondering if you changed or added something from these and don't remember?

This does seem to be a very old problem, I wonder why the heck GG has never done anything about this officially, because it seems like a very popular modification?

Edit* I've been poking around through the code and I'm still not seeing any fix for this. We should all make sure to thank Brian for all the work he's done.
Page «Previous 1 2 3 4 5 Last »