Basic First-Person Character Controller in Unity (Abridged)

About: I pretty much just tinker for fun, but I'm always happy to learn something new and share it with people!


This instructable contains only the steps for this project with minimal explanations. You can find the version with full explanations on my profile that ends with "(Full Explanations)."


The purpose of this project is to help overcome the most humbling of hurdles in game development: the character controller. I will be guiding you on how to make a simple first-person character controller that provides basic movement and the ability to jump.

This project assumes you:

  • Have Unity and Visual Studio installed.
  • Have some basic knowledge regarding both Unity and programming
  • Are already working on a project.

Teacher Notes

Teachers! Did you use this instructable in your classroom?
Add a Teacher Note to share how you incorporated it into your lesson.

Step 1: Creating the Player Object

If you don't already have a model to work with, create a new Capsule object.

Parent the Main Camera to your object and set its transform to (0, 0.75, 0), or wherever you need to look from on your model. Next, add a Character Controller component to your object and modify the capsule to collider to fit your model. If you are using a capsule, it should already fit the model.

Lastly, create a new C# script and call it something like "PlayerController" (or whatever fits your naming convention).

Step 2: Adding the Global Variables

The first two variables you'll need are handles for a CharacterController and a Camera. You can make them public or just leave them private, but serializing them makes linking them to their objects easier.

private Camera camera;
private CharacterController characterController;

You'll need to save the script and drag the correct components/objects into the fields in the Inspector window before the rest of the code will function.

Next, you'll need two float variables to store our mouse input.

private float mouseX;
private float mouseY;

This next variable is optional but makes a great addition to the program by adding a slider to adjust look sensitivity.

[Range(0.0f, 5.0f)]
public float lookSensitivity = 1.0f;

Next, add the three float variables that will be used for movement, serialize them as you see fit.

private float moveSpeed = 5.0f;
private float jumpForce = 5.0f;
// Force of gravity will be Earth's gravity by default
private float gravForce = 9.801f;

The last thing you'll need is a Vector3 to hold the movement vector.

private Vector3 moveDirection;

Step 3: The Methods

I've isolated the code into two methods for movement and rotation, but you can combine them if you wish. Add a private void Movement and private void Rotate method.

private void Movement()

private void Rotate()

Remember to add method calls in the Update method.

void Update()

Step 4: The Rotate Method

We'll first add the code for our Rotate method. That way, your movement will be more intuitive.

Start by receiving mouse input and adding it to the mouse.

mouseX += Input.GetAxisRaw("Mouse X") * lookSensitivity;
mouseY += Input.GetAxisRaw("Mouse Y") * lookSensitivity;

This code adds new mouse input to our mouseX and mouseY floats as a product of our look sensitivity.

You'll want to add the following line of code if you would prefer that you couldn't rotate your camera backward indefinitely.

mouseY = Mathf.Clamp(mouseY, -90.0f, 90.0f);

This line keeps the angle at which your camera rotates between -90 and +90 degrees.

The next line will rotate your object left or right, depending on mouseX's value.

transform.localRotation = Quaternion.Euler(Vector3.up * mouseX);

This line is mainly with the capsule in mind because it looks the same regardless of the y-axis rotation. If you are using a player model, you may have to add additional code to animate your model turning.

Next is the code for camera movement.

camera.transform.localRotation = Quaternion.Euler(Vector3.left * mouseY);

This code sets our camera's x-axis rotation equal to our clamped float mouseY.

Step 5: The Movement Method: User Input

The following code will only occur if you are grounded to make it so you can only change direction if you are connected with the ground. This makes movement a little more realistic.

if (m_CharacterController.isGrounded)

Everything else will be placed inside this if statement.

Vector3 forwardMovement = transform.forward * Input.GetAxisRaw("Vertical");

This line creates a new Vector3 and gives it a vector pointing either forward or backward (based on input from the w, s, up, or down keys) along the object's local rotation. This compliments the rotation your mouse movement modifies, so "forward" is wherever you're facing.

Vector3 strafeMovement = transform.right * Input.GetAxisRaw("Horizontal");

This line is similar to the previous one, but instead stores a vector pointing left or right (based on the a, s, left, or right keys). This vector is also based on local rotation.

Vector3 moveDirection = (forwardMovement + strafeMovement).normalized * moveSpeed;

This line adds our previous to vectors into one and normalizes it (modifies it to have a magnitude of 1). It then multiplies the new vector by our moveSpeed float, allowing us to control our maximum speed easily.

