Загрузка данных
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;
}
}
}