Player turning ‘inertia’
by Daniel Buckmaster · 08/28/2009 (3:10 am) · 13 comments
Stock TGE’s player movement feels a little stiff – it’s very responsive, but some people may want it to feel just a bit less rigid. This simple change adds a little ‘inertia’ when Players look around, and as a bonus lets you specify a turning speed for AIPlayers.
Developed and tested on TGE 1.5.2.
Length: Short
Difficulty: Beginner (simple changes to existing class)
The basic idea of my changes is when a Player rotates, instead of not rotating if the user isn’t moving the mouse, we add a little of the rotation we performed last update. By multiplying this by a small factor, the Player’s rotation slows down over a short period of time after the user stops moving the mouse. This requires adding two datablock members to control the amount of rotation we add from last update, so let’s start with that. At the same time, I’ll add a datablock change for AIPlayers to use that is necessary with this resource. Because AIPlayers turn to face their destination in one tick – no gradual change like a human-controlled Player – adding inertia means they go spinning in circles because of their huge turn speed. I’ll add a member to PlayerData that restricts how far an AIPlayer can turn in a single tick, so they turn smoothly to track a target.
I’ll present a code block for you to find, with my changes surrounded by comments. Just add my code alongside the existing lines.
Open up player.h, and let’s define our two new members in the definition of struct PlayerData:
We’re done with player.h for now, so it’s time to take a look at player.cc. Since we added new members to the datablock, we now have to jump through a bunch of hoops to ensure these members are properly networked and can be set in scripts.
In PlayerData::PlayerData:
In PlayerData::preload:
In PlayerData::initPersistFields:
In PlayerData::packData:
That’s it for our datablock integration! Now we can make use of that value in Player::updateMove:
That code is where the ‘inertia’ is applied, if we’re not turning this tick. This results in a gradual falloff of turning speed, depending on the turning factor you put in the datablock. Let’s go ahead and put those values in the datablock now. In starter.fps/server/scripts/player.cs:
Just a few last changes to make to AIPlayer, in aiPlayer.cc. Find the function getAIMove:
You can go ahead and recompile now, and run the game. You should notice the effect – if not, try setting the values in the datablock higher.
Note: it might be a better idea to try clamping AI turning speed, rather than just multiplying it. I'd tried that in the mast, but failed to wrap my head around the trigonometry and did it this way instead. It works fine, but be careful about setting the turning speed to higher values.
Developed and tested on TGE 1.5.2.
Length: Short
Difficulty: Beginner (simple changes to existing class)
The basic idea of my changes is when a Player rotates, instead of not rotating if the user isn’t moving the mouse, we add a little of the rotation we performed last update. By multiplying this by a small factor, the Player’s rotation slows down over a short period of time after the user stops moving the mouse. This requires adding two datablock members to control the amount of rotation we add from last update, so let’s start with that. At the same time, I’ll add a datablock change for AIPlayers to use that is necessary with this resource. Because AIPlayers turn to face their destination in one tick – no gradual change like a human-controlled Player – adding inertia means they go spinning in circles because of their huge turn speed. I’ll add a member to PlayerData that restricts how far an AIPlayer can turn in a single tick, so they turn smoothly to track a target.
I’ll present a code block for you to find, with my changes surrounded by comments. Just add my code alongside the existing lines.
Open up player.h, and let’s define our two new members in the definition of struct PlayerData:
F32 groundImpactShakeDuration; ///< How long to shake F32 groundImpactShakeFalloff; ///< How fast the shake disapates //Dan’s mods (turning intertia) -> F32 horizTurnSpeedInertia; ///< Amount of z-axis rotation to carry over from last tick F32 vertTurnSpeedInertia; ///< Amount of x-axis rotation to carry over from last tick F32 AITurnSpeed; ///< Fraction of actual distance for AIPlayers to turn //<- Dan’s mods (turning inertia)
We’re done with player.h for now, so it’s time to take a look at player.cc. Since we added new members to the datablock, we now have to jump through a bunch of hoops to ensure these members are properly networked and can be set in scripts.
In PlayerData::PlayerData:
groundImpactShakeDuration = 1.0f; groundImpactShakeFalloff = 10.0f; //Dan’s mods (turning inertia) -> horizTurnSpeedInertia = 0.0f; vertTurnSpeedInertia = 0.0f; AITurnSpeed = 1.0f; //<- Dan’s mods (turning inertia)
In PlayerData::preload:
// Lookup shadow node (shadow center moves in synch with this node) shadowNode = spineNode[0]; //Dan’s mods (turning inertia) -> //Constrain these to sensible values horizTurnSpeedInertia = mClampF(horizTurnSpeedInertia,0.0f,1.0f); vertTurnSpeedInertia = mClampF(vertTurnSpeedInertia,0.0f,1.0f); AITurnSpeed = mClampF(AITurnSpeed,0.01f,1.0f); //<- Dan’s mods (turning inertia)
In PlayerData::initPersistFields:
addField("groundImpactShakeDuration", TypeF32, Offset(groundImpactShakeDuration, PlayerData));
addField("groundImpactShakeFalloff", TypeF32, Offset(groundImpactShakeFalloff, PlayerData));
//Dan’s mods (turning inertia) ->
addField("horizTurnSpeedInertia",TypeF32,Offset(horizTurnSpeedInertia,PlayerData));
addField("vertTurnSpeedInertia",TypeF32,Offset(vertTurnSpeedInertia,PlayerData));
addField("AITurnSpeed",TypeF32,Offset(AITurnSpeed,PlayerData));
//<- Dan’s mods (turning inertia)In PlayerData::packData:
stream->write(groundImpactShakeDuration); stream->write(groundImpactShakeFalloff); //Dan’s mods (turning inertia) -> stream->write(horizTurnSpeedInertia); stream->write(vertTurnSpeedInertia); stream->write(AITurnSpeed); //<- Dan’s mods (turning inertia)And in PlayerData::unpackData:
stream->read(&groundImpactShakeDuration); stream->read(&groundImpactShakeFalloff); //Dan’s mods (turning inertia) -> stream->read(&horizTurnSpeedInertia); stream->read(&vertTurnSpeedInertia); stream->read(&AITurnSpeed); //<- Dan’s mods (turning inertia)
That’s it for our datablock integration! Now we can make use of that value in Player::updateMove:
F32 prevZRot = mRot.z;
//Dan’s mods (turning inertia) ->
//Need to save this before it’s overwritten!
Point3F prevHeadVec = delta.headVec;
//<- Dan’s mods (turning inertia)
delta.headVec = mHead;
F32 p = move->pitch;
//Dan’s mods (turning inertia) ->
//If there was no movement this tick, add on some of last tick’s movement
if(p == 0.0f)
p -= prevHeadVec.x * mDataBlock->vertTurnSpeedInertia;
//<- Dan’s mods (turning inertia)
if (p > M_PI_F)
p -= M_2PI_F;
mHead.x = mClampF(mHead.x + p,mDataBlock->minLookAngle,
mDataBlock->maxLookAngle);
F32 y = move->yaw;
//Dan’s mods (turning inertia) ->
if(y == 0.0f)
y -= delta.rotVec.z * mDataBlock->horizTurnSpeedInertia;
//<- Dan’s mods (turning inertia)
if (y > M_PI_F)
y -= M_2PI_F;That code is where the ‘inertia’ is applied, if we’re not turning this tick. This results in a gradual falloff of turning speed, depending on the turning factor you put in the datablock. Let’s go ahead and put those values in the datablock now. In starter.fps/server/scripts/player.cs:
debrisShapeName = "~/data/shapes/player/debris_player.dts"; debris = playerDebris; //Dan’s mods (turning inertia) -> horizTurnSpeedInertia = 0.5; vertTurnSpeedInertia= 0.4; AITurnSpeed = 0.2; //<- Dan’s mods (turn speed inertia)
Just a few last changes to make to AIPlayer, in aiPlayer.cc. Find the function getAIMove:
// now make sure we take the short way around the circle
if( yawDiff > M_PI )
yawDiff -= M_2PI;
else if( yawDiff < -M_PI )
yawDiff += M_2PI;
movePtr->yaw = yawDiff;
//Dan’s mods (turning inertia) ->
movePtr->yaw *= mDataBlock->AITurnSpeed;
//<- Dan’s mods (turning inertia)
// Next do pitch.
if (!mAimObject && !mAimLocationSet) {
// Level out if were just looking at our next way point.
Point3F headRotation = getHeadRotation();
movePtr->pitch = -headRotation.x;
//Dan’s mods (turning inertia) ->
movePtr->pitch *= mDataBlock->AITurnSpeed;
//<- Dan’s mods (turning inertia)
}
else {
// This should be adjusted to run from the
// eye point to the object's center position. Though this
// works well enough for now.
F32 vertDist = mAimLocation.z - location.z;
F32 horzDist = mSqrt(xDiff * xDiff + yDiff * yDiff);
F32 newPitch = mAtan( horzDist, vertDist ) - ( M_PI / 2.0f );
if (mFabs(newPitch) > 0.01) {
Point3F headRotation = getHeadRotation();
movePtr->pitch = newPitch - headRotation;
//Dan’s mods (turning inertia) ->
movePtr->pitch *= mDataBlock->AITurnSpeed;
//<- Dan’s mods (turning inertia)
}
}You can go ahead and recompile now, and run the game. You should notice the effect – if not, try setting the values in the datablock higher.
Note: it might be a better idea to try clamping AI turning speed, rather than just multiplying it. I'd tried that in the mast, but failed to wrap my head around the trigonometry and did it this way instead. It works fine, but be careful about setting the turning speed to higher values.
About the author
Studying mechatronic engineering and computer science at the University of Sydney. Game development is probably my most time-consuming hobby!
#2
08/28/2009 (6:01 am)
Thanks for the resource Daniel, it works great.
#3
08/28/2009 (5:28 pm)
thanks, maybe this can implement in the vehicle classes to...
#4
08/28/2009 (5:34 pm)
Hey cool! This would go great with making a player play a step animation while turning. Thumbs up for another nifty resource :D
#5
08/28/2009 (7:34 pm)
Too many resources too add. I can't wait to try this one.
#6
09/03/2009 (2:06 pm)
Once again I learn from one of the many masters....Thank you Daniel
#7
But looking at the code, I've seen that you use in player updateMove, for example
there is mStance, I suppose this is because you have created this resource from you code, because vertTurnSpeedInertia isn't declared as a vector.
Could you check?
10/03/2009 (5:19 am)
Thank you for the resource, I'll need it in a later moment.But looking at the code, I've seen that you use in player updateMove, for example
11. if(p == 0.0f) 12. p -= prevHeadVec.x * mDataBlock->vertTurnSpeedInertia[mStance];
there is mStance, I suppose this is because you have created this resource from you code, because vertTurnSpeedInertia isn't declared as a vector.
Could you check?
#8
I also had to make mDatablock in player.h public in order for the aiplayer to access it.
and finally there's a typo in the initPersistFields code. The lines for vertTurnSpeedInertia and AITurnSpeed are missing a close bracket at the end.
Instead of:
use this:
Otherwise a very useful resource!
10/19/2009 (6:31 am)
This works in TGEA 1.8.1 with a little tweaking. As Davide said mStance isn't included in TGEA code so replace both occurances of mDataBlock->vertTurnSpeedInertia[mStance]with
mDataBlock->vertTurnSpeedInertia
I also had to make mDatablock in player.h public in order for the aiplayer to access it.
and finally there's a typo in the initPersistFields code. The lines for vertTurnSpeedInertia and AITurnSpeed are missing a close bracket at the end.
Instead of:
addField("vertTurnSpeedInertia",TypeF32,Offset(vertTurnSpeedInertia,PlayerData);
addField("AITurnSpeed",TypeF32,Offset(AITurnSpeed,PlayerData);use this:
addField("vertTurnSpeedInertia",TypeF32,Offset(vertTurnSpeedInertia,PlayerData));
addField("AITurnSpeed",TypeF32,Offset(AITurnSpeed,PlayerData));Otherwise a very useful resource!
#9
Christopher - thanks for the update and bug fixes. Good catch about mDataBlock... maybe try inserting getDataBlock() instead, to avoid having to publicize mDataBlock (or make it protected instead of public). I was working with my own code in which I've merged Player and AIPlayer, so I didn't have to worry about inheritance.
Resource updated.
10/28/2009 (5:03 am)
Davide - sorry, I forgot to chck 'notify of replies'! :P. Yes, that's a copy-paste error from my own code.Christopher - thanks for the update and bug fixes. Good catch about mDataBlock... maybe try inserting getDataBlock() instead, to avoid having to publicize mDataBlock (or make it protected instead of public). I was working with my own code in which I've merged Player and AIPlayer, so I didn't have to worry about inheritance.
Resource updated.
#11
01/13/2010 (6:30 pm)
Not from me :P. But I would imagine the code is pretty similar - have a go, and if there are any issues, post them up and I'm sure someone will be able to help.
#12
Thanks a lot!
01/14/2010 (2:22 am)
Actually, it went in without a hitch, nice code. Files have changed to .cpp and player.cs is now located on game/art/datablocks/player.csThanks a lot!
#13
Ty for this recource, now i could solve a big problem :) The AIs turn now more realistic.
04/25/2011 (9:26 pm)
I did use that code in T3D beta3, and it works very well!Ty for this recource, now i could solve a big problem :) The AIs turn now more realistic.

Torque Owner CSMP
MP Studios
P.S. I'm glad you took the AI into consideration as well, as it would not feel right if the AI did not react to this as well.
Thanks! :)