Game Development Community

Adding adjustable Yaw to the RTS camera

by Martin "Founder" Hoover · in RTS Starter Kit · 11/15/2004 (6:34 pm) · 35 replies

Well, I'm not claiming this is right, only that it works. I have it setup so Alt + mouseWheel will rotate the camera (spin the Z axis)


First in RTSCamera.h, inside class RTSCamera: public ShapeBase:

find :

F32 mPrevAngle;

after it add:

F32 mTargetZAngle;
   F32 mCurrZAngle;
   F32 mPrevZAngle;

then in the public section find:

void setPitchAngle(F32 angle, bool updateNet, bool toInterpolate = true);

and after it paste:

F32 getYawAngle() { return mRadToDeg(mTargetZAngle); }
   void setYawAngle(F32 angle, bool updateNet, bool toInterpolate = true);
   void increaseYawAngle() { setYawAngle(getYawAngle() + mDataBlock->mAngleStep, true); }
   void decreaseYawAngle() { setYawAngle(getYawAngle() - mDataBlock->mAngleStep, true); }

Now I'm not really sure what the RTScameraUpdate setup is all about, but I figured I'd better add it there too, so in class RTSCameraUpdate : public NetEvent find:

F32 mPitchAngle;

and after it add:

bool mIsYawAngle;
   F32  mYawAngle;
   void setYawAngle(F32 angle);

Now on to RTSCamera.cc

Lets start in RTSCamera::RTSCamera() , add the following line in somewhere:

mCurrZAngle = mTargetZAngle = mPrevZAngle = mDegToRad(0.0f);

since theres no need to start out with the Yaw at any other angle except 0, we'll go down to void RTSCamera::setPitchAngle(F32 angle, bool updateNet, bool toInterpolate) :

we need to change:
zRot.set(EulerF(0, 0, 0));

to:
zRot.set(EulerF(0, 0, mCurrZAngle));

now, below that function we can add this new function:
void RTSCamera::setYawAngle(F32 angle, bool updateNet, bool toInterpolate)
{
   RTSCameraData* data = (RTSCameraData*)mDataBlock;
   mTargetZAngle = mDegToRad(angle);
[i]edit...[/i]
   if(mTargetZAngle > M_2PI_F)
   {
      //lets just fake it into thinking it was a smaller angle to begin with :)
      mTargetZAngle -= M_2PI_F;
      mCurrZAngle -= M_2PI_F;
      mPrevZAngle -= M_2PI_F;
   }
   if(mTargetZAngle < -M_2PI_F)
   {
      mTargetZAngle += M_2PI_F;
      mCurrZAngle += M_2PI_F;
      mPrevZAngle += M_2PI_F;
   }
[i]edit...[/i]
   if (!toInterpolate)
   {
      mCurrZAngle = mTargetZAngle;
      MatrixF xRot, zRot;
      xRot.set(EulerF(mCurrAngle, 0, 0));
      zRot.set(EulerF(0, 0, mCurrZAngle));
      MatrixF temp;
      temp.mul(zRot, xRot);
      setTransform(temp);
   }

   if(updateNet)
   {
      // Send a net update
      GameConnection *conn = getControllingClient();
      if(!conn)
      {
         Con::warnf("This camera's not in control of anything!");
         return;
      }

      RTSCameraUpdate *rtsCamUpdate = new RTSCameraUpdate(this, !isClientObject());
[i]Edit...[/i]
       rtsCamUpdate->setYawAngle(angle);
[i]Edit...[/i]
      conn->postNetEvent(rtsCamUpdate);
   }
}

now into void RTSCamera::processTick(const Move* move)


Edit...
A couple lines down from the Parent:: call add this (near the other assignments)

mPrevZAngle = mCurrZAngle;

Edit...

after the code block(not inside of it) that starts with:

if (mTargetAngle != mCurrAngle)

we need to add another:

if (mTargetZAngle != mCurrZAngle)
   {
[i]edit...[/i]
      if (mCurrZAngle < mTargetZAngle)
[i]edit...[/i]
      {
         F32 step = gDegsPerTick;
         if (mCurrZAngle + step > mTargetZAngle)
            mCurrZAngle = mTargetZAngle;
         else
            mCurrZAngle += step;
      }
      else
      {
         F32 step = -gDegsPerTick;
         if (mCurrZAngle + step < mTargetZAngle)
            mCurrZAngle = mTargetZAngle;
         else
            mCurrZAngle += step;

      }
   }

then again change out
zRot.set(EulerF(0, 0, 0));

