135 lines
7.2 KiB
C#
135 lines
7.2 KiB
C#
using UnityEngine;
|
|
using Unheimlich.Features.Player;
|
|
|
|
namespace Unheimlich.World.HouseOfLeaves
|
|
{
|
|
/// <summary>
|
|
/// Simulates a Dolly Zoom (Vertigo effect) by applying a speed-driven additive FOV offset
|
|
/// on top of the base FOV already written by <see cref="HouseOfLeavesController"/> and
|
|
/// <see cref="CameraEffects"/> earlier in the frame.
|
|
///
|
|
/// Runs in <c>LateUpdate</c> — after both <see cref="HouseOfLeavesController.Update"/> and
|
|
/// <see cref="CameraEffects.Update"/> — so it always layers on top of the final written value.
|
|
/// Subtracts the previous frame's offset before adding the new one to avoid compounding.
|
|
///
|
|
/// The faster the player moves, the more the FOV pinches inward,
|
|
/// compressing the perceived corridor space and amplifying claustrophobia.
|
|
/// </summary>
|
|
public class DollyZoomEffect : MonoBehaviour
|
|
{
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
// Serialized Fields
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
[Header("Effect")]
|
|
[Tooltip("Enable or disable the effect at runtime.")]
|
|
[SerializeField] private bool _isEnabled = true;
|
|
|
|
[Tooltip("Maximum negative FOV offset at full speed (degrees). " +
|
|
"Negative value pinches the FOV inward.")]
|
|
[SerializeField] private float _maxFovOffset = -15f;
|
|
|
|
[Tooltip("Walk speed (km/h) at which the effect starts fading in.")]
|
|
[SerializeField] private float _speedThreshold = 12f;
|
|
|
|
[Tooltip("Walk speed (km/h) at which the effect reaches its maximum.")]
|
|
[SerializeField] private float _maxSpeedForEffect = 22f;
|
|
|
|
[Tooltip("Lerp speed for the FOV offset transitions.")]
|
|
[SerializeField] private float _smoothSpeed = 3f;
|
|
|
|
[Header("Pulse (optional)")]
|
|
[Tooltip("Adds a subtle sinusoidal oscillation to the FOV offset for extra unease.")]
|
|
[SerializeField] private bool _enablePulse;
|
|
|
|
[Tooltip("Amplitude of the pulse oscillation (degrees).")]
|
|
[SerializeField] private float _pulseAmplitude = 1.5f;
|
|
|
|
[Tooltip("Frequency of the pulse oscillation (Hz).")]
|
|
[SerializeField] private float _pulseFrequency = 0.4f;
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
// Private Fields
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
private Camera _playerCamera;
|
|
private float _currentFovOffset;
|
|
private bool _isInitialized;
|
|
|
|
private float _previousFovOffset;
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
// Unity Messages
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
private void Start()
|
|
{
|
|
if (PlayerController.Instance?.PlayerCamera == null)
|
|
{
|
|
Debug.LogError("[DollyZoomEffect] PlayerCamera could not be resolved.");
|
|
return;
|
|
}
|
|
|
|
_playerCamera = PlayerController.Instance.PlayerCamera;
|
|
_isInitialized = true;
|
|
}
|
|
|
|
// LateUpdate ensures this runs after HouseOfLeavesController and CameraEffects
|
|
// have both written their FOV values in Update.
|
|
private void LateUpdate()
|
|
{
|
|
if (!_isInitialized || !_isEnabled) return;
|
|
|
|
TickDollyZoom();
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
// Tick
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Computes the speed-driven FOV delta and applies it additively on top of
|
|
/// the current <c>fieldOfView</c>. Strips the previous frame's offset first
|
|
/// to avoid compounding the delta across frames.
|
|
/// </summary>
|
|
// Dans DollyZoomEffect, stocker le FOV base séparément
|
|
private void TickDollyZoom()
|
|
{
|
|
float speedKmh = GetHorizontalSpeedKmh();
|
|
float speedT = Mathf.Clamp01(
|
|
(speedKmh - _speedThreshold) / Mathf.Max(0.01f, _maxSpeedForEffect - _speedThreshold)
|
|
);
|
|
|
|
float targetOffset = _maxFovOffset * speedT;
|
|
if (_enablePulse && speedT > 0.01f)
|
|
targetOffset += Mathf.Sin(Time.time * _pulseFrequency * Mathf.PI * 2f) * _pulseAmplitude * speedT;
|
|
|
|
_currentFovOffset = Mathf.Lerp(_currentFovOffset, targetOffset, Time.deltaTime * _smoothSpeed);
|
|
|
|
_playerCamera.fieldOfView += _currentFovOffset - _previousFovOffset;
|
|
_previousFovOffset = _currentFovOffset;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
// Helpers
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
private float GetHorizontalSpeedKmh()
|
|
{
|
|
CharacterController charController = PlayerController.Instance?.CharController;
|
|
if (charController == null) return 0f;
|
|
|
|
Vector3 horizontalVelocity = charController.velocity;
|
|
horizontalVelocity.y = 0f;
|
|
|
|
return horizontalVelocity.magnitude * 3.6f;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
// Public API
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
public void SetEnabled(bool value) => _isEnabled = value;
|
|
}
|
|
}
|