using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Unity.Properties;
using Unity.VisualScripting;
using UnityEngine;

public class FirstPersonController : MonoBehaviour
{
    public CharacterController characterController;
    public Camera mainCamera;
    private Camera playerCamera;

    private Vector3 moveDirection;
    private Vector2 currentInput;

    private float rotationX = 0;

    public bool CanMove { get; private set; } = true;
    public bool isPlayerCrouching => isCrouching;
    public bool isPlayerGrounded => characterController.isGrounded;
    public bool isNotMoving => characterController.velocity.magnitude < 0.1f;
    public bool isSprinting => !isWalking && !isCrouching;
    public bool isWalking => Input.GetKey(walkKey) && characterController.isGrounded;
    public bool ShouldJump => Input.GetKeyDown(jumpKey) && !isCrouching && characterController.isGrounded;
    public bool ShouldCrouch => Input.GetKeyDown(crouchKey) && !duringCrouchAnimation && characterController.isGrounded;

    [Header("Functional Options")]
    [SerializeField] private bool canJump = true;
    [SerializeField] private bool canCrouch = true;
    [SerializeField] private bool canUseHeadbob = true;
    [SerializeField] private bool canInteract = true;

    [Header("Controls")]
    [SerializeField] private KeyCode walkKey = KeyCode.LeftShift; 
    [SerializeField] private KeyCode jumpKey = KeyCode.Space;
    [SerializeField] private KeyCode crouchKey = KeyCode.LeftControl;
    [SerializeField] private KeyCode interactKey = KeyCode.E;

    [Header("Movement Parameters")]
    [SerializeField] public float walkSpeed = 5.0f;
    [SerializeField] public float sprintSpeed = 7.5f;
    [SerializeField] public float crouchSpeed = 3.0f;
    public float targetSpeed;

    [Header("Look Parameters")]
    [SerializeField, Range(1, 10)] private float lookSpeedX = 2.0f;
    [SerializeField, Range(1, 10)] private float lookSpeedY = 2.0f;
    [SerializeField, Range(1, 180)] private float upperLookLimit = 80.0f;
    [SerializeField, Range(1, 180)] private float lowerLookLimit = 80.0f;

    [Header("Camera Tilt")]
    [SerializeField] private float tiltAmount;
    [SerializeField] private float tiltSpeed;
    [SerializeField] private float currentTilt;
    [SerializeField] private float tiltVelocity;

    [Header("Jumping Parameters")]
    [SerializeField] private float jumpForce = 9.0f;
    [SerializeField] private float gravity = 30.0f;

    [Header("Croch Parameters")]
    [SerializeField] private float crouchHeight = 0.5f;
    [SerializeField] private float standHeight = 2f;
    [SerializeField] private float timeToCrouch = 0.25f;
    [SerializeField] private Vector3 crouchingCenter = new Vector3(0, 0.5f, 0);
    [SerializeField] private Vector3 standingCenter = new Vector3(0, 0, 0);
    private bool isCrouching;
    private bool duringCrouchAnimation;

    [Header("Headbob Parameters")]
    [SerializeField] private float walkBobSpeed = 14f;
    [SerializeField] private float walkBobAmount = 0.05f;
    [SerializeField] private float sprintBobSpeed = 18f;
    [SerializeField] private float sprintBobAmount = 0.11f;
    [SerializeField] private float crouchBobSpeed = 8f;
    [SerializeField] private float crouchBobAmount = 0.025f;
    private float defaultYPos = 0;
    private float defaultXPos = 0;
    private float timer;

    [Header("FOV Manager")]
    [SerializeField] private float walkingFOV = 75f;
    [SerializeField] private float sprintingFOV = 82.5f;
    [SerializeField] private float crouchingFOV = 60f;
    [SerializeField] private float speedFOVchange = 5f;
    private float targetFOV;

    [Header("Interaction")]
    [SerializeField] private Vector3 interactionRayPoint = default;
    [SerializeField] private float interactionDistance = default;
    [SerializeField] private LayerMask interactionLayer = default;
    private Interactable currentInteractable;