with
zRot.set(EulerF(0, 0, mPrevZAngle));
Page «Previous 1 2
#1
11/15/2004 (6:34 pm)
Continued....
edit from Benoit Touchette, code to make the camera move in the right direction when rotated
At the end of void RTSCamera::processTick(const Move* move)
Change:
if (move)
   {
      mTargetPos.x += move->x * TickSec * mDataBlock->mMovementSpeed;
      mTargetPos.y += move->y * TickSec * mDataBlock->mMovementSpeed;

TO:
if (move)
   {
      F32 tempX, tempY;

      tempX = move->x * TickSec * mDataBlock->mMovementSpeed;
      tempY = move->y * TickSec * mDataBlock->mMovementSpeed;

      mTargetPos.x += ((tempX * mCos(-1 * mCurrZAngle)) - (tempY * mSin(-1 * mCurrZAngle)));
      mTargetPos.y += ((tempY * mCos(-1 * mCurrZAngle)) + (tempX * mSin(-1 * mCurrZAngle)));

edit....
next we go to: void RTSCamera::interpolateTick(F32 dt)

there are two instances of:
zRot.set(EulerF(0, 0, 0));

which need to be changed to:
zRot.set(EulerF(0, 0, mPrevZAngle));

then it should be pretty obvious where to add this next block inside that function:

if (mPrevZAngle != mCurrZAngle)
   {
      F32 tickStep = gDegsPerTick * (mPrevZAngle < mCurrZAngle ? 1 : -1);
      F32 interpRot = mPrevZAngle + tickStep * (1-dt);
      if (tickStep < 0)
      {
         if (interpRot < mCurrZAngle)
            mPrevZAngle = interpRot = mCurrZAngle;
      }
      else
      {
         if (interpRot > mCurrZAngle)
            mPrevZAngle = interpRot = mCurrZAngle;
      }

      MatrixF xRot, zRot;
      xRot.set(EulerF(mPrevAngle, 0, 0));
      zRot.set(EulerF(0, 0, interpRot));
      MatrixF temp;
      temp.mul(zRot, xRot);
      setTransform(temp);

      toUpdate = true;
   }

then move on down to: RTSCameraUpdate::RTSCameraUpdate(RTSCamera *camera, bool onServer)

and add this line:
mIsYawAngle = false;

then somewhere near here add this new function in:

void RTSCameraUpdate::setYawAngle(F32 angle)
{
   mIsYawAngle = true;
   mYawAngle = angle;
}

then in: void RTSCameraUpdate::pack(NetConnection *conn, BitStream * stream)

right after:
if(stream->writeFlag(mIsPitchAngle))
stream->write(mPitchAngle);

add:
if(stream->writeFlag(mIsYawAngle))
         stream->write(mYawAngle);

then in: void RTSCameraUpdate::unpack(NetConnection *conn, BitStream *stream)

we add the corresponding lines (in the same order as pack):

if(mIsYawAngle = stream->readFlag())
         stream->read(&mYawAngle);


next in: void RTSCameraUpdate::process(NetConnection * conn)

down inside the if: if(mCam)
add:

if(mIsYawAngle)
            mCam->setYawAngle(mYawAngle, false);

next, add these console methods to the bottom of the file:

ConsoleMethod(RTSCamera, increaseYawAngle, void, 2, 2, "RTSCamera.increaseYawAngle()")
{
   object->increaseYawAngle();
}

ConsoleMethod(RTSCamera, decreaseYawAngle, void, 2, 2, "RTSCamera.decreaseYawAngle()")
{
   object->decreaseYawAngle();
}

that's all (I hope) for the source, onto the scripts!

in starter.RTS/client/scripts/default.bind.cs
add the following:
function updateViewYaw(%val)
{
	echo("yaw" SPC %val);
   if( %val > 0 )
      $RTSCamera.decreaseYawAngle();
   else if( %val < 0 )
      $RTSCamera.increaseYawAngle();
}
moveMap.bind( mouse, "alt zaxis", updateViewYaw);

Now personally I preferered to put my funtion next to updateViewPitch, but the choice is yours. :)

Also go ahead and paste the keyBind into client/config.cs and client/scripts/client/config.cs (why is there 2?)

Now this may not be the right way to do it, but it's been working for me :)

The only cavet is that it doesn't interpolate smoothly when going in the negative yaw direction. Why? well that's because I have yet to make friends with doing such things (seems too much for my head tonight)
#2
11/15/2004 (6:47 pm)
Do you have a diff patch available? :)
#3
11/15/2004 (7:02 pm)
From my work with the advCamera object, it definitely looks like you got everything that I at least knew about--great work!

I think only one thing grabbed my attention--you never clock math the yaw angle, so theoretically the player could yaw in one direction for a very long time, and eventually overflow a variable somewhere--not sure where the crash would happen, or if it would just do crazy things when you overflow.

Basically what I'm getting at is as you process the input and set the new angle, clamp it with something like:

if (mYawAngle > M_2PI_F)
mYawAngle -= M_2PI_F;
else if (mYawAngle < 0)
mYawAngle += M_2PI_F;

(above code is NOT debugged, use it at risk!)

Very nice mod!
#4
11/15/2004 (7:28 pm)
@Benoit, hmm I could make a patch, but damn am I lazy :)

@Stephen , Right you are. Indeed, in theory someone could keep turning to the left on and on till they finally over flowed that poor F32. And I did have very similar checks in setYawAngle to cap the mTargetZAngle. But either that is the wronge place or my lack of understanding about interpolation makes it wonky. Since when you are decreasing yaw, and turn the full circle, it interpolates you all the way back around the circle. Now I'm sure the solution is obvious, but tonight I just can't wrap my mind around it. So I went with the infinate angle prograssion till either I can figure it out, or someone else can come alonge and give it a poke.
#5
11/15/2004 (7:48 pm)
@Martin: clock math is damned difficult to wrap your head around in any case!
I did double check that wrap above, and it should work for you (assuming you apply it when your mYawAngle is in radians). Now, when to apply it, especially in the interpolation (something I completely blew off in the advCam rotation stuff) is a rough call.
#6
11/16/2004 (3:26 am)
Ask and ye shall recieve.

