Files
Ipseity-Demo/Assets/Unheimlich_POC/Scripts/Gameplay/GameObjects/Player/PlayerLook.cs
T

166 lines
5.8 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 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.
/// </summary>
public void Shake(float amplitude)
{
_shakeTimer -= Time.deltaTime;
if (_shakeTimer <= 0f)
{
_shakeTimer = _shakeInterval * amplitude;
float x = Random.Range(-1f, 1f) * amplitude;
float y = Random.Range(-1f, 1f) * amplitude;
Vector3 originalPos = _playerCameraPivot.position;
_playerCameraPivot.position = originalPos + new Vector3(x, y, 0f);
}
}
public void LockAim(bool locked)
{
IsAimLocked = locked;
}
}
}