Game Development Community

dev|Pro Game Development Curriculum

Do the Twist

by Derk Adams · 11/15/2004 (2:35 pm) · 23 comments

Download Code File

Discussion:
To enable torso twisting, I have extended the controls of the first-person view to include the capabilities of the third-person view. This is a combination process and it is absolute, i.e. both the first-person view and the third-person view function identically and all characters have this ability.

The amount of twisting is determined by the look angle player datablock values. These values are in radians, so 180 degrees equals 3.1415. MinLookAngle and MaxLookAngle control the amount of vertical bending and maxFreelookAngle controls the amount of horizontal twisting.

Comments:
If you want a character to not have torso twist, simply set their maxFreelookAngle to 0.
For animating your character, have the "headside" animation be the torso twist extremes, and the "look" animation be the up-down extremes. Note: the "look" animation expects an 80 degree angle up and an 80 degree angle down for the extremes, even if you don't use the full range.

Key:
I use the same indication of modifications as a patch file. A line beginning with a "-" is to be removed and a line beginning with a "+" is to be added. Be sure to remove the "+" when you actually put the line into your code. A few lines around the changes are shown to give context to the changes. The triple dot "..." is showing that information has been removed for brevity purposes.

Development Environment:
November 2004
Head 1.2.2
Win32
Single Player

Implementation:
EngineFile: moveManager.h
struct Move
{
...
   S32 px, py, pz;
-   U32 pyaw, ppitch, proll;
+   U32 pyaw, ppitch, proll, ptwist;
   F32 x, y, z;          // float -1 to 1
-   F32 yaw, pitch, roll; // 0-2PI
+   F32 yaw, pitch, roll, twist; // 0-2PI
...
}

class MoveManager
{
...
   static F32 mRoll;
+   static F32 mTwist;
...
   static F32 mRollRightSpeed;
+   static F32 mTwistLeftSpeed;
+   static F32 mTwistRightSpeed;
...
};
EngineFile:gameConnectionMoves.cc
...
F32 MoveManager::mRoll = 0;
+F32 MoveManager::mTwist = 0;
...
F32 MoveManager::mRollRightSpeed = 0;
+F32 MoveManager::mTwistLeftSpeed = 0;
+F32 MoveManager::mTwistRightSpeed = 0;
...
const Move NullMove =
{
   16,16,16,
-   0,0,0,
+   0,0,0,0,
   0,0,0,   // x,y,z
-   0,0,0,   // Yaw, pitch, roll
+   0,0,0,0,   // Yaw, pitch, roll, twist
   0,0,

   false,
   false,false,false,false,false,false
};
...
void MoveManager::init()
{
...
   Con::addVariable("mvRoll", TypeF32, &mRoll);
+   Con::addVariable("mvTwist", TypeF32, &mTwist);
   Con::addVariable("mvPitchUpSpeed", TypeF32, &mPitchUpSpeed);
...
   Con::addVariable("mvRollRightSpeed", TypeF32, &mRollRightSpeed);
+   Con::addVariable("mvTwistLeftSpeed", TypeF32, &mTwistLeftSpeed);
+   Con::addVariable("mvTwistRightSpeed", TypeF32, &mTwistRightSpeed);
...
}

void Move::unclamp()
{
   yaw = IANG2FANG(pyaw);
   pitch = IANG2FANG(ppitch);
   roll = IANG2FANG(proll);
+   twist = IANG2FANG(ptwist);

   x = (px - 16) / F32(16);
   y = (py - 16) / F32(16);
   z = (pz - 16) / F32(16);
}

void Move::clamp()
{
   // angles are all 16 bit.
   pyaw = FANG2IANG(yaw);
   ppitch = FANG2IANG(pitch);
   proll = FANG2IANG(roll);
+   ptwist = FANG2IANG(twist);

   px = clampRangeClamp(x);
   py = clampRangeClamp(y);
   pz = clampRangeClamp(z);
   unclamp();
}

