Skip to main content

How to make a first person character controller - Unity

A first-person character controller script is the starting point of any fps shooter game. Even new Unity game developers tend to write their own fps controller script before learning anything else as it is challenging and very exciting. In this article, we gonna create two simple fps controllers from scratch and will guide you step by step on how it works.


fps-controller-unity


First, let's understand the basic concept of an fps controller. You should have a rotating camera that rotates in the x-axis to look up and down and the character should rotate on the y-axis to look around. There are two ways to create an fps controller in the Unity Engine. 
  • Using Character controller component
  • Using the RigidBody component
Both have different advantages and disadvantages when using them. It depends on your game to select the best fps controller according to your needs. 


Character controller based fps controller

Pros:
  • Is grounded check is built-in
  • Has its own methods to move the character
  • Slope angles and step offsets are built-in
Cons :
  • Gravity is not built-in


RigidBody component based fps controller

Pros :
  • Interacts with objects using the physics engine
  • Gravity is built-in

Cons :
  • Slope angles and step offsets should be configured
  • Has to implement a way to check if grounded





Fps Controller using Character controller


Create a capsule and attach a 'CharacterController' component to it. Make sure to remove the capsule collider.
Then set the main camera as a child of the capsule we created, reset the position and move the camera a little bit up to the eye view. Create two scripts for the camera controller and one for the movement controller. Attach the 'CharacterMovementController' to the capsule and the 'CharacterCameraController' to the camera.

CharacterMovementController.cs

using UnityEngine;

public class CharacterMovementController : MonoBehaviour
{
    public CharacterController characterController;

    public float moveSpeed;

    public float sprintSpeedMultiplier = 2f;

    public float jumpHeight = 3f;

    private float _gravity = -10f;

    private float _yAxisVelocity;

    private void Update()
    {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");

        if (Input.GetKey(KeyCode.LeftShift))
            vertical *= sprintSpeedMultiplier;
        
        Vector3 movement = horizontal * moveSpeed * Time.deltaTime * transform.right +
                           vertical * moveSpeed * Time.deltaTime * transform.forward;

        if (characterController.isGrounded)
            _yAxisVelocity = -0.5f;


        if (Input.GetKeyDown(KeyCode.Space))
            _yAxisVelocity = Mathf.Sqrt(jumpHeight * -2f * _gravity);

        _yAxisVelocity += _gravity * Time.deltaTime;
        movement.y = _yAxisVelocity * Time.deltaTime;
        
        characterController.Move(movement);
    }
}



fps-controller-unity



CharacterCameraController.cs


using UnityEngine;

public class CharacterCameraController : MonoBehaviour
{
    public Transform characterBase;
    public float lookSpeed = 10f;

    private float _camRotation;

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

    private void Update()
    {
        float y = Input.GetAxis("Mouse Y");
        float x = Input.GetAxis("Mouse X");
        
        _camRotation -= y * lookSpeed * Time.deltaTime * 10f;
        _camRotation = Mathf.Clamp(_camRotation, -90f, 90f);
        
        transform.localRotation = Quaternion.Euler(_camRotation , 0f ,0f);
        
        characterBase.Rotate(x * lookSpeed * Time.deltaTime * 10f * Vector3.up);
        
        if(Input.GetMouseButton(0))
            Cursor.lockState = CursorLockMode.Locked;
        
        if(Input.GetKeyDown(KeyCode.Escape))
            Cursor.lockState = CursorLockMode.Confined;
    }
}


fps-controller-unity



Make sure to attach all the required components on the inspector window. 'CharacterCameraController' gets the mouse inputs and rotates the camera on the x-axis and rotates the player base capsule on the y-axis. 'lookSpeed' variable acts as the mouse sensitivity and increasing the value results in increased rotation speed. The camera x-axis is clamped between-90 and 90 to make sure that the player is not going to flip the camera upside down. 

The cursor is locked at the start. Pressing the escape key unlocks it and pressing the left mouse button will lock it again.




We have simulated gravity using a variable called '_yAxisVelocity' and we increase it each frame if the player is not grounded. The left shift key multiplies the moving speed by the variable called 'sprintSpeedMultiplier'.

In this fps controller system, you can't detect collision using the normal collision detection methods. Instead, you have to use the 'OnControllerColliderHit' method. The ground check is done using the 'isGrounded' boolean in the 'CharacterController' component.


FPS Controller using RigidBody Component

This method is similar to the 'CharacterController' based fps controller but using the 'RigidBody' component. The game objects are similar to the above one. One capsule and the camera as a child an add a capsule collider and a 'RigidBody' component to the capsule. Create two scripts one for the capsule and one for the camera. 


RigidBodyMovementController.cs


using UnityEngine;

public class RigidBodyMovementController : MonoBehaviour
{
    public Rigidbody rb;

    public float moveSpeed = 35f;

    public float sprintSpeedMultiplier = 1.6f;

    public float jumpForce = 35f;

    public Transform groundCheckTransform;

