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
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).