Загрузка данных


using UnityEngine;

public class PlayerCamera : MonoBehaviour
{
    [Header("References")]
    [SerializeField] private Transform cameraHolder;

    [Header("Mouse")]
    [SerializeField] private float sensitivity = 0.15f;

    [Header("Turn Animation")]
    [SerializeField] private float smallTurnAngle = 15f;
    [SerializeField] private float bigTurnAngle = 45f;
    [SerializeField] private float turnCooldown = 0.8f;

    // Обычный поворот стоя
    private float standingTurnCooldown;
    private float turnStartYaw;
    private bool turnTracking;

    // Поворот в приседании
    private float crouchTurnAngle;
    private float crouchTurnCooldown;

    // Состояние приседа
    private bool wasCrouching;

    [Header("Head Bob")]
    [SerializeField] private float walkBobSpeed = 8f;
    [SerializeField] private float walkBobAmount = 0.03f;

    [SerializeField] private float sprintBobSpeed = 12f;
    [SerializeField] private float sprintBobAmount = 0.06f;

    private float bobTimer;

    [Header("Idle Breathing")]
    [SerializeField] private float breathingSpeed = 1.3f;
    [SerializeField] private float breathingAmount = 0.015f;

    [Header("Camera Tilt")]
    [SerializeField] private float maxTilt = 3f;
    [SerializeField] private float tiltSpeed = 8f;

    private float currentTilt;

    [Header("Field Of View")]
    [SerializeField] private Camera playerCamera;
    [SerializeField] private float walkFOV = 75f;
    [SerializeField] private float sprintFOV = 80f;
    [SerializeField] private float fovSpeed = 5f;

    [Header("Landing Bounce")]
    [SerializeField] private float landingAmount = 0.08f;
    [SerializeField] private float landingRecoverSpeed = 7f;

    private float landingOffset;

    [Header("Camera Crouch")]
    [SerializeField] private float crouchOffset = 0.5f;
    [SerializeField] private float crouchSpeed = 8f;

    private Vector3 cameraDefaultLocalPos;

    private PlayerInput playerInput;
    private PlayerMovement movement;
    private Animator animator;

    private float xRotation;

    private void Awake()
    {
        playerInput = GetComponent<PlayerInput>();
        movement = GetComponent<PlayerMovement>();
        animator = GetComponent<Animator>();

        movement.OnLand += PlayLandingBounce;

        cameraDefaultLocalPos = cameraHolder.localPosition;

        wasCrouching = movement.IsCrouching;

        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
    }

    private void Update()
    {
        Look();
        UpdateCameraEffects();
        UpdateFOV();
        CheckCrouchStateChange();
    }

    // =========================================================
    // CAMERA LOOK
    // =========================================================

    private void Look()
    {
        Vector2 look = playerInput.LookInput;

        // Вертикальный поворот камеры
        xRotation -= look.y * sensitivity;
        xRotation = Mathf.Clamp(xRotation, -90f, 90f);

        // Наклон камеры
        float targetTilt = -look.x * 0.08f;
        targetTilt = Mathf.Clamp(targetTilt, -maxTilt, maxTilt);

        currentTilt = Mathf.Lerp(
            currentTilt,
            targetTilt,
            tiltSpeed * Time.deltaTime
        );

        cameraHolder.localRotation =
            Quaternion.Euler(xRotation, 0f, currentTilt);

        // Поворот самого игрока
        float yawDelta = look.x * sensitivity;

        transform.Rotate(Vector3.up * yawDelta);

        // Проверяем поворотную анимацию
        CheckTurnAnimation(yawDelta);
    }

    // =========================================================
    // TURN ANIMATION
    // =========================================================

    private void CheckTurnAnimation(float yawDelta)
    {
        // В воздухе Turn не работает
        if (!movement.IsGrounded)
        {
            ResetStandingTurn();
            ResetCrouchTurn();
            return;
        }

        // Во время движения Turn не работает
        if (playerInput.MoveInput.sqrMagnitude > 0.01f)
        {
            ResetStandingTurn();
            ResetCrouchTurn();
            return;
        }

        // =====================================================
        // CROUCH TURN
        // =====================================================

        if (movement.IsCrouching)
        {
            ResetStandingTurn();

            CheckCrouchTurnAnimation(yawDelta);

            return;
        }

        // =====================================================
        // STANDING TURN
        // =====================================================

        ResetCrouchTurn();

        CheckStandingTurnAnimation();
    }

    // =========================================================
    // STANDING TURN
    // =========================================================

    private void CheckStandingTurnAnimation()
    {
        if (standingTurnCooldown > 0f)
        {
            standingTurnCooldown -= Time.deltaTime;
            return;
        }

        // Начинаем отслеживать новый поворот
        if (!turnTracking)
        {
            turnTracking = true;
            turnStartYaw = transform.eulerAngles.y;
            return;
        }

        // Текущий угол
        float currentYaw = transform.eulerAngles.y;

        // Реальный угол поворота относительно начала
        float turnedAngle =
            Mathf.DeltaAngle(turnStartYaw, currentYaw);

        // Пока недостаточно повернулись
        if (Mathf.Abs(turnedAngle) < smallTurnAngle)
            return;

        // Поворот завершён
        turnTracking = false;
        standingTurnCooldown = turnCooldown;

        // Влево
        if (turnedAngle < 0f)
        {
            if (Mathf.Abs(turnedAngle) >= bigTurnAngle)
            {
                animator.SetTrigger("TurnLeftBig");
            }
            else
            {
                animator.SetTrigger("TurnLeftSmall");
            }
        }
        // Вправо
        else
        {
            if (Mathf.Abs(turnedAngle) >= bigTurnAngle)
            {
                animator.SetTrigger("TurnRightBig");
            }
            else
            {
                animator.SetTrigger("TurnRightSmall");
            }
        }
    }