if (Input.GetKeyDown(KeyCode.Space))
    moveDirection.y = jumpForce;

This last bit adds jump functionality by checking for input on the spacebar. When activated, it sets our final vector's y-value equal to our jumpForce float. The math behind calculating how high you will jump is pretty tricky, so you'll want to play-test this value until you find what you like.

Step 6: The Movement Method: Gravity & Movement

The following code will go below the if statement and is what simulates gravity for our player.

moveDirection.y -= gravForce * Time.deltaTime;

This code will gradually decrease our vertical speed, which makes the player "fall" when not touching the ground. I would recommend you keep this line outside of the if statement. While putting it in the optional else block of the if statement sounds logical, you'll need to add more code to counteract a few bugs that can occur. Time.deltaTime is the time it takes for your computer to render, allowing you to achieve the same movement on a system running at 30 fps as a system running at 120 fps. Gravity is only a vector in this line, but will be made into an acceleration (as it should be) in the next line after getting multiplied by another Time.deltaTime.

characterController.Move(moveDirection * Time.deltaTime);

This last line is what makes your object move, asides from gravity.

Step 7: Optional: Lock the Cursor to the Project When It Runs

An optional and recommended step is to add the following line inside the Start method:

Cursor.lockState = CursorLockMode.Locked;

This code allows you to play-test without your cursor moving all over the screen and is something you should add for your final build.

Step 8: Final Code

Below is the final code for the project, just in case you had an error you couldn't fix. I would recommend you take a look at the "Full Explanations" version of this Instructable. I put a lot of effort into making it as informative and readable as possible and would love your feedback and criticisms!

using UnityEngine;
public class PlayerController : MonoBehaviour { /* The "m_" in front of a variable is a coding convention that helps you seperate * Global and Local variables at a glance. It stands for "member variable" and * Unity automatically removes the "m_" in the Editor so that they don't look weird * in the Inspector! */

// Handles [Header("Handles")] [SerializeField] private Camera m_Camera; [SerializeField] private CharacterController m_CharacterController;

// Movement speeds [Header("Speed Variables")] [SerializeField] private float m_MoveSpeed = 5.0f; [SerializeField] private float m_JumpForce = 5.0f; [SerializeField] private float m_GravityForce = 9.807f;

// Look sensitivity variable [Range(0.0f, 5.0f)] public float m_LookSensitivity = 1.0f;

[Header("Debugging Variables")] /*Store rotation values from mouse input*/ [SerializeField] private float m_MouseX; [SerializeField] private float m_MouseY;

[SerializeField] private Vector3 m_MoveDirection;

void Start() { Cursor.lockState = CursorLockMode.Locked; }

void Update() { Rotate(); Movement(); }

private void Rotate() { // Receive mouse input and modifies m_MouseX += Input.GetAxisRaw("Mouse X") * m_LookSensitivity; m_MouseY += Input.GetAxisRaw("Mouse Y") * m_LookSensitivity;

// Keep mouseY between -90 and +90 m_MouseY = Mathf.Clamp(m_MouseY, -90.0f, 90.0f);

// Rotate the player object around the y-axis transform.localRotation = Quaternion.Euler(Vector3.up * m_MouseX); // Rotate the camera around the x-axis m_Camera.transform.localRotation = Quaternion.Euler(Vector3.left * m_MouseY); }

private void Movement() { // If the player is touching the ground if (m_CharacterController.isGrounded) { // Receive user input for movement Vector3 forwardMovement = transform.forward * Input.GetAxisRaw("Vertical"); Vector3 strafeMovement = transform.right * Input.GetAxisRaw("Horizontal"); // Convert Input into a Vector3 m_MoveDirection = (forwardMovement + strafeMovement).normalized * m_MoveSpeed;

// If user presses the "jump" button if (Input.GetKeyDown(KeyCode.Space)) m_MoveDirection.y = m_JumpForce; // Jump }

// Calculate gravity and modify movement vector as such m_MoveDirection.y -= m_GravityForce * Time.deltaTime;

// Move the player using the movement vector m_CharacterController.Move(m_MoveDirection * Time.deltaTime); } }

Made with Math Contest

This is an entry in the
Made with Math Contest

Be the First to Share


    • Instrument Contest

      Instrument Contest
    • Make it Glow Contest

      Make it Glow Contest
    • STEM Contest

      STEM Contest