Make your own First-Person Controller
Setting Up the Scene
- Creating the project
Open Unity Hub and Create a project using the 3D template. I’m using the HDR Render Pipeline template but you can choose either of them.
After the project finishes loading, make a plane for our player to stand on. To make a plane,
Right Click on the Hierarchy→3D Objects → Plane.
Reset the transform if it’s not at position (0,0,0) globally. - Creating our Player
Right Click on the Hierarchy → Create Empty. Name it “Player”.
Click on the Player and in the Inspector panel, select Add Component and type Character Controller, and select it.
So the Character Controller takes care of collision, movement, and rotation for us. The only thing we need to externally implement is gravity(more on that later).
A green outline now appears around the Player. That’s the collider that comes with it the Character Controller.
Place the Player on the ground by dragging it along the y-axis until its position on the y-axis becomes 1.
Since we are making a First-Person Controller, the view must be from the Player’s perspective.
To make this happen, Drag the existing Main Camera from the scene into the Player so that it becomes a Child Game Object to our Player. Then reset the camera’s transform component so that it aligns with the Player transform. Drag the camera to the top of the Player’s head (at a position of 0.9 units on the y-axis) so that we can view the world from the supposed Player’s eye level.
Why not drag it all the way up to 1 unit on the y-axis?
→ Because when the Player jumps, the camera has a chance to clip through the ceiling. So keeping the camera inside the Character Controller, helps prevent that.
Rotating the Player
Let’s first create a script and call it “MouseLook”.
Go to Project Panel → Select the Assets Folder → Right Click → Create → C# Script.
Name it “MouseLook” and add it to Player like we added the Character Controller.
Capturing Mouse Delta Input:
When it comes to Player rotation, we first need to capture the mouse delta input i.e. how much our mouse moves on the x-axis and the y-axis. Open up the MouseLook script and let’s capture our mouse delta input by making a vector like this inside our Update function:
//Serialized(can be modified in the inspector) variable to control mouse sensitivity.
[SerializeField] float mouseSensitivity = 200f;// Update is called once per framevoid Update(){//getting the input of mouse deltaVector2 mouseInput = new Vector2(Input.GetAxisRaw("Mouse X"), Input.GetAxisRaw("Mouse Y")) * mouseSensitivity * Time.deltaTime;}
Multiplying the Vector2 by custom variable mouseSensitivity allows us to modify the speed of our rotation. This will come in handy later.
Multiplying by Time.deltaTime allows us to make it frame rate independent.
The axis names “Mouse X” and “Mouse Y” are in-built axis that you can find and change their name by going to Edit→ Project Settings → Input Manager → Axes → Mouse X or Mouse Y.
- Horizontal Rotation
To rotate the Player along with the camera around the y-axis, just rotating the Player does the trick. Since the camera is a child of the Player, the camera rotates with it.
We can get the desired result by using the function called transform.Rotate(). It takes in a Vector3 as a parameter. In this case, we pass in Vector3.up (shorthand for (0,1,0) as we want to rotate the Player transform around the y-axis and multiply it by our mouseInput on the x-axis.
[Change mouse sensitivity in the inspector to rotate faster or slower]
//horizontal rotation of player along with camera.transform.Rotate(Vector3.up * mouseInput.x);
- Vertical Rotation
In the case of vertical rotation, there’s no need to Rotate the Player as it might cause problems with movement later. Just rotating the camera around its x-axis will fetch us our desired result.
Also, we need to clamp the rotation from going beyond 90 degrees or -90 degrees as that might cause our camera to flip.
On top of that, the value of mouseInput on the y-axis goes up as we drag the mouse upwards and down as we drag the mouse downwards. If we simply add the mouseInput on the y-axis to the localRotation around the x-axis of the camera then we’ll end up with the exact opposite result of what we want. That is because adding or increasing the value of the camera along the x-axis would cause it to rotate downwards and subtracting would cause it to rotate upwards.
To avoid this, we subtract our mouseInput on the y-axis from camera localRotation around the x-axis.
With all of these in mind, we end up with the following code:
//creating another float variable to clamp it laterverticalRotation -= mouseInput.y;//clamping verticalRotationverticalRotation = Mathf.Clamp(verticalRotation, -90f, 90f);//setting the localRotation of the cameracam.transform.localRotation = Quaternion.Euler(verticalRotation, 0, 0);
- Locking the cursor from moving around
To lock the cursor, all we need to do is add the following line of code in the Awake function, which will set the Cursor Lock State.
private void Awake(){ Cursor.lockState = CursorLockMode.Locked;}
Moving the Player
We’ll handle our movement in another script. Just like the MouseLook script, make another script and name it “Movement”.
Much Like the axes Mouse X and Mouse Y, Horizontal and Vertical axis help us implement Character Movement.
Input.GetAxisRaw(“Horizontal”) returns -1 if we press ‘a’ key or left arrow button and +1 if we press ‘d’ key or right arrow button. Input.GetAxisRaw(“Vertical”) returns -1 if we press ‘s’ key or down arrow button and +1 if we press ‘w’ key or up arrow button.
Capture both the inputs using the following line of code:
//input for movementVector3 input = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical"));
The value at y remains zero because we will implement movement on the y-axis(gravity) explicitly.
To move the character while taking the rotation of it into consideration, we use the following code:
//direction vector taking rotation and speed into considerationdirection = (transform.right * input.x + transform.forward * input.z).normalized * speed;
The direction vector that will move the Player by input.x * speed amount to its right or left and by input.z * speed amounts to its front or back depending on the input value.
Why not Vector3.right and Vector3.forward?
→ Both of them will not take the rotation of the player into consideration. For example, if we rotate the player 45 degrees around the y-axis, then the global axis remains the same:
The Local Axis:
Normalize the direction vector so that the Player doesn’t move faster when traveling diagonally.
Finally, cache the Character Controller like so:
CharacterController controller;// Start is called before the first frame updatevoid Awake(){ controller = GetComponent<CharacterController>();}
And move the Player using the Move() function in Update. As a parameter, pass in our direction vector multiplied by Time.deltaTime to make it frame rate independent.
Final Movement Code:
using System.Collections;using System.Collections.Generic;using UnityEngine;public class Movement : MonoBehaviour{[SerializeField] float speed;Vector3 direction;CharacterController controller;// Start is called before the first frame updatevoid Awake(){controller = GetComponent<CharacterController>();}// Update is called once per framevoid Update(){//input for movementVector3 input = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical"));//direction vector taking rotation and speed into considerationdirection = (transform.right * input.x + transform.forward * input.z).normalized * speed;//moving the Player using Character Controllercontroller.Move(direction * Time.deltaTime);}}
Add the Movement script to Player, change the speed value to something like 5, Press Play, and see our Player in action.
Link to Github Repo: https://github.com/SeemantaDebdas/Blog-FPC
In the next section, we’ll implement gravity and jump functionality to our Player.
Cheers!
Debdas :D