wip : critical path - flash hallu

This commit is contained in:
2026-05-27 13:56:17 +02:00
parent a847d6a454
commit dbcf52ea85
27 changed files with 1015 additions and 1104 deletions
@@ -160,8 +160,6 @@ namespace Unheimlich.Features.Dialogue.UI
StartShakeInternal();
}
private void OnEnable() => HallucinationLabelRegistry.Register(this);
private void OnDisable() => HallucinationLabelRegistry.Unregister(this);
private void Update()
{
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: f73b9ba084838014a956e0a0a4150ab7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,86 +0,0 @@
using System.Collections.Generic;
using UnityEngine;
using Unheimlich.Features.Dialogue.UI;
namespace Unheimlich.Features.Hallucination
{
// ─── Per-label override data ───────────────────────────────────────────────
/// <summary>
/// Describes how a specific FloatingLabel should behave when a HallucinationEvent fires.
/// </summary>
[System.Serializable]
public class FloatingLabelOverride
{
[Tooltip("The FloatingLabel to override when this event fires.")]
public FloatingLabel Target;
[Header("Shake Override")]
[Tooltip("Absolute shake radius applied during the override (world units). 0 = no shake.")]
[Min(0f)] public float ShakeRadius = 0.15f;
[Tooltip("Shake speed during the override.")]
[Min(0f)] public float ShakeSpeed = 3f;
[Tooltip("Shake smoothness during the override.")]
[Min(0f)] public float ShakeSmoothness = 4f;
[Header("Ghost Trail Override")]
public bool GhostEnabled = true;
[Range(0f, 1f)] public float GhostAlpha = 0.5f;
[Header("Duration")]
[Tooltip("How long the override lasts (seconds) before reverting to the sanity baseline.")]
[Min(0f)] public float Duration = 2f;
}
// ─── ScriptableObject ──────────────────────────────────────────────────────
/// <summary>
/// ScriptableObject describing a discrete hallucination event.
///
/// Ghost word spawn removed. Words live on FloatingLabel components in the scene.
/// LabelOverrides targets specific labels with timed shake/ghost overrides.
/// </summary>
[CreateAssetMenu(
fileName = "HallucinationEvent",
menuName = "Unheimlich/Hallucination/Hallucination Event")]
public class HallucinationEvent : ScriptableObject
{
[Header("Identity")]
[SerializeField] private string _label;
[Header("Sanity Cost")]
[SerializeField, Range(0f, 1f)] private float _sanityCost = 0f;
[Header("Floating Label Overrides")]
[Tooltip("One entry per FloatingLabel to affect when this event fires.")]
[SerializeField] private List<FloatingLabelOverride> _labelOverrides = new();
// ─── Public Accessors ──────────────────────────────────────────────────
public string Label => _label;
public float SanityCost => _sanityCost;
public IReadOnlyList<FloatingLabelOverride> LabelOverrides => _labelOverrides;
// ─── Internal ─────────────────────────────────────────────────────────
/// <summary>
/// Pushes all label overrides onto their target FloatingLabels.
/// Called by HallucinationManager.TriggerHallucinationEvent().
/// </summary>
internal void ApplyLabelOverrides()
{
foreach (FloatingLabelOverride entry in _labelOverrides)
{
if (entry.Target == null) continue;
entry.Target.ApplyOverride(
entry.ShakeRadius,
entry.ShakeSpeed,
entry.ShakeSmoothness,
entry.GhostEnabled,
entry.GhostAlpha,
entry.Duration);
}
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: b4c627a455825134087ba8c6f8931aed
@@ -0,0 +1,70 @@
using UnityEngine;
using UnityEngine.UI;
namespace Unheimlich.Features.Hallucination
{
/// <summary>
/// Plays a full-screen image flash on the HUD canvas.
/// Fades are handled via LeanTween.
/// Call <see cref="Play"/> to trigger the flash.
/// </summary>
public class HallucinationFlash : MonoBehaviour
{
[Header("Target")]
[SerializeField] private Image _image;
[Header("Timings")]
[SerializeField, Min(0f)] private float _fadeIn = 0.1f;
[SerializeField, Min(0f)] private float _hold = 0.2f;
[SerializeField, Min(0f)] private float _fadeOut = 0.5f;
// ─── State ─────────────────────────────────────────────────────────────
private int _tweenId = -1;
// ─── Public API ────────────────────────────────────────────────────────
/// <summary>
/// Plays the flash sequence.
/// Interrupts any flash already in progress.
/// Safe to wire to any UnityEvent with no arguments.
/// </summary>
public void Play()
{
_image.color = new Color(_image.color.r, _image.color.g, _image.color.b, 0f);
CancelCurrent();
_tweenId = LeanTween.value(gameObject, 0f, 1f, _fadeIn)
.setEase(LeanTweenType.easeOutQuad)
.setOnUpdate(SetAlpha)
.setOnComplete(() =>
{
_tweenId = LeanTween.value(gameObject, 1f, 1f, _hold)
.setOnComplete(() =>
{
_tweenId = LeanTween.value(gameObject, 1f, 0f, _fadeOut)
.setEase(LeanTweenType.easeInQuad)
.setOnUpdate(SetAlpha)
.id;
}).id;
}).id;
}
// ─── Private ───────────────────────────────────────────────────────────
private void CancelCurrent()
{
if (_tweenId == -1) return;
LeanTween.cancel(_tweenId);
_tweenId = -1;
}
private void SetAlpha(float alpha)
{
Color c = _image.color;
c.a = alpha;
_image.color = c;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3aa8c513a3632aa4aa3b8ca2da6ca926
@@ -1,35 +0,0 @@
using System.Collections.Generic;
using UnityEngine;
using Unheimlich.Features.Dialogue.UI;
namespace Unheimlich.Features.Hallucination
{
/// <summary>
/// Static registry that FloatingLabel instances self-register into on OnEnable/OnDisable.
/// Lets HallucinationManager iterate active labels in a radius without any FindObjectsOfType.
/// </summary>
public static class HallucinationLabelRegistry
{
private static readonly HashSet<FloatingLabel> _labels = new();
public static void Register(FloatingLabel label) => _labels.Add(label);
public static void Unregister(FloatingLabel label) => _labels.Remove(label);
/// <summary>
/// Returns all registered labels whose transform is within <paramref name="radius"/>
/// of <paramref name="center"/>. Results are written into <paramref name="results"/>
/// (which is cleared first) to avoid allocations per call.
/// </summary>
public static void GetLabelsInRadius(Vector3 center, float radius, List<FloatingLabel> results)
{
results.Clear();
float sqrRadius = radius * radius;
foreach (FloatingLabel label in _labels)
{
if (label == null) continue;
if ((label.transform.position - center).sqrMagnitude <= sqrRadius)
results.Add(label);
}
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 63e30fb837ed3ab4182c89a053c11a6a
@@ -1,278 +0,0 @@
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);
}
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 6806feb984393b441ad0f1b20cf70333
@@ -1,37 +0,0 @@
using UnityEngine;
using Unheimlich.Core.Audio;
namespace Unheimlich.Features.Hallucination
{
/// <summary>
/// Fires a hallucination effect when a specific AudioSource plays, finishes, or reaches a timestamp.
/// Call OnAudioPlay() from whatever drives the AudioSource to start monitoring.
/// </summary>
public class HallucinationOnAudio : AudioTriggerBase
{
[Header("Hallucination Payload")]
[Tooltip("Amount of insanity to add when this fires (0 = no change)")]
[SerializeField, Range(0f, 1f)] private float _insanityAmount = 0f;
[Tooltip("Optional discrete hallucination event to fire")]
[SerializeField] private HallucinationEvent _hallucinationEvent;
protected override void Execute()
{
if (HallucinationManager.Instance == null)
{
Debug.LogError("[HallucinationOnAudio] HallucinationManager not found!", this);
return;
}
if (_insanityAmount > 0f)
HallucinationManager.Instance.AddInsanity(_insanityAmount);
if (_hallucinationEvent != null)
HallucinationManager.Instance.TriggerHallucinationEvent(_hallucinationEvent);
if (_debugMode)
Debug.Log($"[HallucinationOnAudio] Fired on '{_fireCondition}'. Sanity: {HallucinationManager.Instance.SanityLevel:F2}", this);
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 57b4beb469df640499f96b38d4fa204b
@@ -0,0 +1,121 @@
using UnityEngine;
using UnityEngine.Rendering;
namespace Unheimlich.Features.Hallucination
{
/// <summary>
/// Drives a persistent post-process weight targeting either a URP/HDRP <see cref="Volume"/>
/// and/or a <see cref="Material"/> exposing a <c>_Weight</c> property, based on an
/// internal sanity value [0, 1].
/// Wire the Wrapper methods to any UnityEvent with no arguments.
/// </summary>
public class HallucinationPostProcess : MonoBehaviour
{
[Header("Targets")]
[Tooltip("URP/HDRP Volume driven by the sanity weight.")]
[SerializeField] private Volume _volume;
[Tooltip("Optional material exposing a _Weight property.")]
[SerializeField] private Material _shaderMaterial;
[Header("Sanity")]
[SerializeField, Range(0f, 1f)] private float _initialSanity = 0f;
[Tooltip("Sanity lost per second after each tween completes. 0 = no decay.")]
[SerializeField, Min(0f)] private float _autoDecayRate = 0f;
[Header("Wrappers")]
[Tooltip("Amount added when calling AddSanityWrapper().")]
[SerializeField, Range(0f, 1f)] private float _wrapperAddAmount = 0.2f;
[Tooltip("Amount removed when calling RemoveSanityWrapper().")]
[SerializeField, Range(0f, 1f)] private float _wrapperRemoveAmount = 0.2f;
[Tooltip("Transition duration used by all wrappers (seconds).")]
[SerializeField, Min(0f)] private float _wrapperDuration = 1f;
// ─── State ─────────────────────────────────────────────────────────────
private float _sanity;
private int _tweenId = -1;
private bool _decaying = false;
// ─── Lifecycle ─────────────────────────────────────────────────────────
private void Awake()
{
_sanity = Mathf.Clamp01(_initialSanity);
Apply(_sanity);
}
private void Update()
{
if (!_decaying || _autoDecayRate <= 0f || _sanity <= 0f) return;
_sanity = Mathf.Max(0f, _sanity - _autoDecayRate * Time.deltaTime);
Apply(_sanity);
if (_sanity <= 0f) _decaying = false;
}
// ─── Wrappers ──────────────────────────────────────────────────────────
/// <summary>Adds <see cref="_wrapperAddAmount"/> sanity over <see cref="_wrapperDuration"/> seconds.
/// Auto-decay starts when the tween completes, if <see cref="_autoDecayRate"/> is greater than 0.</summary>
public void AddSanityWrapper() => TweenSanity(_sanity + _wrapperAddAmount, _wrapperDuration);
/// <summary>Removes <see cref="_wrapperRemoveAmount"/> sanity over <see cref="_wrapperDuration"/> seconds.</summary>
public void RemoveSanityWrapper() => TweenSanity(_sanity - _wrapperRemoveAmount, _wrapperDuration);
/// <summary>Snaps sanity to 0 instantly and stops decay.</summary>
public void ResetSanityWrapper()
{
CancelCurrent();
_decaying = false;
_sanity = 0f;
Apply(0f);
}
// ─── Private ───────────────────────────────────────────────────────────
private void TweenSanity(float target, float duration)
{
target = Mathf.Clamp01(target);
_decaying = false;
if (duration <= 0f)
{
CancelCurrent();
_sanity = target;
Apply(target);
if (_autoDecayRate > 0f) _decaying = true;
return;
}
CancelCurrent();
float from = _sanity;
_tweenId = LeanTween.value(gameObject, from, target, duration)
.setEase(LeanTweenType.easeInOutQuad)
.setOnUpdate((float w) =>
{
_sanity = w;
Apply(w);
})
.setOnComplete(() =>
{
// Start decay only after the tween has fully completed.
if (_autoDecayRate > 0f) _decaying = true;
}).id;
}
private void Apply(float weight)
{
if (_volume != null) _volume.weight = weight;
if (_shaderMaterial != null) _shaderMaterial.SetFloat("_Weight", weight);
}
private void CancelCurrent()
{
if (_tweenId == -1) return;
LeanTween.cancel(_tweenId);
_tweenId = -1;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8794f9ed5592b56489a289359e19bfed
@@ -1,52 +0,0 @@
using UnityEngine;
namespace Unheimlich.Features.Hallucination
{
/// <summary>
/// Standalone hallucination payload component.
/// Call Trigger() from any external script, UnityEvent, interaction, dialogue callback, etc.
/// No other component required alongside it — compose freely.
/// </summary>
public class HallucinationTrigger : MonoBehaviour
{
[Header("Sanity")]
[Tooltip("Amount of insanity to add when this trigger fires (0 = no change)")]
[SerializeField, Range(0f, 1f)] private float _insanityAmount = 0f;
[Header("Hallucination Event")]
[Tooltip("Optional discrete hallucination event to fire (ghost word, vision flash, etc.)")]
[SerializeField] private HallucinationEvent _hallucinationEvent;
[Header("Debug")]
[SerializeField] private bool _debugMode;
/// <summary>
/// Fires the hallucination effect.
/// Wire this to any UnityEvent, interaction callback, dialogue node event, audio trigger, etc.
/// </summary>
public void Trigger()
{
if (HallucinationManager.Instance == null)
{
Debug.LogError("[HallucinationTrigger] HallucinationManager not found in scene!", this);
return;
}
if (_insanityAmount > 0f)
{
HallucinationManager.Instance.AddInsanity(_insanityAmount);
if (_debugMode)
Debug.Log($"[HallucinationTrigger] Added {_insanityAmount:F2} insanity. Sanity now: {HallucinationManager.Instance.SanityLevel:F2}", this);
}
if (_hallucinationEvent != null)
{
HallucinationManager.Instance.TriggerHallucinationEvent(_hallucinationEvent);
if (_debugMode)
Debug.Log($"[HallucinationTrigger] Fired event: {_hallucinationEvent.Label}", this);
}
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 5e1119e5c406d0346b0350dbcc91845f
@@ -1,66 +0,0 @@
using UnityEngine;
using Unheimlich.Core.Events;
namespace Unheimlich.Features.Hallucination
{
/// <summary>
/// Self-contained zone that triggers a hallucination effect when the player enters.
/// Ghost-word spawn removed — words live on FloatingLabel components in the scene.
/// SetEvent() lets InfiniteStaircaseController inject the right event per slot at runtime.
/// </summary>
public class HallucinationTriggerZone : TriggerZoneBase
{
[Header("Zone Behaviour")]
[SerializeField] private bool _retriggerable = false;
[SerializeField] private float _delay = 0f;
[Header("Sanity")]
[Tooltip("Insanity added when the zone fires, on top of any SanityCost on the event.")]
[SerializeField, Range(0f, 1f)] private float _insanityAmount = 0f;
[Header("Hallucination Event")]
[Tooltip("Overridable at runtime via SetEvent().")]
[SerializeField] private HallucinationEvent _hallucinationEvent;
[Header("Debug")]
[SerializeField] private bool _debugMode;
protected override bool IsRetriggerable => _retriggerable;
protected override void OnPlayerEntered(Collider playerCollider)
{
if (_delay > 0f) Invoke(nameof(Fire), _delay);
else Fire();
}
/// <summary>Overrides the event at runtime. Pass null to disable.</summary>
public void SetEvent(HallucinationEvent hallucinationEvent) =>
_hallucinationEvent = hallucinationEvent;
/// <summary>Fires the hallucination. Can be called from external scripts or UnityEvents.</summary>
public void Fire()
{
if (HallucinationManager.Instance == null)
{
Debug.LogError("[HallucinationTriggerZone] HallucinationManager not found!", this);
return;
}
if (_insanityAmount > 0f)
{
HallucinationManager.Instance.AddInsanity(_insanityAmount);
if (_debugMode) Debug.Log($"[HallucinationTriggerZone] +{_insanityAmount:F2} insanity.", this);
}
if (_hallucinationEvent != null)
{
HallucinationManager.Instance.TriggerHallucinationEvent(_hallucinationEvent);
if (_debugMode) Debug.Log($"[HallucinationTriggerZone] Event: {_hallucinationEvent.Label}", this);
}
else if (_debugMode)
{
Debug.Log($"[HallucinationTriggerZone] No event on '{gameObject.name}'.", this);
}
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: e8fd091b4319d48448d266a24e885dae
@@ -1,26 +0,0 @@
using UnityEngine;
namespace Unheimlich.Features.Hallucination
{
/// <summary>
/// DEPRECATED — Ghost word spawning has been removed from the hallucination system.
///
/// Words are now FloatingLabel components placed directly in the scene (on stair prefabs
/// or world props). Their behaviour (shake, ghost trail) is driven at runtime by
/// HallucinationManager (baseline, sanity-lerped) and HallucinationEvent (timed overrides).
///
/// This script is kept as an empty stub so existing scene references don't break.
/// It can safely be removed from any GameObject it was attached to.
/// </summary>
[DisallowMultipleComponent]
public class HallucinationWordSpawner : MonoBehaviour
{
private void Awake()
{
Debug.LogWarning(
"[HallucinationWordSpawner] This component is deprecated and does nothing. " +
"Remove it from the scene and use FloatingLabel + HallucinationEvent overrides instead.",
this);
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 39db1c5f2a13bc04782e676a6ad133c8