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

Features

  • Mouse look with configurable sensitivity and vertical clamp angles
  • Head bob with separate X/Y amplitude and sprint multiplier
  • Weapon sway that responds to mouse movement with smoothing
  • Q/E lean system with configurable angle, offset, and smoothing
  • Recoil system with snap and return speeds for weapon integration
  • Dynamic FOV transitions for zoom (ADS), sprint, and default states

When to Use This

Built for first-person shooters and horror games that need immersive camera feel beyond basic mouse look. Essential when your FPS needs head bob, weapon sway, ADS zoom, and recoil — all systems that typically require separate scripts but are unified here.

Common Mistakes

The camera must be a child of the player body — yaw rotation is applied to transform.parent, so attaching directly to the root object breaks horizontal rotation. Call SetSprinting() from your player controller every frame or head bob and FOV won't react to sprint state. Recoil values from AddRecoil() are additive, so firing rapidly without tuning recoilReturnSpeed will cause the camera to drift upward permanently.

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;
    }
}