Mantling
by Daniel Buckmaster · 12/15/2008 (6:33 am) · 14 comments
<a href="/static/pg/resource/15803.mantling.zip">Download Code File</a>
Developed and tested with TGE 1.5.2. Not yet network-tested, but should work.
The problem with most FPSs, to me, is that their movement is extremely inorganic and rigid. Thief is a game with what is, in my opinion, a very good movemet model, and part of this comes from the ability to mantle anywhere in the environment, so the player can climb onto ledges without having to perform a jammy, stupid-looking crouch-jump.
The great thing is that Torque can already do this; the Player's 'stepping' does, in effect, exactly what we want to do with a mantle - finds a ledge, and tries to move on top of it. I don't understand most of the code in Player::updatePos and Player::step, but I have worked enough of it out to modify the functions for my purposes.
To get you sold, here's a video of Kork in action:
Sorry about the low quality...
Of course, there are some issues. There are moments when you nearly make a mantle, but end up falling off the ledge, but still in the mantling state, which looks sort of silly.
Here's an overview of the finished system:
PlayerData values:
F32 maxMantleHeight - the maximum height above the bottom of the Player's bound box that a ledge can be (works just like maxStepHeight)
bool mantleWithoutJumping - if set to true, the Player will automatically mantle up ledges it collides with. Otherwise, it will only mantle while not in contact with the ground.
You can turn mantling on or off for specific Players with the console command setMantlingEnabled(bool enable). This allows you to turn it on and off as you wish, maybe if the player is injured, unusually heavy, or is holding something in their hands.
Note: A better way to do auto-mantling is to mantle if a certain button is pressed, rather than waiting for a jump. I'm fairly sure this is quite easy to do somehow...
Mantling requires an animation to be added. I've included the animation with the source files, mantle.dsq. It can be added to Kork and the Blue Guy, but isn't guaranteed to work with any other skeletons. Take a look at how it is set up to figure out how to construct your own animations. Basically, the player is moved down and backwards, as if it is climbing up into its own bounding box. The animation should ideally start and end with poses quite similar to the idle pose. My animation isn't great, but it gives you an idea of what is required.
Okay, now the code changes. Of course, you can just download the files provided and merge them into your own code, but you miss my explanations. And I say that like it's a bad thing...
We'll deal with the .h file first. Open player.h.
The first thing to do is declare the two datablock fields we want to add: maxMantleHeight and mantleWithoutJumping. I stuck them in right after maxStepHeight.
Find this:
Next, we'll define an animation to use when mantling. This follows the same format as animations for jumping, landing, etc.
Find:
We want the ability to toggle mantling on and off per character, which we'll do with a method later on. For now, we need to declare the mask bit that will tell the network we've changed this setting.
Find:
The way I've chosen to integrate mantling is to add a new state for it. Mantling is very similar to recovering in what it does to the player, so I chose to implement it the same way.
Find:
Now we add the per-object members to Player for mantling. These are the mantle enabled flag, another non-networked mantling flag, and a simple method to tell us whether we can mantle. This method is very simple in this implementation; I added it because I plan on adding more complex checks.
Find:
This resource makes some minor changes to the Player::step function. We just need to add a parameter to the header to accomodate these changes.
Find the method declaration:
And finally we declare our method for toggling mantling per player. This is separate from the oter per-player declarations because it needs to be public to be accessed by the console.
Find this declaration:
Now we're done with the header file, open player.cc.
We declared the MantleAnim animation in player.h, but now we have to 'initialise' it with all the other animations.
Find:
And we also have to initialise the datablock fields. You can change the defaults here if you like; they're purely arbitrary. I set the default mantling height equal to the default step height so that if you recompile with no script changes, you won't see any mantling (stepping is checked first, so if we can step, we don't mantle).
Find:
Now we have to expose these new members to scripts, so we can set them in our datablock declarations.
Find:
And we have to send them down the network via packData and unpackData.
Find:
Now we can move on to the per-object fields in Player. First we initialise them.
Find:
Remember we added a state to Player::ActionState to tell the game when we're recovering? We need to o something with it now. Here we add it to the setState method. If we're set to the mantle state, we simply set the action thread to the correct animation.
Look for:
We also need to add a clause to getStateName, so scripts will be informed when we're mantling.
Find:
In updateMove, the player's orientation and velocity are computed. I wanted to stop the player turning if we're in the mantling state, so we add an additional check to the rotation if-block in updateMove. This does seem to cause some wierd jittering issues if you are turning as you start mantling, which I'll look into.
Replace this:
Further down in upateMove, after we've looked for contact surfaces, we can set a flag saying whether we can jump this move. The upshot is that if we're in the air (no run surface) and we can't mantle without jumping, then we can't mantle this move.
Find:
These are our infrastructure methods that deal with mMantleEnabled. Notice that we set the MantleMask that we declared in player.h. This tells the network we've changed this member and need to update the clients.
Find this method:
The updateActionThread method deals with the player's main animation. We need to add a little bit telling the Player that if he's mantling, and the animation has finished, to go back to the movement state and pick the right animation.
Now find:
I mentioned making changes to the Player::step method. Here they are. First we need to fix the function header that we changed in player.h.
Replace the method header:
Now we can make use of that new parameter. What that parameter does is make the method more generic - we can step any height, instead of being forced to use mDataBlock->maxStepHeight. This means that we can now use this method for mantling as well as stepping, instead of having to duplicate code.
Replace this line:
Now we're in Player::updatePos. This takes the velocity that was calculated in updateMove and tries to physically move the Player, checking for collision and stuff. We need to add an extra local variable first.
Beneath this line:
Now we get to the meat of the functoin, the stepping code. Don't worry if you don't understand what it does; neither do I! But we do need to make a small change to the existing stepping code (because we added a parameter to the step function).
Find this chunk:
After the end of that whole chunk, add this chunk:
Now we're back to boring stuff. We need to send the mMantleEnabled flag down the network in packUpdate and unpackUpdate.
Find:
Now we declare a console method so that scripts can turn mantling on and off.
Find:
And that's it for code changes! Go ahead and recompile. If you're using the standard scripts, you shouldn't notice any changes.
First we need to mak the player shape aware of the new animation.
In player.cs in the data/shapes/player folder, find:
And now we can add those datablock values. On a minor note, I've overwritten all the defaults, so be aware of that. If you have your own maxStepHeight, you can use that. Just make sure that if you want your Player to mantle, that maxMantleHeight is greater than maxStepHeight.
In player.cs in server/scripts, find:
Now we're finished with script changes! If you run the game now, you should be able to do similar things to what was in the video.

Developed and tested with TGE 1.5.2. Not yet network-tested, but should work.
The problem with most FPSs, to me, is that their movement is extremely inorganic and rigid. Thief is a game with what is, in my opinion, a very good movemet model, and part of this comes from the ability to mantle anywhere in the environment, so the player can climb onto ledges without having to perform a jammy, stupid-looking crouch-jump.
The great thing is that Torque can already do this; the Player's 'stepping' does, in effect, exactly what we want to do with a mantle - finds a ledge, and tries to move on top of it. I don't understand most of the code in Player::updatePos and Player::step, but I have worked enough of it out to modify the functions for my purposes.
To get you sold, here's a video of Kork in action:
Sorry about the low quality...
Of course, there are some issues. There are moments when you nearly make a mantle, but end up falling off the ledge, but still in the mantling state, which looks sort of silly.
Here's an overview of the finished system:
PlayerData values:
F32 maxMantleHeight - the maximum height above the bottom of the Player's bound box that a ledge can be (works just like maxStepHeight)
bool mantleWithoutJumping - if set to true, the Player will automatically mantle up ledges it collides with. Otherwise, it will only mantle while not in contact with the ground.
You can turn mantling on or off for specific Players with the console command setMantlingEnabled(bool enable). This allows you to turn it on and off as you wish, maybe if the player is injured, unusually heavy, or is holding something in their hands.
Note: A better way to do auto-mantling is to mantle if a certain button is pressed, rather than waiting for a jump. I'm fairly sure this is quite easy to do somehow...
Mantling requires an animation to be added. I've included the animation with the source files, mantle.dsq. It can be added to Kork and the Blue Guy, but isn't guaranteed to work with any other skeletons. Take a look at how it is set up to figure out how to construct your own animations. Basically, the player is moved down and backwards, as if it is climbing up into its own bounding box. The animation should ideally start and end with poses quite similar to the idle pose. My animation isn't great, but it gives you an idea of what is required.
Okay, now the code changes. Of course, you can just download the files provided and merge them into your own code, but you miss my explanations. And I say that like it's a bad thing...
We'll deal with the .h file first. Open player.h.
The first thing to do is declare the two datablock fields we want to add: maxMantleHeight and mantleWithoutJumping. I stuck them in right after maxStepHeight.
Find this:
F32 maxStepHeight; ///< Maximum height the player can step upAnd immediately after it, add:
F32 maxMantleHeight; ///< Maximum height for mantling bool mantleWithoutJumping; ///< Can we mantle from the ground?
Next, we'll define an animation to use when mantling. This follows the same format as animations for jumping, landing, etc.
Find:
StandJumpAnim,And immediately underneath, add:
MantleAnim,Make sure you add this before the next line of code!
We want the ability to toggle mantling on and off per character, which we'll do with a method later on. For now, we need to declare the mask bit that will tell the network we've changed this setting.
Find:
NextFreeMask = Parent::NextFreeMask << 3And replace it with
MantleMask = Parent::NextFreeMask << 3,
NextFreeMask = Parent::NextFreeMask << 4The way I've chosen to integrate mantling is to add a new state for it. Mantling is very similar to recovering in what it does to the player, so I chose to implement it the same way.
Find:
RecoverState,And add underneath:
MantleState,
Now we add the per-object members to Player for mantling. These are the mantle enabled flag, another non-networked mantling flag, and a simple method to tell us whether we can mantle. This method is very simple in this implementation; I added it because I plan on adding more complex checks.
Find:
SimObjectPtr<ShapeBase> mControlObject; ///< Controlling objectAnd add beneath:
bool mMantleEnabled; ///< Can we mantle? bool mMantleThisMove; ///< Can we mantle RIGHT NOW? bool canMantle(); ///< Can we mantle?
This resource makes some minor changes to the Player::step function. We just need to add a parameter to the header to accomodate these changes.
Find the method declaration:
bool step(Point3F *pos,F32 *maxStep,F32 time);And change it to:
bool step(Point3F *pos,F32 *maxStep,F32 height,F32 time);
And finally we declare our method for toggling mantling per player. This is separate from the oter per-player declarations because it needs to be public to be accessed by the console.
Find this declaration:
void getDamageLocation(const Point3F& in_rPos, const char *&out_rpVert, const char *&out_rpQuad);And add beneath it:
void setMantleEnabled(bool enable); ///< Set the ability to mantle
Now we're done with the header file, open player.cc.
We declared the MantleAnim animation in player.h, but now we have to 'initialise' it with all the other animations.
Find:
{ "standjump" }, // StandJumpAnimAnd stick in right beneath:{ "mantle" },And we also have to initialise the datablock fields. You can change the defaults here if you like; they're purely arbitrary. I set the default mantling height equal to the default step height so that if you recompile with no script changes, you won't see any mantling (stepping is checked first, so if we can step, we don't mantle).
Find:
maxStepHeight = 1.0f;And add on the next line:
maxMantleHeight = 1.0f; mantleWithoutJumping = false;
Now we have to expose these new members to scripts, so we can set them in our datablock declarations.
Find:
addField("maxStepHeight", TypeF32, Offset(maxStepHeight, PlayerData));And add beneath:addField("maxMantleHeight",TypeF32,Offset(maxMantleHeight,PlayerData));
addField("mantleWithoutJumping",TypeBool,Offset(mantleWithoutJumping,PlayerData));And we have to send them down the network via packData and unpackData.
Find:
stream->write(maxStepHeight);And add on a new line:
stream->write(maxMantleHeight); stream->writeFlag(mantleWithoutJumping);Find:
stream->read(&maxStepHeight);Add:
stream->read(&maxMantleHeight); mantleWithoutJumping = stream->readFlag();
Now we can move on to the per-object fields in Player. First we initialise them.
Find:
dMemset( mSplashEmitter, 0, sizeof( mSplashEmitter ) );And add:
mMantleEnabled = true; mMantleThisMove = true; //True till proven false
Remember we added a state to Player::ActionState to tell the game when we're recovering? We need to o something with it now. Here we add it to the setState method. If we're set to the mantle state, we simply set the action thread to the correct animation.
Look for:
case RecoverState: {
mRecoverTicks = recoverTicks;
mReversePending = U32(F32(mRecoverTicks) / sLandReverseScale);
setActionThread(PlayerData::LandAnim, true, false, true, true);
break;
}And add underneath:case MantleState:
setActionThread(PlayerData::MantleAnim,true,false,true,true);
break;We also need to add a clause to getStateName, so scripts will be informed when we're mantling.
Find:
if (mState == RecoverState)
return "Recover";And add:if(mState == MantleState)
return "Mantle";In updateMove, the player's orientation and velocity are computed. I wanted to stop the player turning if we're in the mantling state, so we add an additional check to the rotation if-block in updateMove. This does seem to cause some wierd jittering issues if you are turning as you start mantling, which I'll look into.
Replace this:
// Update current orientation
if (mDamageState == Enabled) {With this:// Update current orientation
if (mDamageState == Enabled && mState != MantleState) {Further down in upateMove, after we've looked for contact surfaces, we can set a flag saying whether we can jump this move. The upshot is that if we're in the air (no run surface) and we can't mantle without jumping, then we can't mantle this move.
Find:
if (jumpSurface)
mJumpSurfaceNormal = contactNormal;And add underneath://Set mantle possibility mMantleThisMove = !runSurface || mDataBlock->mantleWithoutJumping;
These are our infrastructure methods that deal with mMantleEnabled. Notice that we set the MantleMask that we declared in player.h. This tells the network we've changed this member and need to update the clients.
Find this method:
bool Player::canJump()
{
...
}And add beneath it:bool Player::canMantle()
{
return mMantleEnabled && mDamageState == Enabled;
}
void Player::setMantleEnabled(bool enable)
{
if(mState != MantleState)
{
mMantleEnabled = enable;
setMaskBits(MantleMask);
}
}The updateActionThread method deals with the player's main animation. We need to add a little bit telling the Player that if he's mantling, and the animation has finished, to go back to the movement state and pick the right animation.
Now find:
// Mount pending variable puts a hold on the delayTicks below so players don't
// inadvertently stand up because their mount has not come over yet.
if (mMountPending)
mMountPending = (isMounted() ? 0 : (mMountPending - 1));And add://Mantle animation
if(mActionAnimation.action == PlayerData::MantleAnim)
if(mActionAnimation.atEnd)
{
setState(MoveState);
pickActionAnimation();
}I mentioned making changes to the Player::step method. Here they are. First we need to fix the function header that we changed in player.h.
Replace the method header:
bool Player::step(Point3F *pos,F32 *maxStep,F32 time)With this:
bool Player::step(Point3F *pos,F32 *maxStep,F32 height,F32 time)
Now we can make use of that new parameter. What that parameter does is make the method more generic - we can step any height, instead of being forced to use mDataBlock->maxStepHeight. This means that we can now use this method for mantling as well as stepping, instead of having to duplicate code.
Replace this line:
box.max.z += mDataBlock->maxStepHeight * scale.z + sMinFaceDistance;With:
box.max.z += height * scale.z + sMinFaceDistance;
Now we're in Player::updatePos. This takes the velocity that was calculated in updateMove and tries to physically move the Player, checking for collision and stuff. We need to add an extra local variable first.
Beneath this line:
F32 maxStep = mDataBlock->maxStepHeight;Add this:
F32 maxMantle = mDataBlock->maxMantleHeight;
Now we get to the meat of the functoin, the stepping code. Don't worry if you don't understand what it does; neither do I! But we do need to make a small change to the existing stepping code (because we added a parameter to the step function).
Find this chunk:
// Try stepping if there is a vertical surface
if (collisionList.maxHeight < start.z + mDataBlock->maxStepHeight) {
bool stepped = false;
for (U32 c = 0; c < collisionList.count; c++) {
Collision& cp = collisionList.collision[c];
// if (mFabs(mDot(cp.normal,VectorF(0,0,1))) < sVerticalStepDot)
// Dot with (0,0,1) just extracts Z component [lh]-
if (mFabs(cp.normal.z) < sVerticalStepDot)
{
stepped = step(&start,&maxStep,time);
break;
}
}
if (stepped)
{
continue;
}
}Replace this line:stepped = step(&start,&maxStep,time);With this:
stepped = step(&start,&maxStep,mDataBlock->maxStepHeight,time);Now we can add the mantling code. For lack of a more elegant way to do this, I simply duplicated the entire step code and stuck it in an else-clause. All the specifics have been changed as well. Note that if we mantled, we set the state to MantleState if we have an animation. This isn't very elegant a way to do it, but if we go into MantleState and we don't have an animation, we get stuck there, which is bad.
After the end of that whole chunk, add this chunk:
else
{
// Now try mantling
if (collisionList.maxHeight < start.z + mDataBlock->maxMantleHeight * scale.z &&
canMantle() && mMantleThisMove) {
bool mantled = false;
for (U32 c = 0; c < collisionList.count; c++) {
Collision& cp = collisionList.collision[c];
if (mFabs(cp.normal.z) < sVerticalStepDot)
{
mantled = step(&start,&maxMantle,mDataBlock->maxMantleHeight,time);
break;
}
}
if (mantled)
{
if(mDataBlock->actionList[PlayerData::MantleAnim].sequence != -1)
setState(MantleState);
continue;
}
}
}Now we're back to boring stuff. We need to send the mMantleEnabled flag down the network in packUpdate and unpackUpdate.
Find:
// Ghost need energy to predict reliably stream->writeFloat(getEnergyLevel() / mDataBlock->maxEnergy,EnergyLevelBits);And add:
if(stream->writeFlag(mask & MantleMask))
stream->writeFlag(mMantleEnabled);Find:F32 energy = stream->readFloat(EnergyLevelBits) * mDataBlock->maxEnergy; setEnergyLevel(energy);And add:
if(stream->readFlag())
mMantleEnabled = stream->readFlag();Now we declare a console method so that scripts can turn mantling on and off.
Find:
ConsoleMethod( Player, checkDismountPoint, bool, 4, 4, "(Point3F oldPos, Point3F pos)")
{
...
}And add:ConsoleMethod(Player,setMantleEnabled,void,3,3,"%player.setMantleEnabled(true/false)")
{
object->setMantleEnabled(dAtob(argv[2]));
}And that's it for code changes! Go ahead and recompile. If you're using the standard scripts, you shouldn't notice any changes.
First we need to mak the player shape aware of the new animation.
In player.cs in the data/shapes/player folder, find:
sequence29 = "./player_looknw.dsq looknw";And add:
sequence30 = "./mantle.dsq mantle";And add the mantle.dsq file to that folder (with all the other animations).
And now we can add those datablock values. On a minor note, I've overwritten all the defaults, so be aware of that. If you have your own maxStepHeight, you can use that. Just make sure that if you want your Player to mantle, that maxMantleHeight is greater than maxStepHeight.
In player.cs in server/scripts, find:
jumpDelay = 15;And add beneath:
maxStepHeight = 0.5; maxMantleHeight = 1.5; mantleWithoutJumping = true;
Now we're finished with script changes! If you run the game now, you should be able to do similar things to what was in the video.

About the author
Studying mechatronic engineering and computer science at the University of Sydney. Game development is probably my most time-consuming hobby!
#2
12/15/2008 (6:55 am)
Agree, very cool work Daniel.. Will tinker with this during the holidays ..
#3
12/15/2008 (11:43 am)
Nice
#4
12/15/2008 (10:58 pm)
This is awesome stuff, anything to get more movements into torque is great, ill definetely try this out in tgea.
#5
12/16/2008 (5:25 am)
Be sure to let me know if it works. As far as I know, which isn't far, it should work. I really have no idea how different Player movement code is in TGEA.
#7
01/25/2009 (9:59 pm)
Deepscratch, would you care to post your changes for everyone to see?
#8
Can you let us know what you did to make it work for TGEA 1.7.1?
Thanks!
02/22/2009 (2:18 pm)
@deepscratchCan you let us know what you did to make it work for TGEA 1.7.1?
Thanks!
#9
find -
void setMantlingEnabled(bool enable);
add
void setMantleEnabled(bool enable);
03/13/2009 (3:15 am)
Player.h filefind -
void setMantlingEnabled(bool enable);
add
void setMantleEnabled(bool enable);
#10
03/13/2009 (12:09 pm)
Whoops, sorry - that was a typo on my part. I originally defined 'setMantlingEnabled' in the resource. Updated.
#11
07/24/2009 (7:00 pm)
VERY good resource! Thank you!
#12
My player has trouble mantling over objects like this, because he tries to step on the slanted edge (outlined in red) and slides off.

Is there a way to increase the forward distance for surfaces that players look for when they try to mantle, so they would step on the flat edge on top?
Help is appreciated!
07/25/2009 (7:33 pm)
One problem though, could somebody help?My player has trouble mantling over objects like this, because he tries to step on the slanted edge (outlined in red) and slides off.

Is there a way to increase the forward distance for surfaces that players look for when they try to mantle, so they would step on the flat edge on top?
Help is appreciated!
#13
I think any modifications you make here should be in the Player::step method. At the start of the method, the mantling search box is set up, and at that point you could step in and change the 'offset' vector.
07/26/2009 (3:31 pm)
That's a tricky problem :P. Increasing the search distance could help, but you might run into cases where you mantle onto something too far ahead, stop moving, and then fall anyway.I think any modifications you make here should be in the Player::step method. At the start of the method, the mantling search box is set up, and at that point you could step in and change the 'offset' vector.
const Point3F& scale = getScale(); Box3F box; VectorF offset = mVelocity * time; //Here! box.min = mObjBox.min + offset + *pos; box.max = mObjBox.max + offset + *pos; box.max.z += mDataBlock->maxStepHeight * scale.z + sMinFaceDistance;See how that works for you. You might want to either scale the distance up by a factor of 2 or whatever, or just add on a standard distance.
#14
Here's the biggest change I did,
I can confirm that it works in multiplayer.
03/29/2014 (4:19 am)
Just for anyone who wants this resource in their game I have just added to my project and I only made a few minor changes to get it working in Torque 3D 3.0. I haven't tested it in multiplayer yet but I will.Here's the biggest change I did,
else
{
// Now try mantling
if (collisionList.getMaxHeight() < start.z + mDataBlock->maxMantleHeight * scale.z &&
canMantle() && mMantleThisMove) {
bool mantled = false;
for (U32 c = 0; c < collisionList.getCount(); c++) {
Collision& cp = collisionList[c];
if (mFabs(cp.normal.z) < sVerticalStepDot)
{
mantled = step(&start,&maxMantle,mDataBlock->maxMantleHeight,time);
break;
}
}
if (mantled)
{
if(mDataBlock->actionList[PlayerData::MantleAnim].sequence != -1)
setState(MantleState);
continue;
}
}
}EDIT:I can confirm that it works in multiplayer.

Associate David Montgomery-Blake
David MontgomeryBlake