8
\$\begingroup\$

As a Defrag player, I would like to add advanced movements to my (simple) game, in particular strafe jumping. I currently have basic fps movements (WASD and free look to be clear), but how can I add quake-like movement techniques? This article explains very well the theory behind it, but I really don't know where to start to actually implement it.

\$\endgroup\$
2
  • 2
    \$\begingroup\$ You want to implement a bug into your game? \$\endgroup\$
    – API-Beast
    Dec 15, 2012 at 20:41
  • 3
    \$\begingroup\$ But what a bug it is! \$\endgroup\$ Dec 15, 2012 at 23:08

2 Answers 2

5
\$\begingroup\$

The article you linked to first explains how the game works and then discusses at length how to exploit the way it works to strafe-jump. I believe only a few paragraphs are useful to you: the one about how the engine decides whether to add an acceleration vector times a small amount of time (which amounts to a small variation of the speed vector):

So when we try to apply an acceleration at some angle to the velocity, we will be allowed to accelerate provided that the component of our current velocity in the proposed direction is less than 320ups.

and how it truncates that small variation of speed so the component of the resulting speed vector along the acceleration direction doesn't exceed a predefined maximum:

If your v.cos(delta) is less than 320 but still very close to 320, applying the usual constant acceleration for a single frame would be enough to take you over 320ups in that direction. This is something the engine checks for, and if the situation exists, the applied acceleration will be only enough to take you up to the 320ups limit (so it would be 320 - v.cos(theta) ), and not beyond.

Just to spare you some confusion when actually implementing the thing, note that the author refers to the vector "a" as the "acceleration vector" but throughout the article it is used sometimes as a unit vector having the same direction as the acceleration vector as in

v.a = v.cos(delta)

or as an amount of speed as in

arccos( (320-a)/v )

or even as a distance (length) as in

a = s.T

where "s" is the global maximum speed (the g_speed variable in the engine) and T is the duration of a frame. So be careful when handling such quirky maths. Here's a rundown to make everything clear:

To sum up in physics/applied maths notation: the player has a speed vector v(t) at time t and we want to compute his speed vector v(t+dt) at time t + dt where dt is a small amount of time. Here, dt will be the duration of a frame, or 1/(framerate). There exists a speed constant V used to cap other speeds or something along those lines, and we will use that constant.

We know v(t) and we can deduce the direction u towards which the player wants to move using the currently pressed movement keys. u is a unit vector. The amount of speed we initially want to add to v(t) is A.dt.u where A, a real number, is the unique, global movement acceleration constant, so that v(t+dt) = v(t) + A.dt.u.

Note that A is not a speed, but an acceleration! The engine uses the same variable g_speed for the global acceleration induced by pressing movement keys AND for the maximum speed, because it simplifies the handling of movement when on the ground, hence the confusion we saw earlier.

We want to add that amount of speed A.dt.u, but we need to truncate it if it makes the component of the resulting vector v(t+dt) bigger than V along u. Therefore, we get the following pseudocode:

// what we want to compute
Vector3D nextSpeed;
// the acceleration direction u, which is a unit vector
Vector3D accelDir = computeAccelDir(pressedkeys);
// our dot product, "v.cos(delta)"
float vcosdelta = currentSpeed.dotProduct(accelDir);
// the total speed along the acceleration direction u (unless we truncate it)
float totalSpeedComponent = vcosdelta + A*dt;
// is it small enough?
if (totalSpeedComponent < V)
{
    nextSpeed = currentSpeed.add(accelDir.times(A*dt));
} else { // no! it's too big! We must truncate -_-'
    nextSpeed = currentSpeed.add(accelDir.times(V - vcosdelta));
}

Sorry for the poor variable names. Please have better variable naming habits than mine. Here, I compute and store float totalSpeedComponent even though I could directly use V - vcosdelta - A*dt in the first if statement, but that was for understandability.

I hope I didn't miss anything. Remember you can customize both A (the global movement-induced acceleration) and V (the maximum speed along acceleration direction).

That being said, just translate that and adapt it to your codebase, I think I got most of it right.

major edit: I forgot to add that this only works in the air! Don't forget to make it so that, if the player lands with the jump key pressed, he only touches the ground for 1 frame at most before he jumps again! Special handling of physics might be required during that particular frame. It's 6AM. Bed.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks for your answer, I'll try it as soon as possible. \$\endgroup\$
    – matt
    Dec 16, 2012 at 9:36
  • \$\begingroup\$ Reading this also helps. \$\endgroup\$
    – matt
    Dec 16, 2012 at 16:12
2
\$\begingroup\$

Actually, I think driven by the question in this post, Adrian "Flafla2" Biagioli has created a very well written and explained post/answer on this same topic.

Here: http://flafla2.github.io/2015/02/14/bunnyhop.html

The product of the post is a C# implementation of the system:

// accelDir: normalized direction that the player has requested to move (taking into account the movement keys and look direction)
// prevVelocity: The current velocity of the player, before any additional calculations
// accelerate: The server-defined player acceleration value
// max_velocity: The server-defined maximum player velocity (this is not strictly adhered to due to strafejumping)
private Vector3 Accelerate(Vector3 accelDir, Vector3 prevVelocity, float accelerate, float max_velocity)
{
    float projVel = Vector3.Dot(prevVelocity, accelDir); // Vector projection of Current velocity onto accelDir.
    float accelVel = accelerate * Time.fixedDeltaTime; // Accelerated velocity in direction of movment

    // If necessary, truncate the accelerated velocity so the vector projection does not exceed max_velocity
    if(projVel + accelVel > max_velocity)
        accelVel = max_velocity - projVel;

    return prevVelocity + accelDir * accelVel;
}

private Vector3 MoveGround(Vector3 accelDir, Vector3 prevVelocity)
{
    // Apply Friction
    float speed = prevVelocity.magnitude;
    if (speed != 0) // To avoid divide by zero errors
    {
        float drop = speed * friction * Time.fixedDeltaTime;
        prevVelocity *= Mathf.Max(speed - drop, 0) / speed; // Scale the velocity based on friction.
    }

    // ground_accelerate and max_velocity_ground are server-defined movement variables
    return Accelerate(accelDir, prevVelocity, ground_accelerate, max_velocity_ground);
}

private Vector3 MoveAir(Vector3 accelDir, Vector3 prevVelocity)
{
    // air_accelerate and max_velocity_air are server-defined movement variables
    return Accelerate(accelDir, prevVelocity, air_accelerate, max_velocity_air);
}

All credits to Adrian.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .