WIP: Classic FPS Controller

Hello all,
I am currently working on getting a new FPS character controller ready for the Asset Store.
This FPS controller is designed to make your game feel like the classics such as Quake, and derivatives such as Half Life or Counter Strike. In fact, a lot of code was directly ported from the Quake 3 GPL source code.

This comes with all of the fun physics quirks players love about these games, such as circle/strafe jumping and rocket jumping.

I have a demo WebGL build here:

https://dl.dropboxusercontent.com/u/99106620/Classic%20FPS%20Controller/index.html

You can try it yourself - watch your speed climb as you circle jump across the arena (movement speed is 8, but with circle jumping you can accelerate up to 20 or even higher). Additionally, you can click to spawn an “explosion” (it’s invisible, but it adds an explosion force from the point you clicked on), which is needed to get onto the higher ledge in the arena.

1 Like

Is this on the asset store yet?

No. Honestly, I didn’t think there was much interest in it actually so it was never released. Maybe after my current project is released I’ll take another look at getting this released as a package.

Thanks for replying. Really all I am looking for is the controller code so I can have air strafing in my project. Is it using a Rigidbody? If you still have the project files could you put the controller script on Pastebin please?

Well, if I ever want to release this on the asset store, I’m of course a bit hesitant to post any source code for free (as that would sort of defeat the purpose of putting it on the asset store).

That said, I can give a general crash course of sorts about how the physics in Quake worked and how my code did things.
For one, I used Character Controller. Since this is a highly customized physics routine, rigidbodies just won’t cut it (I also did this in order to have control over exactly when character physics are updated, so that they can be integrated into a client-side prediction system for network games).

Generally, there are two parts to the physics routine: applying acceleration, and applying friction.

First, applying acceleration. This is what my accelerate function looks like:

// apply input acceleration to our velocity
// wishDir is the normalized direction we want to move in
// wishSpeed is the speed we want to move in that direction
// accel is how fast we want to accelerate towards wishSpeed
protected void applyAcceleration( Vector3 wishDir, float wishSpeed, float accel )
{
    // first, we store the current velocity along Y axis, before applying acceleration.
    float y = velocity.y;

    float addSpeed, accelSpeed, currentSpeed;

    // first, get flat velocity (negating Y component)
    Vector3 flatVel = velocity;
    flatVel.y = 0f;
  
    // find out how fast we are already moving in the desired direction (this can be done using dot product)
    currentSpeed = Vector3.Dot( flatVel, wishDir );
  
    // get the difference between our current speed and our desired speed
    addSpeed = wishSpeed - currentSpeed;
  
    // ignore any acceleration that would *reduce* speed instead of increasing it
    if( addSpeed <= 0 )
        return;

    // accelSpeed is the speed we want to move at multiplied by acceleration factor
    accelSpeed = accel * wishSpeed;
  
    // if that exceeds the delta between current and desired speed, clamp it (so that we don't over-accelerate)
    if( accelSpeed > addSpeed )
    {
        accelSpeed = addSpeed;
    }

    // now add it to velocity
    velocity += accelSpeed * wishDir;
  
    // and restore what the Y velocity was prior to adding acceleration
    velocity.y = y;
}

The dot product there is where a lot of the magic happens, it’s what allows strafe jumping to occur because it only considers velocity in the desired direction (if velocity is perpendicular to the desired input direction, that value becomes 0 and an acceleration is applied, net result is that velocity actually increases).

Next is friction, because we definitely want the player to come to a stop when on solid ground :wink:
Friction is actually super simple. In a nutshell, you just subtract a friction value from velocity speed every frame, that friction term depends on whether you’re in air or on ground (or even what surface you’re on, if you want to support slippery surfaces for example). Higher = faster to stop, lower = slower to stop (in air, you will probably set this to zero because air friction is negligible)

// apply friction to our velocity
// vel is the current velocity which we will be applying friction to
// drop is how much to reduce speed each frame
protected Vector3 applyFriction( Vector3 vel, float drop )
{
    // store Y velocity prior to applying friction
    float y = vel.y;
  
    // negate Y velocity
    vel.y = 0f;

    // get the speed we're currently moving at in the X-Z plane
    float speed = vel.magnitude;
  
    // subtract a value from speed
    float newSpeed = speed - drop;
  
    // clamp speed to 0
    if( newSpeed < 0f )
        newSpeed = 0f;
      
    // later we're actually going to be multiplying the old velocity by this value
    // this is just a shortcut to normalizing the vector, this way I only have to divide one value instead of dividing
    // all three X, Y, and Z components of the vector (which is what Normalize does internally)
    // basically, highscool algebra / rearranging terms ;)
    newSpeed /= speed;
  
    // if newSpeed is 0 the result will now be NaN so we need to handle that case
    if( float.IsNaN( newSpeed ) )
    {
        return Vector3.up * y;
    }

    // multiply the velocity by the new speed value to set the speed of the vector
    Vector3 ret = vel * newSpeed;
  
    // restore the Y value (we don't want friction affecting Y velocity)
    ret.y = y;
  
    // return new velocity value
    return ret;
}

So, that in a nutshell is the core of how Quake’s character physics work and is the reason strafe and rocket jumping work among other tricks used by veteran players. There’s a lot more that has to be built on this of course, which if I polish this up for an asset store package would all be implemented for you (moving platforms, crouching, colliding with rigidbodies, etc).

1 Like

Thanks for this post but I really have no idea how to implement any of this into a working character controller script. The only way I’m ever gonna get this to work is if you release to the asset store. So please pick back up this project. I really need it xD.

I always wondered how those weird physics worked… Thanks for the knowledge. :wink:

Yo dude quick question just because you mentioned you did this with CharacterController.
(By the way I did eventually figure out how to make Quake style movement in my game :slight_smile: )

Anyway my question is this: How did you get rocket jumping to work with a character controller?
also did you ever manage to fix the edge sinking problem that comes with CharacterController because it forces a capsule collider?

1.) Actually, this applies to any character controller code: if your controller code is based around the concept of momentum and acceleration (not just directly setting velocity anywhere, but applying acceleration & friction instead), rocket jumping pretty much just works ™. All you do is apply an impulse to your velocity vector based on distance to explosion & explosion force (that is, calculate a force to apply and then just add that to velocity). Actually, jumping (should) work exactly the same way. The rest of the character code will handle it pretty much automatically.

2.) Nope. You don’t really fix that, you just deal with it TBH.

Thanks for the answer man and yeah I’m learning to live with the edge sinking. So basically you just add the vector of the explosion, multiplied by force to the velocity?

Edit I figured it out :smile:

Pretty much. If you wanted a simple linear falloff for force it’d be something like:

/// <summary>
/// Add an explosion force to this character
/// </summary>
void AddExplosionForce( Vector3 explosionPos, float explosionRadius, float explosionForce )
{
    Vector3 explosionVec = transform.position - explosionPos; // vector from explosion to our center
    float distance = explosionVec.magnitude; // get distance to explosion

    if( distance > explosionRadius ) return; // we're outside the explosion's radius, ignore it

    float forceMult = 1f - ( distance / explosionRadius ); // 1.0 at explosion center, 0.0 at explosion radius

    explosionVec /= distance; // normalize the vector
    this.velocity += explosionVec * explosionForce * forceMult; // add explosion impulse to velocity
}