void Move::pack(BitStream *stream)
{
   if(stream->writeFlag(pyaw != 0))
      stream->writeInt(pyaw, 16);
   if(stream->writeFlag(ppitch != 0))
      stream->writeInt(ppitch, 16);
   if(stream->writeFlag(proll != 0))
      stream->writeInt(proll, 16);
+   if(stream->writeFlag(ptwist != 0))
+      stream->writeInt(ptwist, 16);

   stream->writeInt(px, 6);
   stream->writeInt(py, 6);
   stream->writeInt(pz, 6);
   stream->writeFlag(freeLook);

   for(U32 i = 0; i < MaxTriggerKeys; i++)
      stream->writeFlag(trigger[i]);
}

void Move::unpack(BitStream *stream)
{
   if(stream->readFlag())
      pyaw = stream->readInt(16);
   else
      pyaw = 0;
   if(stream->readFlag())
      ppitch = stream->readInt(16);
   else
      ppitch = 0;
   if(stream->readFlag())
      proll = stream->readInt(16);
   else
      proll = 0;
+   if(stream->readFlag())
+      ptwist = stream->readInt(16);
+   else
+      ptwist = 0;
   px = stream->readInt(6);
   py = stream->readInt(6);
   pz = stream->readInt(6);

   unclamp();
   freeLook = stream->readFlag();

   for(U32 i = 0; i < MaxTriggerKeys; i++)
      trigger[i] = stream->readFlag();
}

bool GameConnection::getNextMove(Move &curMove)
{
...
   F32 pitchAdd = MoveManager::mPitchUpSpeed - MoveManager::mPitchDownSpeed;
   F32 yawAdd = MoveManager::mYawLeftSpeed - MoveManager::mYawRightSpeed;
   F32 rollAdd = MoveManager::mRollRightSpeed - MoveManager::mRollLeftSpeed;
+   F32 twistAdd = MoveManager::mTwistLeftSpeed - MoveManager::mTwistRightSpeed;

   curMove.pitch = MoveManager::mPitch + pitchAdd;
   curMove.yaw = MoveManager::mYaw + yawAdd;
   curMove.roll = MoveManager::mRoll + rollAdd;
+   curMove.twist = MoveManager::mTwist + twistAdd;
   
   MoveManager::mPitch = 0;
   MoveManager::mYaw = 0;
   MoveManager::mRoll = 0;
+   MoveManager::mTwist = 0;
...
}
EngineFile: player.cc
void Player::updateMove(const Move* move)
{
...
      F32 p = move->pitch;
      if (p > M_PI) p -= M_2PI;
      mHead.x = mClampF(mHead.x + p,mDataBlock->minLookAngle,
                        mDataBlock->maxLookAngle);

+  F32 t = move->twist;
+  if (t > M_PI) t -= M_2PI;
+  mHead.z = mClampF(mHead.z + t,-mDataBlock->maxFreelookAngle,mDataBlock->maxFreelookAngle);
...
}

void Player::getMuzzleTransform(U32 imageSlot,MatrixF* mat)
{
...
   if (mActionAnimation.action < PlayerData::NumTableActionAnims) {
-      MatrixF xmat;
+      MatrixF pmat,xmat,zmat;
      xmat.set(EulerF(mHead.x, 0, 0));
+      zmat.set(EulerF(0, 0, mHead.z));
+      pmat.mul(zmat,xmat);
+      mat->mul(getTransform(), pmat);
-      mat->mul(getTransform(), xmat);
      F32 *sp = nmat,*dp = *mat;
      dp[3] = sp[3]; dp[7] = sp[7]; dp[11] = sp[11];
   }
...
}
ScriptFile: /client/scripts/default.bind.cs
function twist(%val)
{
   $mvTwist += getMouseAdjustAmount(%val);
}
moveMap.bind( mouse, xaxis, twist );

function twistLeft( %val )
{
   $mvTwistRightSpeed = %val ? $Pref::Input::KeyboardTurnSpeed : 0;
}
moveMap.bind(keyboard, "comma", twistLeft);

function twistRight( %val )
{
   $mvTwistLeftSpeed = %val ? $Pref::Input::KeyboardTurnSpeed : 0;
}
moveMap.bind(keyboard, "period", twistRight);
ScriptFile: /client/config.cs
moveMap.bind(mouse0, "xaxis", twist);
moveMap.bind(keyboard, "comma", twistLeft);
moveMap.bind(keyboard, "period", twistRight);
Page «Previous 1 2
#1
11/16/2004 (12:07 pm)
Great resource, Derk!

Helps to understand lots of things for me!

Thanks!
#2
11/17/2004 (3:29 am)
Very useful! I'll look into this tonight, as well.
#3
11/17/2004 (7:53 pm)
The forum post that is linked to this resource is Turning Head to Look.
#4
12/03/2004 (7:11 am)
It is very useful!thanks.
#5
12/03/2004 (8:16 am)
When I implement this on the mac, my player jumps constantly and fires all his weapons.

any other mac folks try that?
#6
12/03/2004 (10:09 am)
Edward,

Since these code changes have nothing to do with the jump or firing code, my guess is that it is a bug that came from before implementing this. Try removing the twist code and see if it still happens. Of course, it could always be a missing semicolon :)

Thanks.
#7
03/17/2005 (6:22 pm)
I'm having a bit of troblem getting this to work in 1.3. One thing is that this

"mhead.z = mClampF(mHead.z + t,-mDataBlock->maxFreelookAngle,mDataBlock->maxFreelookAngle);" should be
"mHead.z = mClampF(mHead.z + t,-mDataBlock->maxFreelookAngle,mDataBlock->maxFreelookAngle);"

Typo change the h to H

-Stephen
#8
03/23/2005 (10:05 am)
Great resource.
#9
07/09/2005 (4:43 am)
would this code allow me to have a still person look around?
ie: i want my character to be standing still and while in 3rd view, have him look left/right and NOT move his lower half.
im not planning on using this for a mecha but for bipeds.
I'd like the ability for my character to look left and right without fully rotating the charcter.
Thanks again!

anyone know where i can find more about creating custom animations? links?
I want to integrate this with crouching, walking, running, and jumping/jetting.
#10
07/09/2005 (8:43 pm)
Alaric,

If you only want your character to look left and right, use the code in the default engine. You just need to have the correct animations (look and headside) in your character. The Orc already has them.

A search in the forums will get you the other resources for which you are looking.

Thanks.
#11
01/06/2006 (3:45 pm)
hmm.. how this should work, if in player.cc in void Player::updateMove(const Move* move)
after
mHead.x = mClampF(mHead.x + p,mDataBlock->minLookAngle,
                        mDataBlock->maxLookAngle);
we add:
F32 t = move->twist;
  if (t > M_PI) t -= M_2PI;
  mHead.z = mClampF(mHead.z + t,-mDataBlock->maxFreelookAngle,mDataBlock->maxFreelookAngle);
but just few lines after we have:
(after setting "yaw"):
F32 y = move->yaw;
      if (y > M_PI) y -= M_2PI;

      GameConnection* con = getControllingClient();
      if (move->freeLook && ((isMounted() && getMountNode() == 0) || (con && !con->isFirstPerson())))
      {
         mHead.z = mClampF(mHead.z + y,
                           -mDataBlock->maxFreelookAngle,
                           mDataBlock->maxFreelookAngle);
      }
      else
      {
         mRot.z += y;
         // Rotate the head back to the front, center horizontal
         // as well if we're controlling another object.
         mHead.z *= 0.5;
         if (mControlObject)
            mHead.x *= 0.5;
      }
where mHead.z is anyway overwritten?
Am I lost somewhere in code?

And... in GetMuzzleTransform:
-      mat->mul(getTransform(), pmat);
+      mat->mul(getTransform(), xmat);
But in code it IS xmat ... shouldn't it be
-      mat->mul(getTransform(), xmat);
+      mat->mul(getTransform(), pmat);
?
#12
02/23/2006 (11:13 am)
bank,

You understand the code enough to realize that you have to remove any other lines that change mHead.z.

Good catch with the error in GetMuzzleTransform. I have corrected it above and in the file.

Thanks.
#13
07/10/2006 (11:12 pm)
Hi Derk,

How do I get the torso twist to be animated? You mentioned briefly to have the "headside" and "look" animation. I do have these defined in the player and are able to play them standalone, but not when I'm twisting with the ., keys.

Thanks.
#14
07/12/2006 (1:12 pm)
Ron,

Make sure you have selected them to be "blend" animations. Otherwise, they won't "blend" with the root and run animations.

Thanks.
#15
01/01/2008 (5:15 am)
While this may not be part of the intended implementation, I found it needed for my FPS, and I think other people have been wondering about this as well.
I made an adjustment to this resource(thanks for the original, by the way, Derk :D) so that when you hit the threshold of the freelook, you'll start pivoting, like Halo 2, and other games like that.