Patch file:
http://tweetie.gxsnmp.org/rts-camera.diff

Apply with something as such:

patch -p1

In my case I call it rts-engine so I would do:

patch -p1
Enjoy.
#7
11/16/2004 (3:34 am)
Greg: you seem to be misisng the .cs files in your patch.

Martin: Added it locally by hand :) but all i get is jittering left or right when i move it in either direction. Will try to see why later tonight ... i'm off to my day job ...
#8
11/16/2004 (3:47 am)
Nice, we'll take a look at adding this.
#9
11/16/2004 (4:02 am)
Updated the patch to contain the script changes. As far as the config.cs, thats the user defined mappings and can be clobbered, the engine will re-write it. I didn't mess with the clock math on the yaw angle as I get confused real quick on that stuff. Anyway the patch is as the resource is posted :)
#10
11/16/2004 (4:35 am)
Greg: Actually it didn't re-write for me, spent an hour trying to figure out why the key binding wasn't working. Actually was the first thing i did this morning right after waking up! LOL! Now for some much needed coffee!
#11
11/16/2004 (1:41 pm)
This is really cool. Helps me alot.

I think a line is missing at the function RTSCamera::processTick(const Move* move)

mPrevZAngle = mCurrZAngle;

Thats why there is Camera jittering. :)
#12
11/16/2004 (2:04 pm)
DOH! You are definately right there Kevin. I was afraid I'd miss something like that. I shall edit the post. Thanks for finding it!
#13
11/16/2004 (2:04 pm)
Yup fixed it here thanks kevin, saved some debugging time :)
#14
11/16/2004 (4:38 pm)
Good stuff. Got this implemented, thanks a bunch. Now, however, my camera moves along a fixed axis when I poke at the edges of my screen/use the arrow keys like I was still in the default camera position. eg. If I rotate the camera 180deg and push 'forward' I move backwards. ;/

Is this just me? Did I typo something? Or are the direction commands totally oriented towards the worldspace co-ords and not the camera object?
#15
11/16/2004 (4:55 pm)
It's the latter case I'm afraid. Might not be too difficult to change it, but I haven't had a chance to look at that yet.
#16
11/16/2004 (6:58 pm)
I have somewhat of a fix for the camera issue. In RTSCamera.cc at the end of void RTSCamera::processTick(const Move* move) i changed

if (move)
   {
      mTargetPos.x += move->x * TickSec * mDataBlock->mMovementSpeed;
      mTargetPos.y += move->y * TickSec * mDataBlock->mMovementSpeed;

to

if (move)
   {
      F32 tempX, tempY;

      tempX = move->x * TickSec * mDataBlock->mMovementSpeed;
      tempY = move->y * TickSec * mDataBlock->mMovementSpeed;

      mTargetPos.x += ((tempX * mCos(-1 * mCurrZAngle)) - (tempY * mSin(-1 * mCurrZAngle)));
      mTargetPos.y += ((tempY * mCos(-1 * mCurrZAngle)) + (tempX * mSin(-1 * mCurrZAngle)));

now as the camera view rotates, its movement should match.
#17
11/16/2004 (7:37 pm)
On my todo list. This seems like a great implementation.
#18
11/17/2004 (4:29 am)
You guys rule. Thanks again. Wow, this little spin off community is really going. I'd have slogged through this myself (and still have to, I've got some very particular wants for a pair of camera controls) but I've been a tad too busy the last few days. Hopefully I'll find some time to contribute back in the next little bit.

Fyi, the pair of cameras I'll be working on are:

Free camera: Poke mouse at edges to pan, to the screen corners to rotate and the wheel to change height (you guys have already managed 90% of this)

Tethered 3rd person camera: Locked to a unit, poking forward or back w/mouse on screen edges changes pitch/zoom within a set range (tighter than the current implementation) and poking to the sides orbits the camera around the unit while still facing it.

With luck I'll have this done in the next week and be able to give something back.

Indy communities rock ;) Pat yourselves on the back all of you.
#19
11/17/2004 (5:16 am)
@James: Have you looked at the advancedCamera resource for stock TGE? Your Tethered 3rd person camera description is just about exactly the requirements I had when I wrote the OrbitCam mode of that resource.

On my todo list is to re-write that to be more smoothly integrated with the RTSCam's input model, as well as net updating (it currently uses a lot of Servercommands, which isn't efficient).

If you want something to start from however, the resource is here, and I've got all the spherical to cartesian coordination transformation math done--it just needs the input method modifications, and conversion to the RTSCamera way of updating and receiving input.

Advanced Camera Resource
#20
11/17/2004 (8:30 am)
Anyone coded up an update to the minimap so it actually rotates with the camera view?
Page «Previous 1 2