    public LayerMask groundCheckLayerMask;
    
    private Vector3 _inputVector;

    private bool _isGrounded = true;

    private void Update()
    {
        _inputVector = new Vector3(Input.GetAxis("Horizontal") , 0f , Input.GetAxis("Vertical"));

        if (Input.GetKey(KeyCode.LeftShift))
            _inputVector.z *= sprintSpeedMultiplier;
        
        if (Input.GetKeyDown(KeyCode.Space) && _isGrounded)
        {
            rb.AddForce(jumpForce * 10 * transform.up, ForceMode.Acceleration);
            _isGrounded = false;
        }
    }

    private void FixedUpdate()
    {
        _isGrounded = Physics.CheckSphere(groundCheckTransform.position, 0.3f, groundCheckLayerMask);
        
        Vector3 movement = moveSpeed * 10f * _inputVector.z * Time.fixedDeltaTime * transform.forward +
                        moveSpeed * 10f * _inputVector.x * Time.fixedDeltaTime * transform.right;
        rb.MovePosition(rb.position + movement);
    }
}



fps-controller-unity



RigidBodyCameraController.cs


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RigidBodyCameraController : MonoBehaviour
{
    public Rigidbody characterRb;
    public float lookSpeed = 10f;

    private float _camRotation;

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

    private void Update()
    {
        float y = Input.GetAxis("Mouse Y");
        float x = Input.GetAxis("Mouse X");
        
        _camRotation -= y * lookSpeed * Time.deltaTime * 10f;
        _camRotation = Mathf.Clamp(_camRotation, -90f, 90f);
        
        transform.localRotation = Quaternion.Euler(_camRotation , 0f ,0f);
        
        characterRb.rotation =  Quaternion.Euler(characterRb.rotation.eulerAngles + x * lookSpeed * Time.deltaTime * 10f * Vector3.up);
        
        if(Input.GetMouseButton(0))
            Cursor.lockState = CursorLockMode.Locked;
        
        if(Input.GetKeyDown(KeyCode.Escape))
            Cursor.lockState = CursorLockMode.Confined;
    }
}

fps-controller-unity


fps-controller-unity






The 'RigidBody' component based fps controller works in a similar way as the 'CharacterController' based fps controllers. Instead, we use the 'rigidbody.MovePosition()' method and pass the movement vector to it. The camera rotation is applied in the same way as the above fps controller.

make sure to attach the components on the inspector and turn on the 'RigidBody' constraints for rotation in every axis. Turn on interpolate and play around with the drag and jump force to get the perfect jump motion needed.

Ground check is implemented using the 'CheckSphere' method in the 'FixedUpdate' method and the input is recorded in the update method and applied in the 'FixedUpdate' method as applying physics in the 'Update' method is quite expensive. The 'groundCheckTransform'
is an empty game object positioned at the very bottom of the capsule.


Selecting what suits your game

It's totally up to you to decide which fps controller suits your game. Sometimes you might need the 'CharacterController' based fps controller in multiplayer games as its easy to sync and sometimes the 'RigidBody' based fps controller when you need your character to interact with every physics enabled object in the scene.


Related Articles




Comments

  1. love it but you can jump in air forever

    ReplyDelete
  2. create a empty object like "CheckGround" and put inside the playerObject, then the transform's CheckGround goes to Ground Check Transform.

    ReplyDelete

Post a Comment

Popular posts from this blog

Unity Get Post web requests - Technoob Technology

Web requests in unity are super easy to use. We gonna use the UnityWebRequest class as the WWW class is deprecated now. UnityWebRequest class can be used for both Get and Post methods. The 'UnityEngine.Networking' namespace is required to work with UnityWebRequests.  This tutorial is made with some simple UI elements. First Let's see how the GET method works. 01. UnityWebRequest.GET We use this method to receive data from the server as normal get requests. Using coroutines in web requests is mandatory as we have to wait until the download is complete before showing the results, and the coroutines make this much easier. A simple button calls the method and a text element will show the result. The script contains a reference to the text element and the string URL of your PHP file.  When the button is pressed the button executes the ButtonGetData method and that method simply starts the GetData coroutine. We use the using keyword as this data ...

How to make an Advanced Audio Manager for Unity - Technoob Technology

Unity engine gives us a good control on audio clips we use, But the problem is when we add several audio sources for several audio clips it's gonna get hard to control them all. And if you want to make some changes to only one audio source you have to find it first.  This will become a mess if you gonna work with more than 10 audio clips. In this case, we can Create an AudioManager and include all the audio clips in it and make some static methods to play, pause the audio from other scripts. This Audio Manager is written in C# and if you use unity-script you have to convert this into unity script first. In this tutorial, we gonna create an audio manager which contains 7 methods. 1.Play 2.Pause 3.Unpause 4.Stop 5.FadeIn 6.FadeOut 7.lower volume for a duration First, we need a Custom class which contains some strings, floats, bools and an audio clip. This class is not derived from unity mono behavior so we should add [System.Serializable] t...