It's pretty quick and easy, you just find the twist code in updateMove(), as seen here:
F32 p = move->pitch;      
if (p > M_PI) p -= M_2PI;      
mHead.x = mClampF(mHead.x + p,mDataBlock->minLookAngle, mDataBlock->maxLookAngle);


F32 t = move->twist;
if (t > M_PI) t -= M_2PI;
mHead.z = mClampF(mHead.z + t,-mDataBlock->maxFreelookAngle,mDataBlock->maxFreelookAngle);

and change it so that it looks like:
F32 p = move->pitch;
if (p > M_PI) p -= M_2PI;
mHead.x = mClampF(mHead.x + p,mDataBlock->minLookAngle,
       mDataBlock->maxLookAngle);

F32 y = move->yaw;

F32 t = move->twist;
if (t > M_PI) 
       t -= M_2PI;

if((mHead.z + t) > mDataBlock->maxFreelookAngle)
{
      y += (mHead.z + t) - mDataBlock->maxFreelookAngle;
}
else if((mHead.z + t) < -mDataBlock->maxFreelookAngle)
{
     y += mDataBlock->maxFreelookAngle + (mHead.z + t);
}

mHead.z = mClampF(mHead.z + t,-mDataBlock->maxFreelookAngle,mDataBlock->maxFreelookAngle);

//if we've overstepped on the yaw...
if (y > M_PI) 
     y -= M_2PI;

And yes, you do over write the yaw portion of that code. All other original changes in the resource apply.
Now you have twisting so your lower half stays put untill you look too far(defined by the maxFreeLook) and then you start pivoting!
Hope that helps!
#16
01/11/2008 (6:32 am)
Hmm.. a word 'twist' make it a bit wiried at first seeing only the title though.

But I finally knew that it is very useful resource. :-)
#17
02/25/2008 (1:17 am)
Great resource.

I was wondering, does anyone have an idea how one might go about controling a bot using this? AIPlayer has all the goodness that we added into the Player class, so there is twist ability encoded (right?). But the only example given to apply twist was with the $mvTwist globals (which will control the player's twist - to my understanding). Anyone have any insight to offer? Thanks.
#18
02/29/2008 (1:30 pm)
Very nice resource, and thank you Jeff for the modifications! The only problem is that the look animation is not a blend by default (I think), so until I create my own character I can't see the animation. Grr.

EDIT: Actually, not the only problem. The looking and twisting is fine, but when you run, you move in the direction you're facing, not twisting. Which is cool for a mecha game, but extremely annoying for an FPS. I'll see what I can do... I'd say that after adjusting mRot.z to be 0...2PI, you check to see if the mRot vector is different to the mHead vector. If so, and we're moving, change mRot to be centered to mHead.
#19
03/08/2008 (1:17 am)
Okay, problem solved. The following will turn your character's torso to face the same way as his head whenever you're moving.
My additions go after the following part in the code (in Player::updateMove):
GameConnection* con = getControllingClient();
		if (move->freeLook && ((isMounted() && getMountNode() == 0) ||
			(con && !con->isFirstPerson())))
		{
			...
		}
		else
		{
			...
		}

Just add
//Dan's twist improvements ->
		//If we're moving, centre legs to twist
		if(((move->x != 0.0f) || (move->y != 0.0f))
			&& (mState == MoveState)
			&& (mDamageState == Enabled))
		{
			//Body rot in +-180deg
			F32 torso = mRot.z - M_PI_F;
			//Head rot in absolute terms
			F32 head = mHead.z;
			//Scale by speed factor
			head *= 0.25;
			//Add to body, subtract from head
			mRot.z += head / 2;
			mHead.z -= head;
		}
		//<- Dan

Hmm... there seems to be something wrong with this code, though I can't figure out what. It seems to overcorrect the rotation, so you end up facing way off where you were looking.

This is fixed, so you now face the right direction, but for some reason there's a bit of wobble before you get on the right track.
#20
07/29/2008 (9:07 am)
Hi i have a problem, in my engine i dont have gameconnectionsMoves.h, i only have gameConnection.h and gameConnectionEvents.h :S, if you can help me..
Page «Previous 1 2