Загрузка данных
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 standingTurnAngle;
private float standingTurnCooldown;
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 float turnStartYaw;
private bool turnTracking;
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 ResetCrouchTurn()
{
crouchTurnAngle = 0f;
crouchTurnCooldown = 0f;
}
private void Update()
{
Look();
UpdateCameraEffects();
UpdateFOV();
CheckCrouchStateChange();
}
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);
// Проверяем turn-анимацию
CheckTurnAnimation(yawDelta);
}
private void CheckTurnAnimation(float yawDelta)
{
// В воздухе ничего не делаем
if (!movement.IsGrounded)
{
turnTracking = false;
return;
}
// Во время движения Turn не нужен
if (playerInput.MoveInput.sqrMagnitude > 0.01f)
{
turnTracking = false;
return;
}
// Если присел — crouch turn обрабатывается отдельно
if (movement.IsCrouching)
{
turnTracking = false;
CheckCrouchTurnAnimation(yawDelta);
return;
}
// Если только что закончили предыдущий Turn
if (turnCooldownTimer > 0f)
{
turnCooldownTimer -= 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;
turnCooldownTimer = 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");
}
}
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;
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");
}
}
private void CheckCrouchStateChange()
{
bool isCrouching = movement.IsCrouching;
if (isCrouching == wasCrouching)
return;
ResetStandingTurn();
ResetCrouchTurn();
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;
}
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
);
}
private void PlayLandingBounce()
{
landingOffset = -landingAmount;
}
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
);
}
private void OnDestroy()
{
if (movement != null)
{
movement.OnLand -= PlayLandingBounce;
}
}
}