    void Awake() {
        playerCamera = GetComponentInChildren<Camera>();
        characterController = GetComponent<CharacterController>();
        defaultYPos = playerCamera.transform.localPosition.y;
        defaultXPos = playerCamera.transform.localPosition.x;
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
    }
    void Update() {
        if (CanMove) {
            HandleMovementInput();
            HandleMouseLook();
            ManagerFOV();

            if (canJump) 
                HandleJump();

            if (canCrouch)
                HandleCrouch();
            
            if (canUseHeadbob) 
                HandleHeadbob();
            
            if (canInteract)
                HandleInteractionCheck();
                HandleInteractionInput();

            HandleCameraTilt();
            SpeedControl();   
            ApplyFinalMovements();            
        }
    }
    private void HandleInteractionCheck() {
        if (Physics.Raycast(playerCamera.ViewportPointToRay(interactionRayPoint), out RaycastHit hit, interactionDistance)) {
            if (hit.collider.gameObject.layer == 31 && (currentInteractable == null || hit.collider.gameObject.GetInstanceID() != currentInteractable.GetInstanceID())) {
                hit.collider.TryGetComponent(out currentInteractable);

                if(currentInteractable)
                    currentInteractable.OnFocus();
            }
        }
        else if (currentInteractable) {
            currentInteractable.OnLoseFocus();
            currentInteractable = null;
        }
    }
    private void HandleInteractionInput() {
        if (Input.GetKeyDown(interactKey) && currentInteractable != null && Physics.Raycast(playerCamera.ViewportPointToRay(interactionRayPoint), out RaycastHit hit, interactionDistance, interactionLayer)) {
            currentInteractable.OnInteract();            
        }
    }
    private void HandleMovementInput() {
        currentInput = new Vector2((isSprinting ? sprintSpeed : isCrouching ? crouchSpeed : walkSpeed) * Input.GetAxis("Vertical"),
        (isSprinting ? sprintSpeed : isCrouching ? crouchSpeed : walkSpeed) * Input.GetAxis("Horizontal"));

        float moveDirectionY = moveDirection.y;
        moveDirection = (transform.TransformDirection(Vector3.forward) * currentInput.x) + (transform.TransformDirection(Vector3.right) * currentInput.y);
        moveDirection.y = moveDirectionY;
    }
    private void HandleMouseLook() {
        rotationX -= Input.GetAxis("Mouse Y") * lookSpeedY;
        rotationX = Mathf.Clamp(rotationX, -upperLookLimit, lowerLookLimit);
        playerCamera.transform.localRotation = Quaternion.Euler(rotationX, 0, 0);
        transform.rotation *= Quaternion.Euler(0, Input.GetAxis("Mouse X") * lookSpeedX, 0);
    }
    private void HandleCameraTilt() {
        float targetTilt = Input.GetAxis("Horizontal") * -tiltAmount;
        currentTilt = Mathf.SmoothDamp(currentTilt, targetTilt, ref tiltVelocity, tiltSpeed * Time.deltaTime);

        Quaternion headbobRotation = playerCamera.transform.localRotation;
        playerCamera.transform.localRotation = Quaternion.Euler(rotationX, headbobRotation.eulerAngles.y, currentTilt);
    }
    private void HandleJump() {
        if (ShouldJump) {
            moveDirection.y = jumpForce;
        }
    }
    private void HandleCrouch() {
        if (ShouldCrouch) {
          StartCoroutine(CrouchStand());
        }
    }
    private void HandleHeadbob() {
        if (!characterController.isGrounded) return;

        if (Mathf.Abs(moveDirection.x) > 0.1f || Mathf.Abs(moveDirection.z) > 0.1f) {
            timer += Time.deltaTime * (isCrouching ? crouchBobSpeed : isSprinting ? sprintBobSpeed : walkBobSpeed);
            playerCamera.transform.localPosition = new Vector3 (
            defaultXPos + Mathf.Sin(timer) * (isCrouching ? crouchBobAmount * 0.25f : isSprinting ? sprintBobAmount * 0.25f : walkBobAmount * 0.25f),
            defaultYPos + Mathf.Sin(timer) * (isCrouching ? crouchBobAmount : isSprinting ? sprintBobAmount : walkBobAmount),
            playerCamera.transform.localPosition.z
            );
        }     
    }
    private void ApplyFinalMovements() {
        if (!characterController.isGrounded) {
            moveDirection.y -= gravity * Time.deltaTime;
        }
        characterController.Move(moveDirection * Time.deltaTime);
    }
    private void ManagerFOV() {
        targetFOV = isSprinting && (characterController.velocity.x >= sprintSpeed * 0.2f || characterController.velocity.z >= sprintSpeed * 0.2f ||
        -characterController.velocity.x >= sprintSpeed * 0.2f || -characterController.velocity.z >= sprintSpeed * 0.2f) ? sprintingFOV : isPlayerCrouching ? crouchingFOV : walkingFOV; 
        mainCamera.fieldOfView = Mathf.Lerp(mainCamera.fieldOfView, targetFOV, speedFOVchange * Time.deltaTime);      
    }
    private IEnumerator CrouchStand() {
        if (isCrouching && Physics.Raycast(playerCamera.transform.position, Vector3.up, 1f)) 
            yield break;      

        duringCrouchAnimation = true;

        float timeElapsed = 0;
        float targetHeight = isCrouching ? standHeight : crouchHeight;
        float currentHeight = characterController.height;
        Vector3 targetCenter = isCrouching ? standingCenter : crouchingCenter;
        Vector3 currentCenter = characterController.center;

        while(timeElapsed < timeToCrouch) {
            characterController.height = Mathf.Lerp(currentHeight, targetHeight, timeElapsed/timeToCrouch);
            characterController.center = Vector3.Lerp(currentCenter, targetCenter, timeElapsed/timeToCrouch);
            timeElapsed += Time.deltaTime;
            yield return null;  
        }

        characterController.height = targetHeight;
        characterController.center = targetCenter;

        isCrouching = !isCrouching;        

        duringCrouchAnimation = false;
    }
    private void SpeedControl() {
        Vector3 flatVel = new Vector3(characterController.velocity.x, 0, characterController.velocity.z);
        targetSpeed = isCrouching ? crouchSpeed : isSprinting ? sprintSpeed : walkSpeed;

        if (flatVel.magnitude > targetSpeed) {
            flatVel = flatVel.normalized * targetSpeed;

            moveDirection.x = flatVel.x;
            moveDirection.z = flatVel.z;
        }
    }
}
У меня есть камера тильт, то есть камера наклоняется в зависимости от движения, но переход выглядит довольно резким, как это можно иссправить?