Need something simpler? See the (basic version).
Part of these game systems:
intermediate Movement PRO

First Person Camera Pro

Advanced FPS camera with head bob, weapon sway, lean, recoil system, and FOV zoom for immersive first person gameplay.

Unity 2022.3+ · 5.5 KB · FirstPersonCameraPro.cs

How to Use

1

Attach to your Camera (child of player)

2

Adjust mouse sensitivity and vertical clamp

3

Head bob activates automatically when WASD keys are pressed

4

Weapon sway follows mouse movement — tune amount and smoothing

5

Press Q/E to lean left/right

6

Call AddRecoil(vertical, horizontal) from your weapon script on fire

7

Right-click to zoom (ADS), sprint widens FOV automatically

8

Press Escape to toggle cursor lock

Source Code

FirstPersonCameraPro.cs
C#
using UnityEngine;

public class FirstPersonCameraPro : MonoBehaviour
{
    [Header("Mouse Look")]
    [SerializeField] private float mouseSensitivity = 2f;
    [SerializeField] private float verticalClampMin = -85f;
    [SerializeField] private float verticalClampMax = 85f;
    [SerializeField] private bool lockCursor = true;

    [Header("Head Bob")]
    [SerializeField] private bool enableHeadBob = true;
    [SerializeField] private float bobFrequency = 8f;
    [SerializeField] private float bobAmplitudeX = 0.02f;
    [SerializeField] private float bobAmplitudeY = 0.04f;
    [SerializeField] private float sprintBobMultiplier = 1.4f;

    [Header("Weapon Sway")]
    [SerializeField] private bool enableWeaponSway = true;
    [SerializeField] private float swayAmount = 0.02f;
    [SerializeField] private float swayMaxAmount = 0.06f;
    [SerializeField] private float swaySmoothing = 6f;

    [Header("Lean")]
    [SerializeField] private bool enableLean = true;
    [SerializeField] private KeyCode leanLeftKey = KeyCode.Q;
    [SerializeField] private KeyCode leanRightKey = KeyCode.E;
    [SerializeField] private float leanAngle = 15f;
    [SerializeField] private float leanOffset = 0.4f;
    [SerializeField] private float leanSmoothing = 8f;

    [Header("Recoil")]
    [SerializeField] private float recoilReturnSpeed = 8f;
    [SerializeField] private float recoilSnapSpeed = 15f;

    [Header("FOV")]
    [SerializeField] private float defaultFOV = 70f;
    [SerializeField] private float zoomFOV = 40f;
    [SerializeField] private float sprintFOV = 80f;
    [SerializeField] private float fovTransitionSpeed = 8f;
    [SerializeField] private KeyCode zoomKey = KeyCode.Mouse1;

    private Camera cam;
    private float xRotation;
    private float yRotation;

    // Head bob
    private Vector3 originalLocalPos;
    private float bobTimer;

    // Weapon sway
    private Vector3 swayPosition;

    // Lean
    private float currentLean;
    private float targetLean;

    // Recoil
    private Vector3 currentRecoil;
    private Vector3 targetRecoil;

    // FOV
    private float targetFOV;

    // External state
    private bool isSprinting;
    private bool isMoving;

    private void Start()
    {
        cam = GetComponent<Camera>();
        if (cam == null) cam = Camera.main;

        originalLocalPos = transform.localPosition;
        targetFOV = defaultFOV;

        if (lockCursor)
        {
            Cursor.lockState = CursorLockMode.Locked;
            Cursor.visible = false;
        }
    }

