using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using Unheimlich.Core; namespace Unheimlich.UI { [RequireComponent(typeof(Camera))] public class DialogueBlurEffectURP : Singleton { [Header("Volume Settings")] [SerializeField] private Volume _globalVolume; [Header("Depth of Field (Gaussian)")] [SerializeField] private float _gaussianStart = 0f; [SerializeField] private float _gaussianEnd = 15f; [SerializeField] private float _maxGaussianRadius = 4f; [Header("Brightness (Color Adjustments)")] [SerializeField] private float _startPostExposure = 0f; [SerializeField] private float _targetPostExposure = 0.3f; [Header("Animation Settings")] [SerializeField] private float _fadeDuration = 1.0f; [SerializeField] private AnimationCurve _fadeCurve; [Header("UI Configuration")] [Tooltip("Layer(s) for dialogue (World Space UI) to render without blur")] [SerializeField] private LayerMask _dialogueLayers; [SerializeField] private bool _autoCreateDialogueCamera = true; private DepthOfField _dof; private ColorAdjustments _color; private VolumeProfile _profile; private float _current = 0f; private float _target = 0f; private float _start = 0f; private float _timer = 0f; private bool _fading = false; private Camera _baseCam; private Camera _dialogueCam; protected override void OnAwake() { _baseCam = GetComponent(); if (_fadeCurve == null || _fadeCurve.keys.Length == 0) _fadeCurve = AnimationCurve.EaseInOut(0, 0, 1, 1); if (_globalVolume == null) { Debug.LogError("[DialogueBlur] ERROR: Global Volume NOT ASSIGNED in inspector!"); enabled = false; return; } if (_globalVolume.profile == null) { Debug.LogError("[DialogueBlur] ERROR: Global Volume has NO VolumeProfile assigned!"); enabled = false; return; } _profile = _globalVolume.profile; if (!_profile.TryGet(out _dof)) { Debug.LogError("[DialogueBlur] ERROR: Depth Of Field NOT FOUND in Volume Profile!"); enabled = false; return; } if (!_profile.TryGet(out _color)) { Debug.LogWarning("[DialogueBlur] WARNING: Color Adjustments not found (not critical)"); } _dof.mode.Override(DepthOfFieldMode.Gaussian); _dof.gaussianStart.Override(_gaussianStart); _dof.gaussianEnd.Override(_gaussianEnd); _dof.gaussianMaxRadius.Override(0f); _dof.active = false; if (_color != null) { _color.postExposure.Override(_startPostExposure); _color.active = false; } _globalVolume.weight = 0f; _globalVolume.enabled = true; } void Start() { if (_autoCreateDialogueCamera) SetupDialogueOverlayCamera(); } void SetupDialogueOverlayCamera() { var baseData = _baseCam.GetComponent(); if (baseData == null) baseData = _baseCam.gameObject.AddComponent(); baseData.renderType = CameraRenderType.Base; baseData.renderPostProcessing = true; if (_dialogueLayers.value == 0) Debug.LogWarning("[DialogueBlur] WARNING: dialogueLayers is EMPTY! Ghost words will be blurred!"); _baseCam.cullingMask &= ~_dialogueLayers; var go = new GameObject("Dialogue Camera (Overlay)"); go.transform.SetParent(_baseCam.transform); go.transform.localPosition = Vector3.zero; go.transform.localRotation = Quaternion.identity; _dialogueCam = go.AddComponent(); _dialogueCam.CopyFrom(_baseCam); _dialogueCam.clearFlags = CameraClearFlags.Depth; _dialogueCam.cullingMask = _dialogueLayers; var overlayData = go.AddComponent(); overlayData.renderType = CameraRenderType.Overlay; overlayData.renderPostProcessing = false; baseData.cameraStack.Add(_dialogueCam); } void Update() { if (!_fading) return; _timer += Time.deltaTime; float t = Mathf.Clamp01(_timer / Mathf.Max(0.0001f, _fadeDuration)); float cv = _fadeCurve.Evaluate(t); _current = Mathf.Lerp(_start, _target, cv); ApplyEffects(_current); if (t >= 1f) { _current = _target; ApplyEffects(_current); _fading = false; } } void ApplyEffects(float s) { s = Mathf.Clamp01(s); bool active = s > 0.01f; _globalVolume.weight = s; if (active) { _dof.active = true; _dof.gaussianStart.value = _gaussianStart; _dof.gaussianEnd.value = _gaussianEnd; float radius = Mathf.Lerp(0f, _maxGaussianRadius, s); _dof.gaussianMaxRadius.value = radius; if (_color != null) { _color.active = true; _color.postExposure.value = Mathf.Lerp(_startPostExposure, _targetPostExposure, s); } } else { _dof.gaussianMaxRadius.value = 0f; _dof.active = false; if (_color != null) { _color.postExposure.value = _startPostExposure; _color.active = false; } } } public void ShowBlur() { if (_fading && _target == 1f) return; _start = _current; _target = 1f; _timer = 0f; _fading = true; } public void HideBlur() { if (_fading && _target == 0f) return; _start = _current; _target = 0f; _timer = 0f; _fading = true; } } }