201 lines
8.0 KiB
C#
201 lines
8.0 KiB
C#
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
using Unheimlich.Core;
|
|
using Unheimlich.Features.Player.Data;
|
|
using Unheimlich.Features.Dialogue;
|
|
using InputManager = Unheimlich.Core.Input.InputManager;
|
|
|
|
namespace Unheimlich.Features.Player
|
|
{
|
|
/// <summary>
|
|
/// Handles camera effects: aim assist, shake, and FOV kick on sprint.
|
|
/// </summary>
|
|
public class CameraEffects : MonoBehaviour
|
|
{
|
|
[Header("Settings")]
|
|
public CameraEffectsSettings Settings;
|
|
|
|
private float _shakeTimer = 0f;
|
|
private float _shakeInterval;
|
|
private Vector3 _shakeTarget;
|
|
private Vector3 _shakeOriginalPos;
|
|
private float _shakeSmoothness;
|
|
private Vector3 _pendingAimAssistTarget;
|
|
private bool _hasAimAssistTarget;
|
|
|
|
// FOV kick
|
|
private float _baseFov = -1f;
|
|
private float _currentFovOffset = 0f;
|
|
|
|
// External FOV override — when >= 0, UpdateFovKick uses this as the base FOV
|
|
// instead of the locally cached snapshot. Set by HouseOfLeavesController each frame.
|
|
// Consumed once per frame — caller must re-submit every Update.
|
|
private float _externalBaseFov = -1f;
|
|
|
|
public bool IsAimLocked { get; private set; }
|
|
public bool HasActiveLookInput { get; private set; }
|
|
public bool IsUsingGamepad { get; private set; }
|
|
|
|
//Cam headbob mods
|
|
public enum HeadBobMode
|
|
{
|
|
Normal,
|
|
Idle,
|
|
Disabled
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
if (Settings != null)
|
|
_shakeInterval = 1f / Settings.shakeFrequency;
|
|
|
|
if (PlayerController.Instance?.PlayerCamera != null)
|
|
_baseFov = PlayerController.Instance.PlayerCamera.fieldOfView;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
DetectInputDevice();
|
|
UpdateFovKick();
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
if (_hasAimAssistTarget)
|
|
ApplyAimAssistInternal(_pendingAimAssistTarget);
|
|
}
|
|
|
|
// ── FOV Kick ──────────────────────────────────────────────────────────────
|
|
|
|
private void UpdateFovKick()
|
|
{
|
|
if (Settings == null || PlayerController.Instance?.PlayerCamera == null) return;
|
|
|
|
// Use the external override if submitted this frame, otherwise fall back
|
|
// to the locally cached base FOV.
|
|
float effectiveBase = _externalBaseFov >= 0f ? _externalBaseFov : _baseFov;
|
|
|
|
if (effectiveBase < 0f)
|
|
{
|
|
effectiveBase = PlayerController.Instance.PlayerCamera.fieldOfView;
|
|
_baseFov = effectiveBase;
|
|
}
|
|
|
|
float targetOffset = PlayerController.Instance.IsRunning ? Settings.fovKickAmount : 0f;
|
|
_currentFovOffset = Mathf.Lerp(_currentFovOffset, targetOffset, Time.deltaTime * Settings.fovKickSpeed);
|
|
|
|
PlayerController.Instance.PlayerCamera.fieldOfView = effectiveBase + _currentFovOffset;
|
|
|
|
// Consume the override — must be re-submitted next frame by the caller.
|
|
_externalBaseFov = -1f;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Overrides the base FOV used by the sprint kick for this frame.
|
|
/// Call every Update from HouseOfLeavesController — consumed automatically after use.
|
|
/// Must be called before CameraEffects.Update runs (use Script Execution Order -10).
|
|
/// </summary>
|
|
public void SetBaseFovOverride(float fov) => _externalBaseFov = fov;
|
|
|
|
/// <summary>
|
|
/// Resets the locally cached base FOV to the camera's current fieldOfView.
|
|
/// </summary>
|
|
public void ResetBaseFov()
|
|
{
|
|
if (PlayerController.Instance?.PlayerCamera != null)
|
|
_baseFov = PlayerController.Instance.PlayerCamera.fieldOfView;
|
|
}
|
|
|
|
// ── Input device detection ────────────────────────────────────────────────
|
|
|
|
private void DetectInputDevice()
|
|
{
|
|
Vector2 lookInput = InputManager.GetLookInput();
|
|
HasActiveLookInput = lookInput.magnitude > 0.1f;
|
|
|
|
if (Gamepad.current != null && Gamepad.current.rightStick.ReadValue().magnitude > 0.1f)
|
|
IsUsingGamepad = true;
|
|
else if (Mouse.current != null && Mouse.current.delta.ReadValue().magnitude > 0.1f)
|
|
IsUsingGamepad = false;
|
|
}
|
|
|
|
// ── Camera Shake ──────────────────────────────────────────────────────────
|
|
|
|
public void ShakeCamera(float amplitude, float smoothness = 0f)
|
|
{
|
|
if (PlayerController.Instance == null || PlayerController.Instance.CameraPivot == null || Settings == null)
|
|
return;
|
|
|
|
PlayerEvents.TriggerCameraShake(amplitude);
|
|
|
|
_shakeSmoothness = smoothness;
|
|
_shakeTimer -= Time.deltaTime;
|
|
|
|
if (_shakeTimer <= 0f)
|
|
{
|
|
_shakeTimer = _shakeInterval * amplitude;
|
|
_shakeOriginalPos = PlayerController.Instance.CameraPivot.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)
|
|
{
|
|
PlayerController.Instance.CameraPivot.position = Vector3.Lerp(
|
|
PlayerController.Instance.CameraPivot.position,
|
|
_shakeTarget,
|
|
Time.deltaTime * _shakeSmoothness);
|
|
}
|
|
else
|
|
{
|
|
PlayerController.Instance.CameraPivot.position = _shakeTarget;
|
|
}
|
|
}
|
|
|
|
// ── Aim Assist ────────────────────────────────────────────────────────────
|
|
|
|
public void ApplyAimAssist(Vector3 targetCenter)
|
|
{
|
|
_pendingAimAssistTarget = targetCenter;
|
|
_hasAimAssistTarget = true;
|
|
}
|
|
|
|
private void ApplyAimAssistInternal(Vector3 targetCenter)
|
|
{
|
|
if (PlayerController.Instance == null || PlayerController.Instance.CameraTransform == null || Settings == null)
|
|
return;
|
|
|
|
if (PlayerController.Instance.Movement == null)
|
|
return;
|
|
|
|
if (IsAimLocked && HasActiveLookInput)
|
|
IsAimLocked = false;
|
|
|
|
Vector3 directionToTarget = (targetCenter - PlayerController.Instance.CameraTransform.position).normalized;
|
|
float angleError = Vector3.Angle(PlayerController.Instance.CameraTransform.forward, directionToTarget);
|
|
|
|
if (IsAimLocked || angleError < Settings.aimAssistMaxAngle)
|
|
{
|
|
Vector3 targetDirection = directionToTarget;
|
|
float targetYaw = Mathf.Atan2(targetDirection.x, targetDirection.z) * Mathf.Rad2Deg;
|
|
float targetPitch = -Mathf.Asin(targetDirection.y) * Mathf.Rad2Deg;
|
|
|
|
float currentYaw = PlayerController.Instance.Movement.State.Yaw;
|
|
float currentPitch = PlayerController.Instance.Movement.State.Pitch;
|
|
|
|
float newYaw = Mathf.LerpAngle(currentYaw, targetYaw, Settings.aimAssistStrength * Time.deltaTime);
|
|
float newPitch = Mathf.Lerp(currentPitch, targetPitch, Settings.aimAssistStrength * Time.deltaTime);
|
|
|
|
PlayerController.Instance.Movement.State.Yaw = newYaw;
|
|
PlayerController.Instance.Movement.State.Pitch = newPitch;
|
|
}
|
|
|
|
_hasAimAssistTarget = false;
|
|
}
|
|
|
|
public void LockAim(bool locked) => IsAimLocked = locked;
|
|
}
|
|
} |