    private void Update()
    {
        HandleMouseLook();
        HandleRecoil();
        HandleHeadBob();
        HandleWeaponSway();
        HandleLean();
        HandleFOV();

        // Apply final rotation
        Quaternion lookRotation = Quaternion.Euler(xRotation + currentRecoil.x,
            yRotation + currentRecoil.y, currentLean);
        transform.localRotation = lookRotation;

        // Apply position offsets
        Vector3 posOffset = originalLocalPos;
        if (enableHeadBob) posOffset += GetBobOffset();
        if (enableWeaponSway) posOffset += swayPosition;
        if (enableLean) posOffset += transform.right * (currentLean / leanAngle * -leanOffset);
        transform.localPosition = Vector3.Lerp(transform.localPosition, posOffset, 10f * Time.deltaTime);

        // Cursor toggle
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            bool locked = Cursor.lockState == CursorLockMode.Locked;
            Cursor.lockState = locked ? CursorLockMode.None : CursorLockMode.Locked;
            Cursor.visible = locked;
        }
    }

    private void HandleMouseLook()
    {
        float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity;
        float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity;

        yRotation += mouseX;
        xRotation -= mouseY;
        xRotation = Mathf.Clamp(xRotation, verticalClampMin, verticalClampMax);

        // Apply yaw to parent (player body)
        if (transform.parent != null)
            transform.parent.rotation = Quaternion.Euler(0f, yRotation, 0f);
    }

    private void HandleRecoil()
    {
        targetRecoil = Vector3.Lerp(targetRecoil, Vector3.zero, recoilReturnSpeed * Time.deltaTime);
        currentRecoil = Vector3.Slerp(currentRecoil, targetRecoil, recoilSnapSpeed * Time.deltaTime);
    }

    private void HandleHeadBob()
    {
        if (!enableHeadBob) return;

        float h = Input.GetAxisRaw("Horizontal");
        float v = Input.GetAxisRaw("Vertical");
        isMoving = Mathf.Abs(h) > 0.1f || Mathf.Abs(v) > 0.1f;

        if (isMoving)
        {
            float multiplier = isSprinting ? sprintBobMultiplier : 1f;
            bobTimer += Time.deltaTime * bobFrequency * multiplier;
        }
        else
        {
            bobTimer = Mathf.Lerp(bobTimer, 0f, 4f * Time.deltaTime);
        }
    }

    private Vector3 GetBobOffset()
    {
        float x = Mathf.Sin(bobTimer) * bobAmplitudeX;
        float y = Mathf.Sin(bobTimer * 2f) * bobAmplitudeY;
        return new Vector3(x, y, 0f);
    }

    private void HandleWeaponSway()
    {
        if (!enableWeaponSway) return;

        float mouseX = Input.GetAxis("Mouse X");
        float mouseY = Input.GetAxis("Mouse Y");

        Vector3 targetSway = new Vector3(
            Mathf.Clamp(-mouseX * swayAmount, -swayMaxAmount, swayMaxAmount),
            Mathf.Clamp(-mouseY * swayAmount, -swayMaxAmount, swayMaxAmount),
            0f
        );

        swayPosition = Vector3.Lerp(swayPosition, targetSway, swaySmoothing * Time.deltaTime);
    }

    private void HandleLean()
    {
        if (!enableLean) return;

        targetLean = 0f;
        if (Input.GetKey(leanLeftKey)) targetLean = leanAngle;
        else if (Input.GetKey(leanRightKey)) targetLean = -leanAngle;

        currentLean = Mathf.Lerp(currentLean, targetLean, leanSmoothing * Time.deltaTime);
    }

    private void HandleFOV()
    {
        if (cam == null) return;

        if (Input.GetKey(zoomKey))
            targetFOV = zoomFOV;
        else if (isSprinting)
            targetFOV = sprintFOV;
        else
            targetFOV = defaultFOV;

        cam.fieldOfView = Mathf.Lerp(cam.fieldOfView, targetFOV, fovTransitionSpeed * Time.deltaTime);
    }

    /// <summary>
    /// Add recoil impulse. Call from weapon system on fire.
    /// </summary>
    public void AddRecoil(float verticalRecoil, float horizontalRecoil)
    {
        targetRecoil += new Vector3(-verticalRecoil,
            Random.Range(-horizontalRecoil, horizontalRecoil), 0f);
    }

    /// <summary>
    /// Set sprint state from external controller.
    /// </summary>
    public void SetSprinting(bool sprinting)
    {
        isSprinting = sprinting;
    }
}