278 lines
13 KiB
C#
278 lines
13 KiB
C#
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.Rendering.Universal;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using Unheimlich.Core;
|
|
using Unheimlich.Features.Dialogue.UI;
|
|
|
|
namespace Unheimlich.Features.Hallucination
|
|
{
|
|
/// <summary>
|
|
/// Central manager for the hallucination system.
|
|
public class HallucinationManager : Singleton<HallucinationManager>
|
|
{
|
|
private const float MinSanity = 0f;
|
|
private const float MaxSanity = 1f;
|
|
|
|
[Header("Sanity")]
|
|
[SerializeField, Range(0f, 1f)] private float _initialSanity = 0f;
|
|
[SerializeField, Range(0f, 0.1f)] private float _sanityRecoveryRate = 0f;
|
|
|
|
[Header("Effect Lerp")]
|
|
[SerializeField] private float _effectLerpSpeed = 2f;
|
|
|
|
[Header("URP Post-Process Volume")]
|
|
[SerializeField] private Volume _hallucinationVolume;
|
|
|
|
[Header("Chromatic Aberration")]
|
|
[SerializeField] private bool _driveChromaticAberration = true;
|
|
[SerializeField, Range(0f, 1f)] private float _chromaticAberrationMin = 0f;
|
|
[SerializeField, Range(0f, 1f)] private float _chromaticAberrationMax = 0.8f;
|
|
|
|
[Header("Lens Distortion")]
|
|
[SerializeField] private bool _driveLensDistortion = true;
|
|
[SerializeField, Range(-1f, 1f)] private float _lensDistortionMin = 0f;
|
|
[SerializeField, Range(-1f, 1f)] private float _lensDistortionMax = -0.4f;
|
|
|
|
[Header("Vignette")]
|
|
[SerializeField] private bool _driveVignette = true;
|
|
[SerializeField, Range(0f, 1f)] private float _vignetteMin = 0.3f;
|
|
[SerializeField, Range(0f, 1f)] private float _vignetteMax = 0.7f;
|
|
|
|
[Header("Film Grain")]
|
|
[SerializeField] private bool _driveFilmGrain = true;
|
|
[SerializeField, Range(0f, 1f)] private float _filmGrainMin = 0f;
|
|
[SerializeField, Range(0f, 1f)] private float _filmGrainMax = 0.6f;
|
|
|
|
[Header("Bloom")]
|
|
[SerializeField] private bool _driveBloom = true;
|
|
[SerializeField, Min(0f)] private float _bloomIntensityMin = 0f;
|
|
[SerializeField, Min(0f)] private float _bloomIntensityMax = 1f;
|
|
[SerializeField, Min(0f)] private float _bloomThresholdMin = 1f;
|
|
[SerializeField, Min(0f)] private float _bloomThresholdMax = 0.5f;
|
|
[SerializeField, Range(0f, 1f)] private float _bloomScatterMin = 0f;
|
|
[SerializeField, Range(0f, 1f)] private float _bloomScatterMax = 0.7f;
|
|
[SerializeField, Min(0f)] private float _bloomDirtIntensityMin = 0f;
|
|
[SerializeField, Min(0f)] private float _bloomDirtIntensityMax = 0f;
|
|
|
|
[Header("Floating Label — Baseline Influence")]
|
|
[Tooltip("Labels within this radius of the player receive the sanity-driven baseline.")]
|
|
[SerializeField, Min(0f)] private float _labelInfluenceRadius = 12f;
|
|
|
|
[Header("Floating Label — Shake")]
|
|
[Tooltip("Shake radius applied to labels at sanity = 0.")]
|
|
[SerializeField, Min(0f)] private float _labelShakeRadiusAtZero = 0f;
|
|
[SerializeField, Min(0f)] private float _labelShakeSpeedAtZero = 1.5f;
|
|
[SerializeField, Min(0f)] private float _labelShakeSmoothnessAtZero = 2f;
|
|
|
|
[Tooltip("Shake radius applied to labels at sanity = 1.")]
|
|
[SerializeField, Min(0f)] private float _labelShakeRadiusAtOne = 0.2f;
|
|
[SerializeField, Min(0f)] private float _labelShakeSpeedAtOne = 4f;
|
|
[SerializeField, Min(0f)] private float _labelShakeSmoothnessAtOne = 5f;
|
|
|
|
[Header("Floating Label — Ghost Trail")]
|
|
[SerializeField] private bool _ghostEnabledAtZero = false;
|
|
[SerializeField] private bool _ghostEnabledAtOne = true;
|
|
[SerializeField, Range(0f, 1f)] private float _ghostAlphaMin = 0.1f;
|
|
[SerializeField, Range(0f, 1f)] private float _ghostAlphaMax = 0.5f;
|
|
|
|
[Header("Word Shake (legacy — for other systems)")]
|
|
[SerializeField] private float _shakeMultiplierLegacyAtZero = 1f;
|
|
[SerializeField] private float _shakeMultiplierLegacyAtOne = 3f;
|
|
|
|
[Header("Debug")]
|
|
[SerializeField] private bool _debugMode;
|
|
|
|
// ─── State ─────────────────────────────────────────────────────────────
|
|
|
|
private float _currentSanity;
|
|
private float _displayedSanity;
|
|
private Coroutine _lerpCoroutine;
|
|
private bool _vignetteExternallyOverridden;
|
|
|
|
private ChromaticAberration _chromaticAberration;
|
|
private LensDistortion _lensDistortion;
|
|
private Vignette _vignette;
|
|
private FilmGrain _filmGrain;
|
|
private Bloom _bloom;
|
|
|
|
private Transform _playerTransform;
|
|
private readonly List<FloatingLabel> _labelsInRange = new();
|
|
|
|
// ─── Public API ────────────────────────────────────────────────────────
|
|
|
|
public float SanityLevel => _currentSanity;
|
|
|
|
public float WordShakeMultiplier =>
|
|
Mathf.Lerp(_shakeMultiplierLegacyAtZero, _shakeMultiplierLegacyAtOne, _displayedSanity);
|
|
|
|
public event Action<float> OnSanityChanged;
|
|
public event Action<HallucinationEvent> OnHallucinationTriggered;
|
|
|
|
// ─── Lifecycle ─────────────────────────────────────────────────────────
|
|
|
|
protected override void OnAwake()
|
|
{
|
|
_currentSanity = Mathf.Clamp(_initialSanity, MinSanity, MaxSanity);
|
|
_displayedSanity = _currentSanity;
|
|
CacheVolumeOverrides();
|
|
ApplyPostProcessImmediate(_currentSanity);
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (_sanityRecoveryRate > 0f && _currentSanity > MinSanity)
|
|
SetSanity(_currentSanity - _sanityRecoveryRate * Time.deltaTime);
|
|
|
|
DriveFloatingLabelBaseline();
|
|
}
|
|
|
|
// ─── Public Methods ────────────────────────────────────────────────────
|
|
|
|
public void AddInsanity(float amount) => SetSanity(_currentSanity + amount);
|
|
|
|
public void SetSanity(float value)
|
|
{
|
|
float clamped = Mathf.Clamp(value, MinSanity, MaxSanity);
|
|
if (Mathf.Approximately(clamped, _currentSanity)) return;
|
|
|
|
_currentSanity = clamped;
|
|
OnSanityChanged?.Invoke(_currentSanity);
|
|
if (_debugMode) Debug.Log($"[HallucinationManager] Sanity → {_currentSanity:F2}", this);
|
|
RestartLerp();
|
|
}
|
|
|
|
public void SetVignetteOverriddenExternally(bool overridden)
|
|
{
|
|
_vignetteExternallyOverridden = overridden;
|
|
if (_vignette != null)
|
|
_vignette.intensity.value = overridden
|
|
? 0f
|
|
: Mathf.Lerp(_vignetteMin, _vignetteMax, _displayedSanity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fires a discrete hallucination event:
|
|
/// 1. Applies SanityCost.
|
|
/// 2. Dispatches per-label overrides defined in the event asset.
|
|
/// 3. Raises OnHallucinationTriggered.
|
|
/// </summary>
|
|
public void TriggerHallucinationEvent(HallucinationEvent hallucinationEvent)
|
|
{
|
|
if (hallucinationEvent == null)
|
|
{
|
|
Debug.LogError("[HallucinationManager] Null HallucinationEvent!", this);
|
|
return;
|
|
}
|
|
|
|
if (hallucinationEvent.SanityCost > 0f)
|
|
AddInsanity(hallucinationEvent.SanityCost);
|
|
|
|
hallucinationEvent.ApplyLabelOverrides();
|
|
|
|
if (_debugMode)
|
|
Debug.Log($"[HallucinationManager] Event: {hallucinationEvent.Label} " +
|
|
$"({hallucinationEvent.LabelOverrides.Count} label overrides)", this);
|
|
|
|
OnHallucinationTriggered?.Invoke(hallucinationEvent);
|
|
}
|
|
|
|
// ─── Floating Label Baseline ───────────────────────────────────────────
|
|
|
|
private void DriveFloatingLabelBaseline()
|
|
{
|
|
EnsurePlayerTransform();
|
|
if (_playerTransform == null) return;
|
|
|
|
HallucinationLabelRegistry.GetLabelsInRadius(
|
|
_playerTransform.position, _labelInfluenceRadius, _labelsInRange);
|
|
|
|
float t = _displayedSanity;
|
|
|
|
float shakeRadius = Mathf.Lerp(_labelShakeRadiusAtZero, _labelShakeRadiusAtOne, t);
|
|
float shakeSpeed = Mathf.Lerp(_labelShakeSpeedAtZero, _labelShakeSpeedAtOne, t);
|
|
float shakeSmoothness = Mathf.Lerp(_labelShakeSmoothnessAtZero, _labelShakeSmoothnessAtOne, t);
|
|
bool ghostEnabled = t >= 0.5f ? _ghostEnabledAtOne : _ghostEnabledAtZero;
|
|
float ghostAlpha = Mathf.Lerp(_ghostAlphaMin, _ghostAlphaMax, t);
|
|
|
|
foreach (FloatingLabel label in _labelsInRange)
|
|
{
|
|
label.ApplyBaseline(shakeRadius, shakeSpeed, shakeSmoothness, ghostEnabled, ghostAlpha);
|
|
}
|
|
}
|
|
|
|
private void EnsurePlayerTransform()
|
|
{
|
|
if (_playerTransform != null) return;
|
|
if (Unheimlich.Features.Player.PlayerController.Instance != null)
|
|
_playerTransform = Unheimlich.Features.Player.PlayerController.Instance.transform;
|
|
else if (Camera.main != null)
|
|
_playerTransform = Camera.main.transform;
|
|
}
|
|
|
|
// ─── Lerp ──────────────────────────────────────────────────────────────
|
|
|
|
private void RestartLerp()
|
|
{
|
|
if (_lerpCoroutine != null) StopCoroutine(_lerpCoroutine);
|
|
_lerpCoroutine = StartCoroutine(LerpEffects());
|
|
}
|
|
|
|
private IEnumerator LerpEffects()
|
|
{
|
|
while (!Mathf.Approximately(_displayedSanity, _currentSanity))
|
|
{
|
|
_displayedSanity = Mathf.MoveTowards(
|
|
_displayedSanity, _currentSanity, _effectLerpSpeed * Time.deltaTime);
|
|
ApplyPostProcessImmediate(_displayedSanity);
|
|
yield return null;
|
|
}
|
|
_displayedSanity = _currentSanity;
|
|
ApplyPostProcessImmediate(_displayedSanity);
|
|
_lerpCoroutine = null;
|
|
}
|
|
|
|
// ─── Post-Process Effects ──────────────────────────────────────────────
|
|
|
|
private void CacheVolumeOverrides()
|
|
{
|
|
if (_hallucinationVolume == null || _hallucinationVolume.profile == null) return;
|
|
|
|
_hallucinationVolume.profile.TryGet(out _chromaticAberration);
|
|
_hallucinationVolume.profile.TryGet(out _lensDistortion);
|
|
_hallucinationVolume.profile.TryGet(out _vignette);
|
|
_hallucinationVolume.profile.TryGet(out _filmGrain);
|
|
_hallucinationVolume.profile.TryGet(out _bloom);
|
|
|
|
_hallucinationVolume.weight = 1f;
|
|
_hallucinationVolume.enabled = true;
|
|
}
|
|
|
|
private void ApplyPostProcessImmediate(float t)
|
|
{
|
|
if (_driveChromaticAberration && _chromaticAberration != null)
|
|
_chromaticAberration.intensity.value =
|
|
Mathf.Lerp(_chromaticAberrationMin, _chromaticAberrationMax, t);
|
|
|
|
if (_driveLensDistortion && _lensDistortion != null)
|
|
_lensDistortion.intensity.value =
|
|
Mathf.Lerp(_lensDistortionMin, _lensDistortionMax, t);
|
|
|
|
if (_driveVignette && _vignette != null && !_vignetteExternallyOverridden)
|
|
_vignette.intensity.value = Mathf.Lerp(_vignetteMin, _vignetteMax, t);
|
|
|
|
if (_driveFilmGrain && _filmGrain != null)
|
|
_filmGrain.intensity.value = Mathf.Lerp(_filmGrainMin, _filmGrainMax, t);
|
|
|
|
if (_driveBloom && _bloom != null)
|
|
{
|
|
_bloom.intensity.value = Mathf.Lerp(_bloomIntensityMin, _bloomIntensityMax, t);
|
|
_bloom.threshold.value = Mathf.Lerp(_bloomThresholdMin, _bloomThresholdMax, t);
|
|
_bloom.scatter.value = Mathf.Lerp(_bloomScatterMin, _bloomScatterMax, t);
|
|
_bloom.dirtIntensity.value = Mathf.Lerp(_bloomDirtIntensityMin, _bloomDirtIntensityMax, t);
|
|
}
|
|
}
|
|
}
|
|
} |