RSS

Rampsliding Is a Quake Engine Quirk in the Same Way That Bunnyhopping Is

- Games

In Quake and derivative engines (like Half-Life 1 & 2), it is possible to slide up sloped surfaces without losing much speed. This is a major gameplay component of games like QuakeWorld Team Fortress, Team Fortress Classic, and Fortress Forever, where maintaining momentum from large speed bursts is fundamental.

Rampsliding in Team Fortress Classic from the video notsofantastic

The obvious assumption would be that this is an intentional feature that uses things like the slope of the surface and the player’s velocity to determine when a player is rampsliding, but that is not the case. In fact, in the same way that bunnyhopping was likely an unintentional quirk of the ‘air acceleration’ code, rampsliding was likely an unintentional quirk of the ‘categorize position’ code.

In Quake’s PM_CatagorizePosition [sic] function, we see the following code:

if (pmove.velocity[2] > 180)
{
    onground = -1;
}

That is, if the player is ever moving up at greater than 180 units (velocity index 2 is the vertical component), then the player is automatically considered ‘in the air,’ and this overrides all other ‘on ground’ checks. With this, if a player is colliding with a ramp such that their velocity along the ramp has a large enough vertical component, then they are considered in the air, and thus ground friction is simply not applied (specifically, PM_AirMove is called instead of PM_GroundMove).

This creates two emergent conditions for rampsliding:

And these two conditions also interact with eachother (e.g. you can slide a shallower ramp when you’re going faster).

30°
700
606
350
player state: 'in air'
Mouse over the diagram to interact with it

Similar code exists in the Half-Life (GoldSrc) engine:

if (pmove->velocity[2] > 180)   // Shooting up really fast.  Definitely not on ground.
{
    pmove->onground = -1;
}

and in the Half-Life 2 (Source) engine:

// Was on ground, but now suddenly am not
if ( bMovingUpRapidly || 
    ( bMovingUp && player->GetMoveType() == MOVETYPE_LADDER ) )   
{
    SetGroundEntity( NULL );
}

Why would this code exist?

It seems like this code is mostly a catch-all fix to resolve any instance where a player is moved by an external force that should push them off the ground, but that doesn’t directly alter the player’s ‘on ground’ flag–things like explosions, or trigger_push brush entities. This is necessary because the ‘on ground’ and ‘in air’ states are handled very differently: for example, when on the ground, the player’s vertical velocity is set to zero every frame, so things like RPG explosions would otherwise never be able to push a player off the ground.

If there’s no friction, why do you slow down while rampsliding?

When a player collides with a surface, the resulting velocity is determined using a function called PM_ClipVelocity. The following is a simplified version of the ClipVelocity logic:

float backoff = DotProduct(velocity, surfaceNormal);

for (i=0; i<3; i++)
{
    float change = surfaceNormal[i] * backoff;
    velocity[i] = velocity[i] - change;
}

Its job is to make velocity parallel to the surface that is being collided with. If the velocity is not already close to parallel, then non-negligible speed loss can result from ClipVelocity, but once velocity is parallel to the surface, running it through the function again will have no effect. ClipVelocity therefore is responsible for speed loss when first colliding with a slope, and makes it so that the angle of your velocity entering the ramp matters for how much speed you ultimately maintain, but it does not explain speed loss while rampsliding.

30°
495
700
velocity maintained during ClipVelocity: 71%
Speed loss due to ClipVelocity at different approach angles

This is where gravity comes into the picture: because you are considered ‘in the air’ while rampsliding, gravity is applied every frame. This creates a loop that goes like this:

In this loop, ClipVelocity basically serves to redistribute changes in velocity among all of its components.

30°
700
606
350
  • Clip Velocity
  • Move
  • Apply Gravity
Note that with gravity <= 25, velocity's magnitude only changes in the 'Apply Gravity' phase

So, if you are rampsliding on a constant slope, all speed loss is typically due to gravity. If you set gravity to 0, you can rampslide infinitely, and if you set gravity really high, you can only rampslide for a second or two. This makes sense if you think of rampsliding in terms of an object sliding up a completely frictionless slope: the force that will make that object eventually stop and start sliding back down the slope is gravity.

What about surfing (like in Counter-Strike surf maps)?

Surfing comes from a separate but related mechanism: if a surface is steep enough, then the player is always considered ‘in the air’ when colliding with it. The speed gain while surfing comes from two places:

Wrapping up

It’s pretty remarkable to note that almost every movement technique in games like Team Fortress Classic and Fortress Forever was originally accidental:

Even more remarkable is that this phenomenon is actually somewhat common in games, where unintended mechanics become fundamental to the gameplay as we know it today (see things like mutalisk micro in StarCraft, or k-style in GunZ, or even denying creeps in DotA).


Addendum: Landing in front of a ramp instead of directly on it

After playing a game with rampsliding for a while, it becomes clear that if you land on a flat surface right before a ramp instead of directly on the ramp, you will often maintain more speed. This is due to how ClipVelocity works: you can maintain more speed after two calls of ClipVelocity with smaller angle differentials than after a single call with a larger angle differential.

30°
350
700
30°
525
606
700
increased % velocity maintained from landing on flat: 50%
Comparison of landing on the ramp directly vs. landing right in front of the ramp

Note that in the above diagram, the velocity loss that would occur from friction when landing on the ground is not represented (i.e. the diagram is showing ‘perfect’ execution where you land directly in front of the ramp). In reality, the velocity maintained when landing on flat varies depending on how long you slide on the ground before hitting the ramp, since ground friction will be applied during that time.