Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

WIP: Classic FPS Controller

Discussion in 'Works In Progress - Archive' started by PhobicGunner, Apr 8, 2015.

  1. PhobicGunner

    PhobicGunner

    Joined:
    Jun 28, 2011
    Posts:
    1,813
    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 FPS Controller/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.
     
    Razputin likes this.
  2. 420BlazeIt

    420BlazeIt

    Joined:
    Aug 14, 2014
    Posts:
    102
    Is this on the asset store yet?
     
  3. PhobicGunner

    PhobicGunner

    Joined:
    Jun 28, 2011
    Posts:
    1,813
    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.
     
  4. 420BlazeIt

    420BlazeIt

    Joined:
    Aug 14, 2014
    Posts:
    102
    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?
     
  5. PhobicGunner

    PhobicGunner

    Joined:
    Jun 28, 2011
    Posts:
    1,813
    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:
    Code (csharp):
    1.  
    2. // apply input acceleration to our velocity
    3. // wishDir is the normalized direction we want to move in
    4. // wishSpeed is the speed we want to move in that direction
    5. // accel is how fast we want to accelerate towards wishSpeed
    6. protected void applyAcceleration( Vector3 wishDir, float wishSpeed, float accel )
    7. {
    8.     // first, we store the current velocity along Y axis, before applying acceleration.
    9.     float y = velocity.y;
    10.  
    11.     float addSpeed, accelSpeed, currentSpeed;
    12.  
    13.     // first, get flat velocity (negating Y component)
    14.     Vector3 flatVel = velocity;
    15.     flatVel.y = 0f;
    16.  
    17.     // find out how fast we are already moving in the desired direction (this can be done using dot product)
    18.     currentSpeed = Vector3.Dot( flatVel, wishDir );
    19.  
    20.     // get the difference between our current speed and our desired speed
    21.     addSpeed = wishSpeed - currentSpeed;
    22.  
    23.     // ignore any acceleration that would *reduce* speed instead of increasing it
    24.     if( addSpeed <= 0 )
    25.         return;
    26.  
    27.     // accelSpeed is the speed we want to move at multiplied by acceleration factor
    28.     accelSpeed = accel * wishSpeed;
    29.  
    30.     // if that exceeds the delta between current and desired speed, clamp it (so that we don't over-accelerate)
    31.     if( accelSpeed > addSpeed )
    32.     {
    33.         accelSpeed = addSpeed;
    34.     }
    35.  
    36.     // now add it to velocity
    37.     velocity += accelSpeed * wishDir;
    38.  
    39.     // and restore what the Y velocity was prior to adding acceleration
    40.     velocity.y = y;
    41. }
    42.  
    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)

    Code (csharp):
    1.  
    2. // apply friction to our velocity
    3. // vel is the current velocity which we will be applying friction to
    4. // drop is how much to reduce speed each frame
    5. protected Vector3 applyFriction( Vector3 vel, float drop )
    6. {
    7.     // store Y velocity prior to applying friction
    8.     float y = vel.y;
    9.  
    10.     // negate Y velocity
    11.     vel.y = 0f;
    12.  
    13.     // get the speed we're currently moving at in the X-Z plane
    14.     float speed = vel.magnitude;
    15.  
    16.     // subtract a value from speed
    17.     float newSpeed = speed - drop;
    18.  
    19.     // clamp speed to 0
    20.     if( newSpeed < 0f )
    21.         newSpeed = 0f;
    22.      
    23.     // later we're actually going to be multiplying the old velocity by this value
    24.     // this is just a shortcut to normalizing the vector, this way I only have to divide one value instead of dividing
    25.     // all three X, Y, and Z components of the vector (which is what Normalize does internally)
    26.     // basically, highscool algebra / rearranging terms ;)
    27.     newSpeed /= speed;
    28.  
    29.     // if newSpeed is 0 the result will now be NaN so we need to handle that case
    30.     if( float.IsNaN( newSpeed ) )
    31.     {
    32.         return Vector3.up * y;
    33.     }
    34.  
    35.     // multiply the velocity by the new speed value to set the speed of the vector
    36.     Vector3 ret = vel * newSpeed;
    37.  
    38.     // restore the Y value (we don't want friction affecting Y velocity)
    39.     ret.y = y;
    40.  
    41.     // return new velocity value
    42.     return ret;
    43. }
    44.  
    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).
     
    RavenOfCode likes this.
  6. 420BlazeIt

    420BlazeIt

    Joined:
    Aug 14, 2014
    Posts:
    102
    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.
     
  7. RavenOfCode

    RavenOfCode

    Joined:
    Apr 5, 2015
    Posts:
    869
    I always wondered how those weird physics worked... Thanks for the knowledge. ;)
     
  8. 420BlazeIt

    420BlazeIt

    Joined:
    Aug 14, 2014
    Posts:
    102
    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 :) )



    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?
     
  9. PhobicGunner

    PhobicGunner

    Joined:
    Jun 28, 2011
    Posts:
    1,813
    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 (tm). 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.
     
  10. 420BlazeIt

    420BlazeIt

    Joined:
    Aug 14, 2014
    Posts:
    102
    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 :D
     
    Last edited: Dec 31, 2016
  11. PhobicGunner

    PhobicGunner

    Joined:
    Jun 28, 2011
    Posts:
    1,813
    Pretty much. If you wanted a simple linear falloff for force it'd be something like:

    Code (csharp):
    1.  
    2. /// <summary>
    3. /// Add an explosion force to this character
    4. /// </summary>
    5. void AddExplosionForce( Vector3 explosionPos, float explosionRadius, float explosionForce )
    6. {
    7.     Vector3 explosionVec = transform.position - explosionPos; // vector from explosion to our center
    8.     float distance = explosionVec.magnitude; // get distance to explosion
    9.  
    10.     if( distance > explosionRadius ) return; // we're outside the explosion's radius, ignore it
    11.  
    12.     float forceMult = 1f - ( distance / explosionRadius ); // 1.0 at explosion center, 0.0 at explosion radius
    13.  
    14.     explosionVec /= distance; // normalize the vector
    15.     this.velocity += explosionVec * explosionForce * forceMult; // add explosion impulse to velocity
    16. }
    17.