180 lines
6.4 KiB
C#
180 lines
6.4 KiB
C#
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
using Unheimlich.Gameplay.Portals;
|
|
using InputManager = Unheimlich.Managers.PlayerInputManager;
|
|
|
|
namespace Unheimlich.Gameplay.Player
|
|
{
|
|
/// <summary>
|
|
/// Handles first-person camera rotation and look mechanics for the player.
|
|
/// Supports both mouse and gamepad input with adaptive sensitivity.
|
|
/// Includes aim assist for dialogue choices and camera shake effects.
|
|
/// </summary>
|
|
public class PlayerLook : MonoBehaviour
|
|
{
|
|
private const float INPUT_DEADZONE = 0.1f;
|
|
private const float MOUSE_DELTA_MULTIPLIER = 0.001f;
|
|
|
|
public static PlayerLook Instance { get; private set; }
|
|
|
|
[Header("Components")]
|
|
[SerializeField] private Transform _playerCamera, _playerCameraPivot;
|
|
|
|
[Header("Settings")]
|
|
[SerializeField] private int _frameRate = 120;
|
|
[SerializeField] private float _mouseSensitivity = 0.2f;
|
|
[SerializeField] private float _gamepadSensitivity = 100f;
|
|
[SerializeField] private float _minLookAngle = -60f;
|
|
[SerializeField] private float _maxLookAngle = 60f;
|
|
|
|
[Header("Aim Assist")]
|
|
[SerializeField] private float _aimAssistStrength = 25f;
|
|
[SerializeField] private float _aimAssistMaxAngle = 10f;
|
|
|
|
public bool HasActiveLookInput { get; private set; }
|
|
public bool IsAimLocked { get; private set; } = false;
|
|
|
|
[Header("Shake Control")]
|
|
[SerializeField] private float _shakeFrequency = 40f;
|
|
private float _shakeInterval;
|
|
private float _shakeTimer = 0f;
|
|
private Vector3 _shakeTarget = Vector3.zero;
|
|
private Vector3 _shakeOriginalPos = Vector3.zero;
|
|
private float _shakeSmoothness = 0f;
|
|
|
|
private float _horizontalRotation;
|
|
private float _verticalRotation;
|
|
private bool _isUsingGamepad = false;
|
|
|
|
private void Awake()
|
|
{
|
|
if (Instance != null && Instance != this)
|
|
{
|
|
Destroy(this);
|
|
return;
|
|
}
|
|
Instance = this;
|
|
|
|
QualitySettings.vSyncCount = 0;
|
|
Application.targetFrameRate = _frameRate;
|
|
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
Cursor.lockState = CursorLockMode.Locked;
|
|
Cursor.visible = false;
|
|
_shakeInterval = 1f / _shakeFrequency;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if(!InputManager.IsLookingEnabled)
|
|
return;
|
|
|
|
HandleLook();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes look input from mouse or gamepad and updates camera rotation.
|
|
/// Automatically detects input device and applies appropriate sensitivity.
|
|
/// Clamps vertical rotation to prevent over-rotation.
|
|
/// </summary>
|
|
private void HandleLook()
|
|
{
|
|
Vector2 lookInput = InputManager.Instance.LookInput;
|
|
|
|
HasActiveLookInput = lookInput.magnitude > INPUT_DEADZONE;
|
|
|
|
if (Gamepad.current != null && Gamepad.current.rightStick.ReadValue().magnitude > PortalConstants.GAMEPAD_DEADZONE)
|
|
_isUsingGamepad = true;
|
|
else if (Mouse.current != null && Mouse.current.delta.ReadValue().magnitude > PortalConstants.GAMEPAD_DEADZONE)
|
|
_isUsingGamepad = false;
|
|
|
|
float sensitivity = _isUsingGamepad ? _gamepadSensitivity : _mouseSensitivity;
|
|
float deltaMultiplier = _isUsingGamepad ? Time.deltaTime : MOUSE_DELTA_MULTIPLIER;
|
|
|
|
_horizontalRotation += lookInput.x * sensitivity * deltaMultiplier;
|
|
transform.rotation = Quaternion.Euler(0, _horizontalRotation, 0f);
|
|
|
|
_verticalRotation -= lookInput.y * sensitivity * deltaMultiplier;
|
|
_verticalRotation = Mathf.Clamp(_verticalRotation, _minLookAngle, _maxLookAngle);
|
|
|
|
_playerCamera.localRotation = Quaternion.Euler(_verticalRotation, 0f, 0f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Smoothly rotates the player camera toward a target using spherical interpolation.
|
|
/// Assists player aiming by gently pulling the view toward dialogue choices when looking near them.
|
|
/// Automatically unlocks if player provides manual input.
|
|
/// </summary>
|
|
public void ApplyAimAssist(Vector3 targetCenter)
|
|
{
|
|
if (IsAimLocked && HasActiveLookInput)
|
|
IsAimLocked = false;
|
|
|
|
Vector3 directionToTarget = (targetCenter - _playerCamera.position).normalized;
|
|
float angleError = Vector3.Angle(_playerCamera.forward, directionToTarget);
|
|
|
|
if (IsAimLocked || (angleError > 0.01f && angleError < _aimAssistMaxAngle))
|
|
{
|
|
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget);
|
|
|
|
Quaternion correctedRotation = Quaternion.Slerp(
|
|
_playerCamera.rotation,
|
|
targetRotation,
|
|
_aimAssistStrength * Time.deltaTime
|
|
);
|
|
Quaternion newBodyRotation = Quaternion.Euler(0, correctedRotation.eulerAngles.y, 0);
|
|
|
|
_horizontalRotation = newBodyRotation.eulerAngles.y;
|
|
transform.rotation = newBodyRotation;
|
|
|
|
float newVerticalAngle = correctedRotation.eulerAngles.x;
|
|
|
|
if (newVerticalAngle > 180) newVerticalAngle -= 360;
|
|
|
|
_verticalRotation = Mathf.Clamp(newVerticalAngle, _minLookAngle, _maxLookAngle);
|
|
_playerCamera.localRotation = Quaternion.Euler(_verticalRotation, 0f, 0f);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a random camera shake effect for feedback (e.g., impacts, events).
|
|
/// Uses a timer-based interval to control shake frequency.
|
|
/// Amplitude controls both the intensity and duration of the shake.
|
|
/// Smoothness controls how quickly the camera interpolates to the shake position (0 = instant, higher = smoother).
|
|
/// </summary>
|
|
public void Shake(float amplitude, float smoothness = 0f)
|
|
{
|
|
_shakeSmoothness = smoothness;
|
|
_shakeTimer -= Time.deltaTime;
|
|
|
|
if (_shakeTimer <= 0f)
|
|
{
|
|
_shakeTimer = _shakeInterval * amplitude;
|
|
_shakeOriginalPos = _playerCameraPivot.position;
|
|
|
|
float x = Random.Range(-1f, 1f) * amplitude;
|
|
float y = Random.Range(-1f, 1f) * amplitude;
|
|
|
|
_shakeTarget = _shakeOriginalPos + new Vector3(x, y, 0f);
|
|
}
|
|
|
|
if (_shakeSmoothness > 0f)
|
|
{
|
|
_playerCameraPivot.position = Vector3.Lerp(_playerCameraPivot.position, _shakeTarget, Time.deltaTime * _shakeSmoothness);
|
|
}
|
|
else
|
|
{
|
|
_playerCameraPivot.position = _shakeTarget;
|
|
}
|
|
}
|
|
|
|
public void LockAim(bool locked)
|
|
{
|
|
IsAimLocked = locked;
|
|
}
|
|
}
|
|
}
|