    // =========================================================
    // CROUCH TURN
    // =========================================================

    private void CheckCrouchTurnAnimation(float yawDelta)
    {
        if (!movement.IsGrounded)
        {
            ResetCrouchTurn();
            return;
        }

        if (!movement.IsCrouching)
        {
            ResetCrouchTurn();
            return;
        }

        if (playerInput.MoveInput.sqrMagnitude > 0.01f)
        {
            ResetCrouchTurn();
            return;
        }

        if (crouchTurnCooldown > 0f)
        {
            crouchTurnCooldown -= Time.deltaTime;
            return;
        }

        // Накапливаем угол мыши
        crouchTurnAngle += yawDelta;

        // Недостаточный поворот
        if (Mathf.Abs(crouchTurnAngle) < smallTurnAngle)
            return;

        float angle = crouchTurnAngle;

        // Сбрасываем накопление
        crouchTurnAngle = 0f;

        // Запускаем cooldown
        crouchTurnCooldown = turnCooldown;

        // Влево
        if (angle < 0f)
        {
            if (Mathf.Abs(angle) >= bigTurnAngle)
            {
                animator.SetTrigger("CrouchTurnLeftBig");
            }
            else
            {
                animator.SetTrigger("CrouchTurnLeftSmall");
            }
        }
        // Вправо
        else
        {
            if (Mathf.Abs(angle) >= bigTurnAngle)
            {
                animator.SetTrigger("CrouchTurnRightBig");
            }
            else
            {
                animator.SetTrigger("CrouchTurnRightSmall");
            }
        }
    }

    // =========================================================
    // RESET TURN
    // =========================================================

    private void ResetStandingTurn()
    {
        turnTracking = false;
        standingTurnCooldown = 0f;
        turnStartYaw = transform.eulerAngles.y;
    }

    private void ResetCrouchTurn()
    {
        crouchTurnAngle = 0f;
        crouchTurnCooldown = 0f;
    }

    // =========================================================
    // CROUCH STATE CHANGE
    // =========================================================

    private void CheckCrouchStateChange()
    {
        bool isCrouching = movement.IsCrouching;

        if (isCrouching == wasCrouching)
            return;

        // Полностью сбрасываем Turn
        ResetStandingTurn();
        ResetCrouchTurn();

        // Убираем старые Trigger
        animator.ResetTrigger("TurnLeftSmall");
        animator.ResetTrigger("TurnRightSmall");
        animator.ResetTrigger("TurnLeftBig");
        animator.ResetTrigger("TurnRightBig");

        animator.ResetTrigger("CrouchTurnLeftSmall");
        animator.ResetTrigger("CrouchTurnRightSmall");
        animator.ResetTrigger("CrouchTurnLeftBig");
        animator.ResetTrigger("CrouchTurnRightBig");

        wasCrouching = isCrouching;
    }

    // =========================================================
    // CAMERA EFFECTS
    // =========================================================

    private void UpdateCameraEffects()
    {
        Vector3 targetPos = cameraDefaultLocalPos;

        // Приседание
        if (movement.IsCrouching)
        {
            targetPos.y -= crouchOffset;
        }

        // Приземление
        targetPos.y += landingOffset;

        bool moving =
            playerInput.MoveInput.sqrMagnitude > 0.01f;

        if (!moving)
        {
            // Дыхание
            targetPos.y +=
                Mathf.Sin(Time.time * breathingSpeed) *
                breathingAmount;

            bobTimer = 0f;
        }
        else
        {
            bool sprinting = playerInput.SprintHeld;

            float speed =
                sprinting
                    ? sprintBobSpeed
                    : walkBobSpeed;

            float amount =
                sprinting
                    ? sprintBobAmount
                    : walkBobAmount;

            bobTimer += Time.deltaTime * speed;

            targetPos.y +=
                Mathf.Sin(bobTimer) * amount;

            targetPos.x +=
                Mathf.Cos(bobTimer * 0.5f) *
                amount *
                0.5f;
        }

        // Восстановление камеры после приземления
        landingOffset = Mathf.Lerp(
            landingOffset,
            0f,
            landingRecoverSpeed * Time.deltaTime
        );

        cameraHolder.localPosition = Vector3.Lerp(
            cameraHolder.localPosition,
            targetPos,
            crouchSpeed * Time.deltaTime
        );
    }

    // =========================================================
    // LANDING
    // =========================================================

    private void PlayLandingBounce()
    {
        landingOffset = -landingAmount;

        // После приземления Turn снова готов
        ResetStandingTurn();
        ResetCrouchTurn();
    }

    // =========================================================
    // FOV
    // =========================================================

    private void UpdateFOV()
    {
        bool sprinting =
            playerInput.SprintHeld &&
            playerInput.MoveInput.y > 0;

        float targetFOV =
            sprinting
                ? sprintFOV
                : walkFOV;

        playerCamera.fieldOfView = Mathf.Lerp(
            playerCamera.fieldOfView,
            targetFOV,
            fovSpeed * Time.deltaTime
        );
    }

    // =========================================================
    // DESTROY
    // =========================================================

    private void OnDestroy()
    {
        if (movement != null)
        {
            movement.OnLand -= PlayLandingBounce;
        }
    }
}