This is a roundup of Unity C# first person controllers. A controller is the code that makes your player move based on keyboard/gamepad/mouse input. For now, only free options are included.
This is a good starting place for your own controller, as everything is simple and well labeled in a single script file. It will need some work, however, as the actual control has problems.
Horizontal movement has a major issue: it's easy to get stuck on a wall just by walking at it on a 45 degree angle (this also happens when jumping, meaning you can cling to walls). Jumping has major issues too - if you push against a wall and try to jump it won't let you. Jumping when moving downhill isn't possible, either. Steps are hard, but not impossible; the step size just has to be very short, and this is not tunable. It uses a Rigidbody to do most of the work, so the problems can probably be fixed by changing how that is configured.
Here's the jumping code:
// Gets input and calls jump method && isGrounded
if(enableJump && Input.GetKeyDown(jumpKey) && isGrounded)
{
Jump();
}
// Sets isGrounded based on a raycast sent straight down from the player object
private void CheckGround()
{
Vector3 origin = new Vector3(transform.position.x, transform.position.y - (transform.localScale.y * .5f), transform.position.z);
Vector3 direction = transform.TransformDirection(Vector3.down);
float distance = .75f;
if (Physics.Raycast(origin, direction, out RaycastHit hit, distance))
{
Debug.DrawRay(origin, direction * distance, Color.red);
isGrounded = true;
}
else
isGrounded = false;
}
you can help (but not fix completely) the issue with not being able to jump going downhill by increasing the distance value, as I have already done above.
private void Jump()
{
// Adds force to the player rigidbody to jump
if (isGrounded)
{
rb.AddForce(0f, jumpPower, 0f, ForceMode.Impulse);
isGrounded = false;
}
// When crouched and using toggle system, will uncrouch for a jump
if(isCrouched && !holdToCrouch)
{
Crouch();
}
}
It uses the legacy InputManager meaning only mouse+keyboard support out of the box, but this is fixable.
First Person V2.1.0 (new project from built-in template)
You get this from the Unity Hub instead of the asset store. It uses the new input system, and as such is much more complex than the previous example. But with that complexity comes controller + mouse + keyboard support. Movement is smooth, with no catching on walls, and no more problems with jumping going down hill. No issues with walking into the ceiling pushing you thru the floor, either. Unfortunately when a jump hits a ceiling it behaves quite weird - either you get stuck against it for a couple frames and then drop normally, or you slowly slide along them and then drop.
The mouse + keyboard support feels well tuned, but the gamepad input is super twitchy. The new input system spreads things out over a lot of different files and UIs, so it's far from obvious how to fix this, but I did find the following hack that works ok: change line 138 of FirstPersonController.cs to
float deltaTimeMultiplier = IsCurrentDeviceMouse ? 1.0f : Time.deltaTime/3;
Also, movement in the air is very floaty (basically the same as moving on the ground) which makes any kind of platforming very hard. The code for the controller is very short, but doesn't really explain its logic, eg. here's the jump code:
private void JumpAndGravity()
{
if (Grounded)
{
// reset the fall timeout timer
_fallTimeoutDelta = FallTimeout;
// stop our velocity dropping infinitely when grounded
if (_verticalVelocity < 0.0f)
_verticalVelocity = -2f;
// Jump
if (_input.jump && _jumpTimeoutDelta <= 0.0f)
// the square root of H * -2 * G = how much velocity needed to reach desired height
_verticalVelocity = Mathf.Sqrt(JumpHeight * -2f * Gravity);
// jump timeout
if (_jumpTimeoutDelta >= 0.0f)
_jumpTimeoutDelta -= Time.deltaTime;
}
else
{
// reset the jump timeout timer
_jumpTimeoutDelta = JumpTimeout;
// fall timeout
if (_fallTimeoutDelta >= 0.0f)
_fallTimeoutDelta -= Time.deltaTime;
// if we are not grounded, do not jump
_input.jump = false;
}
// apply gravity over time if under terminal (multiply by delta time twice to linearly speed up over time)
if (_verticalVelocity < _terminalVelocity)
_verticalVelocity += Gravity * Time.deltaTime;
}
Seems to be nearly identical to the controller you get from the built-in templet described above, despite the very different version number (1.2 vs 2.1).
Maybe useful as a starting point, but too broken for anything beyond that.
Movement is nice and smooth. If you walk into a wall you glide along it as you would expect, not getting stuck, and you can't slip thru narrow gaps. You can glide up steps if they are not too steep, which seems to come from the fact that a capsule collider is used, which means that it's not particularly tunable. Also, if you stand close enough to an edge (so that your capsule's lowest point is over open space) you start to drift over the edge. At least when standing squarely on a slope there's no problem with slipping down, and you can jump when walking down a slope.
If you jump while moving toward a wall you get stuck on it, almost like wall jumping. Indeed, if you jump into the air and then push against a wall you stick to it, slowly drifting down. That could be a fun mechanic for some games if it were optional. Worse, however, pushing against any kind of surface, be it a wall or an overly high step, prevents you from jumping at all.
At least jumping and hitting your head works as expected - you immediately bounce back downwards. With so much broken (and with most of the behavior stemming from using a rigid body) I'm not sure it's worth showing the fairly minimal source. Here's a couple of very familiar lines, though:
bool isGroundedNow = Physics.Raycast(RaycastOrigin, Vector3.down, distanceThreshold * 2);
// Jump when the Jump button is pressed and we are on the ground.
if (Input.GetButtonDown("Jump") && (!groundCheck || groundCheck.isGrounded))
{
rigidbody.AddForce(Vector3.up * 100 * jumpStrength);
Jumped?.Invoke();
}
It uses the legacy InputManager meaning only mouse+keyboard support out of the box, but this is fixable.
The name is misleading; there's no support for shooting/aiming/etc. Aside from a grappling hook, this is just a standard first person controller that also features wall running. While the code is nice and simple making it potentially useful as a starting place, it has major issues. Moving toward a wall while in the air causes you to stick to it indefinitely, so unless you desire that mechanic, this is not for you. And to be clear, that's not how wall running works, that's just how the rigidbody behaves for all vertical surfaces.
At least you can still jump when moving toward a wall. And you don't slide down on slopes when standing still.
Here's how jumping works:
if (isGrounded)
{
// Jump
if (Input.GetButton("Jump") && !jumpBlocked)
{
rb.AddForce(-jumpForce * rb.mass * Vector3.down);
jumpBlocked = true;
Invoke("UnblockJump", jumpCooldown);
}
// Ground controller
rb.velocity = Vector3.Lerp(rb.velocity, inputForce, changeInStageSpeed * Time.fixedDeltaTime);
}
else
// Air control
rb.velocity = ClampSqrMag(rb.velocity + inputForce * Time.fixedDeltaTime, rb.velocity.sqrMagnitude);
Maybe useful as a starting point, but too broken for anything beyond that.
The major thing this brings is a HUD (health bar, etc) and camera shake when you land. Neither are great; the HUD often clips into geometry and half-disappears. The camera shake is really neat and effective AT FIRST, and then you'll be shouting how do I turn this effect off?!
Movement is fairly nice and smooth. If you walk into a wall you glide along it as you would expect, not getting stuck, and you can't slip thru narrow gaps. Occasionally though, you bounce off a little, which feels like a glitch. You can glide up steps if they are not too steep, which seems to come from the fact that a capsule collider is used, which means that it's not particularly tunable. Unfortunately, rather than climbing up a step, it's more like a little "jump" and thus you usually get the camera shake effect after each step. Also, if you stand close enough to an edge (so that your capsule's lowest point is over open space) you start to drift over the edge.
Slopes are problematic. You start to drift downwards whenever standing on slope of any magnitude. If it's moderately steep and you start walking down you actually move forward a little and then drop, causing another camera shake. At least you can jump while walking down a slope.
Hitting your head mostly works as expected - you do get pushed back a little if it's sloped at all, but the effect is mild and somewhat realistic (assuming your head is made of rubber). Most of the bounce is directed down. And if you walk into a sloped ceiling it doesn't push you thru the floor.
Much of this behavior stems from the use of a rigidbody, but the code still manages to be complex, and not particularly well layed out or commented.
public virtual void Accelerate () {
LookingForGround ();
MoveTorwardsAcceleration ();
if (!isMovementAvailable) return;
if (!rb) return;
if (System.Math.Abs(movementInputValues.x) < epsilon & System.Math.Abs(movementInputValues.y) < epsilon) return;
if (!isAirControl) {
if (!isGrounded) return;
}
if (rb.velocity.magnitude > 1.0f) {
rb.interpolation = RigidbodyInterpolation.Extrapolate;
}
else {
rb.interpolation = RigidbodyInterpolation.Interpolate;
}
delta = new Vector3 (movementInputValues.x, 0, movementInputValues.y);
delta = Vector3.ClampMagnitude (delta, 1);
delta = rb.transform.TransformDirection (delta) * currentAcceleration;
vector3_Target = new Vector3 (delta.x, rb.velocity.y, delta.z);
rb.velocity = Vector3.SmoothDamp (rb.velocity, vector3_Target, ref vector3_Reference, Time.smoothDeltaTime * movementSmoothing);
}
It uses the
legacy InputManager meaning only mouse+keyboard support out of the box, but this is fixable.