Restructured for new direction.

This commit is contained in:
2026-05-12 12:01:09 +01:00
parent 0439b6c1d2
commit c203f836b1
1134 changed files with 125569 additions and 213519 deletions

View File

@@ -0,0 +1,499 @@
using System;
using System.Collections.Generic;
using System.Threading;
using BriarQueen.Framework.Services.Destruction;
using Cysharp.Threading.Tasks;
using PrimeTween;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using VContainer;
namespace BriarQueen.Framework.Effects
{
[ExecuteAlways]
public class UIDissolveImage : MonoBehaviour
{
private static readonly int _dissolveAmountId = Shader.PropertyToID("_DissolveAmount");
private static readonly int _reverseDirectionId = Shader.PropertyToID("_ReverseDirection");
[Header("References")]
[SerializeField]
private Image _image;
[SerializeField]
private CanvasGroup _canvasGroup;
[SerializeField]
private Material _dissolveMaterialTemplate;
[Header("Targeting")]
[SerializeField]
private bool _dissolveChildGraphics;
[SerializeField]
private bool _includeInactiveChildGraphics = true;
[SerializeField]
private bool _includeTextGraphics;
[Header("Tween")]
[SerializeField]
private float _duration = 0.75f;
[SerializeField]
private Ease _ease = Ease.InOutSine;
[SerializeField]
private bool _useUnscaledTime = true;
[Header("Direction")]
[SerializeField]
private bool _reverseDirection;
[Header("Destruction")]
[SerializeField]
private bool _destroyWhenFullyDissolved = true;
[Header("Editor Preview")]
[SerializeField]
private bool _previewInEditMode;
[SerializeField]
[Range(0f, 1f)]
private float _previewDissolveAmount;
private readonly List<Graphic> _targetGraphics = new();
private readonly List<Material> _originalMaterials = new();
private CancellationTokenSource _dissolveCts;
private DestructionService _destructionService;
private bool _hasOriginalMaterials;
private bool _isPreviewMaterial;
private Material _runtimeMaterial;
private Sequence _dissolveSequence;
public float DissolveAmount
{
get
{
if (_runtimeMaterial == null)
{
return 0f;
}
return _runtimeMaterial.GetFloat(_dissolveAmountId);
}
set => SetDissolveAmount(value);
}
[Inject]
public void Construct(DestructionService destructionService)
{
_destructionService = destructionService;
}
private void Awake()
{
ResolveReferences();
if (Application.isPlaying)
{
CreateRuntimeMaterial(false);
SetDissolveAmount(0f);
return;
}
#if UNITY_EDITOR
RefreshEditModePreview();
#endif
}
private void OnEnable()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
RefreshEditModePreview();
}
#endif
}
private void OnDisable()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
RestoreEditModeMaterial();
}
#endif
}
private void OnDestroy()
{
CancelDissolve();
if (Application.isPlaying)
{
DestroyRuntimeMaterial(false);
return;
}
#if UNITY_EDITOR
RestoreEditModeMaterial();
#endif
}
private void OnValidate()
{
ResolveReferences();
if (Application.isPlaying)
{
SetReverseDirection(_reverseDirection);
return;
}
#if UNITY_EDITOR
RestoreEditModeMaterial();
RefreshEditModePreview();
#endif
}
public UniTask DissolveIn()
{
gameObject.SetActive(true);
return TweenDissolve(1f, 0f, _duration, false);
}
public UniTask DissolveOut()
{
return TweenDissolve(0f, 1f, _duration, _destroyWhenFullyDissolved);
}
public UniTask DissolveOut(bool destroyWhenComplete)
{
return TweenDissolve(0f, 1f, _duration, destroyWhenComplete);
}
public UniTask DissolveOutAndDestroy()
{
return TweenDissolve(0f, 1f, _duration, true);
}
public UniTask TweenDissolve(float from, float to, float duration)
{
return TweenDissolve(from, to, duration, false);
}
public async UniTask TweenDissolve(float from, float to, float duration, bool destroyWhenComplete)
{
if (_runtimeMaterial == null)
{
CreateRuntimeMaterial(false);
}
CancelDissolve();
_dissolveCts = new CancellationTokenSource();
SetDissolveAmount(from);
_dissolveSequence = Sequence.Create(useUnscaledTime: _useUnscaledTime)
.Group(Tween.Custom(
from,
to,
Mathf.Max(0f, duration),
SetDissolveAmount,
_ease,
useUnscaledTime: _useUnscaledTime));
try
{
await _dissolveSequence.ToUniTask(cancellationToken: _dissolveCts.Token);
SetDissolveAmount(to);
if (destroyWhenComplete && Mathf.Approximately(to, 1f))
{
await DestroyAfterDissolve();
}
}
catch (OperationCanceledException)
{
// Interrupted by another dissolve request or object destruction.
}
finally
{
_dissolveSequence = default;
if (_dissolveCts != null)
{
_dissolveCts.Dispose();
_dissolveCts = null;
}
}
}
public void SetDissolveAmount(float amount)
{
if (_runtimeMaterial == null)
{
return;
}
_runtimeMaterial.SetFloat(_dissolveAmountId, Mathf.Clamp01(amount));
SetTargetsMaterialDirty();
}
public void SetReverseDirection(bool reverseDirection)
{
_reverseDirection = reverseDirection;
if (_runtimeMaterial == null)
{
return;
}
_runtimeMaterial.SetFloat(_reverseDirectionId, _reverseDirection ? 1f : 0f);
SetTargetsMaterialDirty();
}
public void CancelDissolve()
{
if (_dissolveSequence.isAlive)
{
_dissolveSequence.Stop();
_dissolveSequence = default;
}
if (_dissolveCts != null)
{
_dissolveCts.Cancel();
_dissolveCts.Dispose();
_dissolveCts = null;
}
}
private async UniTask DestroyAfterDissolve()
{
if (_destructionService != null)
{
await _destructionService.Destroy(gameObject);
return;
}
Debug.LogWarning($"[{nameof(UIDissolveImage)}] Missing {nameof(DestructionService)}. Destroying directly.");
Destroy(gameObject);
}
private void ResolveReferences()
{
if (_image == null)
{
_image = GetComponent<Image>();
}
if (_canvasGroup == null)
{
_canvasGroup = GetComponent<CanvasGroup>();
}
}
private void ResolveTargetGraphics()
{
ResolveReferences();
_targetGraphics.Clear();
if (_dissolveChildGraphics)
{
var root = _canvasGroup != null ? _canvasGroup.transform : transform;
var graphics = root.GetComponentsInChildren<Graphic>(_includeInactiveChildGraphics);
foreach (var graphic in graphics)
{
if (IsValidTargetGraphic(graphic))
{
_targetGraphics.Add(graphic);
}
}
return;
}
if (_image != null)
{
_targetGraphics.Add(_image);
}
}
private bool IsValidTargetGraphic(Graphic graphic)
{
if (graphic == null)
{
return false;
}
// TMP needs a TMP-compatible dissolve shader; replacing its SDF material breaks text rendering.
if (!_includeTextGraphics && graphic is TMP_Text)
{
return false;
}
return true;
}
private void CreateRuntimeMaterial(bool isPreviewMaterial)
{
ResolveTargetGraphics();
if (_targetGraphics.Count == 0)
{
return;
}
var sourceMaterial = _dissolveMaterialTemplate != null
? _dissolveMaterialTemplate
: _targetGraphics[0].material;
if (sourceMaterial == null)
{
return;
}
if (_runtimeMaterial != null)
{
ApplyRuntimeMaterialToTargets();
return;
}
SaveOriginalMaterials();
_runtimeMaterial = Instantiate(sourceMaterial);
_runtimeMaterial.name = isPreviewMaterial
? $"{nameof(UIDissolveImage)} Preview Material"
: $"{nameof(UIDissolveImage)} Runtime Material";
_isPreviewMaterial = isPreviewMaterial;
if (isPreviewMaterial)
{
_runtimeMaterial.hideFlags = HideFlags.DontSaveInEditor;
}
ApplyRuntimeMaterialToTargets();
SetReverseDirection(_reverseDirection);
}
private void ApplyRuntimeMaterialToTargets()
{
foreach (var graphic in _targetGraphics)
{
if (graphic == null)
{
continue;
}
graphic.material = _runtimeMaterial;
graphic.SetMaterialDirty();
}
}
private void SaveOriginalMaterials()
{
_originalMaterials.Clear();
foreach (var graphic in _targetGraphics)
{
_originalMaterials.Add(graphic != null ? graphic.material : null);
}
_hasOriginalMaterials = true;
}
private void RestoreOriginalMaterials()
{
if (!_hasOriginalMaterials)
{
return;
}
var count = Mathf.Min(_targetGraphics.Count, _originalMaterials.Count);
for (var i = 0; i < count; i++)
{
var graphic = _targetGraphics[i];
if (graphic == null)
{
continue;
}
graphic.material = _originalMaterials[i];
graphic.SetMaterialDirty();
}
_originalMaterials.Clear();
_hasOriginalMaterials = false;
}
private void SetTargetsMaterialDirty()
{
foreach (var graphic in _targetGraphics)
{
if (graphic != null)
{
graphic.SetMaterialDirty();
}
}
}
private void DestroyRuntimeMaterial(bool immediate)
{
if (_runtimeMaterial == null)
{
return;
}
RestoreOriginalMaterials();
if (immediate)
{
DestroyImmediate(_runtimeMaterial);
}
else
{
Destroy(_runtimeMaterial);
}
_runtimeMaterial = null;
_isPreviewMaterial = false;
}
#if UNITY_EDITOR
private void RefreshEditModePreview()
{
if (Application.isPlaying)
{
return;
}
if (!_previewInEditMode)
{
RestoreEditModeMaterial();
return;
}
CreateRuntimeMaterial(true);
SetReverseDirection(_reverseDirection);
SetDissolveAmount(_previewDissolveAmount);
}
private void RestoreEditModeMaterial()
{
if (!_isPreviewMaterial)
{
return;
}
DestroyRuntimeMaterial(true);
}
#endif
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e1ca11eb5d39d4da19232afd2e808c96

View File

@@ -0,0 +1,502 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using PrimeTween;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace BriarQueen.Framework.Effects
{
[ExecuteAlways]
public class UIEdgeDarken : MonoBehaviour
{
private static readonly int _amountId = Shader.PropertyToID("_Amount");
private static readonly int _centerDarknessId = Shader.PropertyToID("_CenterDarkness");
private static readonly int _colorId = Shader.PropertyToID("_Color");
private static readonly int _edgeDarknessId = Shader.PropertyToID("_EdgeDarkness");
private static readonly int _edgeWidthId = Shader.PropertyToID("_EdgeWidth");
private static readonly int _rectPivotId = Shader.PropertyToID("_RectPivot");
private static readonly int _rectSizeId = Shader.PropertyToID("_RectSize");
private static readonly int _softnessId = Shader.PropertyToID("_Softness");
[Header("References")]
[SerializeField]
private Graphic _graphic;
[SerializeField]
private CanvasGroup _canvasGroup;
[SerializeField]
private Material _edgeDarkenMaterialTemplate;
[Header("Targeting")]
[SerializeField]
private bool _targetChildGraphics;
[SerializeField]
private bool _includeInactiveChildGraphics = true;
[SerializeField]
private bool _includeTextGraphics;
[Header("Darken")]
[SerializeField]
[Range(0f, 1f)]
private float _amount = 1f;
[SerializeField]
private Color _color = new(0f, 0f, 0f, 0.65f);
[SerializeField]
[Range(0f, 1f)]
private float _edgeDarkness = 1f;
[SerializeField]
[Range(0f, 1f)]
private float _centerDarkness;
[SerializeField]
[Range(0.001f, 0.5f)]
private float _edgeWidth = 0.22f;
[SerializeField]
[Range(0.001f, 0.5f)]
private float _softness = 0.18f;
[Header("Tween")]
[SerializeField]
private float _duration = 0.35f;
[SerializeField]
private Ease _ease = Ease.InOutSine;
[SerializeField]
private bool _useUnscaledTime = true;
[Header("Editor Preview")]
[SerializeField]
private bool _previewInEditMode;
[SerializeField]
[Range(0f, 1f)]
private float _previewAmount = 1f;
private readonly List<Graphic> _targetGraphics = new();
private readonly List<Material> _originalMaterials = new();
private readonly List<Material> _runtimeMaterials = new();
private CancellationTokenSource _darkenCts;
private bool _hasOriginalMaterials;
private bool _isPreviewMaterial;
private Sequence _darkenSequence;
public float Amount
{
get => _amount;
set => SetAmount(value);
}
private void Awake()
{
ResolveReferences();
if (Application.isPlaying)
{
CreateRuntimeMaterial(false);
ApplyMaterialProperties(_amount);
return;
}
#if UNITY_EDITOR
RefreshEditModePreview();
#endif
}
private void OnEnable()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
RefreshEditModePreview();
}
#endif
}
private void OnDisable()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
RestoreEditModeMaterial();
}
#endif
}
private void OnDestroy()
{
CancelTween();
if (Application.isPlaying)
{
DestroyRuntimeMaterial(false);
return;
}
#if UNITY_EDITOR
RestoreEditModeMaterial();
#endif
}
private void OnValidate()
{
ResolveReferences();
if (Application.isPlaying)
{
ApplyMaterialProperties(_amount);
return;
}
#if UNITY_EDITOR
RestoreEditModeMaterial();
RefreshEditModePreview();
#endif
}
public UniTask DarkenIn()
{
return TweenAmount(0f, 1f, _duration);
}
public UniTask DarkenOut()
{
return TweenAmount(1f, 0f, _duration);
}
public async UniTask TweenAmount(float from, float to, float duration)
{
if (_runtimeMaterials.Count == 0)
{
CreateRuntimeMaterial(false);
}
CancelTween();
_darkenCts = new CancellationTokenSource();
SetAmount(from);
_darkenSequence = Sequence.Create(useUnscaledTime: _useUnscaledTime)
.Group(Tween.Custom(
from,
to,
Mathf.Max(0f, duration),
SetAmount,
_ease,
useUnscaledTime: _useUnscaledTime));
try
{
await _darkenSequence.ToUniTask(cancellationToken: _darkenCts.Token);
SetAmount(to);
}
catch (OperationCanceledException)
{
// Interrupted by another darken request or object destruction.
}
finally
{
_darkenSequence = default;
if (_darkenCts != null)
{
_darkenCts.Dispose();
_darkenCts = null;
}
}
}
public void SetAmount(float amount)
{
_amount = Mathf.Clamp01(amount);
ApplyMaterialProperties(_amount);
}
public void CancelTween()
{
if (_darkenSequence.isAlive)
{
_darkenSequence.Stop();
_darkenSequence = default;
}
if (_darkenCts != null)
{
_darkenCts.Cancel();
_darkenCts.Dispose();
_darkenCts = null;
}
}
private void ResolveReferences()
{
if (_graphic == null)
{
_graphic = GetComponent<Graphic>();
}
if (_canvasGroup == null)
{
_canvasGroup = GetComponent<CanvasGroup>();
}
}
private void ResolveTargetGraphics()
{
ResolveReferences();
_targetGraphics.Clear();
if (_targetChildGraphics)
{
var root = _canvasGroup != null ? _canvasGroup.transform : transform;
var graphics = root.GetComponentsInChildren<Graphic>(_includeInactiveChildGraphics);
foreach (var graphic in graphics)
{
if (IsValidTargetGraphic(graphic))
{
_targetGraphics.Add(graphic);
}
}
return;
}
if (_graphic != null)
{
_targetGraphics.Add(_graphic);
}
}
private bool IsValidTargetGraphic(Graphic graphic)
{
if (graphic == null)
{
return false;
}
// TMP needs a TMP-compatible shader; replacing its SDF material breaks text rendering.
if (!_includeTextGraphics && graphic is TMP_Text)
{
return false;
}
return true;
}
private void CreateRuntimeMaterial(bool isPreviewMaterial)
{
ResolveTargetGraphics();
if (_targetGraphics.Count == 0)
{
return;
}
var sourceMaterial = _edgeDarkenMaterialTemplate != null
? _edgeDarkenMaterialTemplate
: _targetGraphics[0].material;
if (sourceMaterial == null)
{
return;
}
if (_runtimeMaterials.Count > 0)
{
ApplyRuntimeMaterialToTargets();
return;
}
SaveOriginalMaterials();
_isPreviewMaterial = isPreviewMaterial;
foreach (var graphic in _targetGraphics)
{
if (graphic == null)
{
_runtimeMaterials.Add(null);
continue;
}
var runtimeMaterial = Instantiate(sourceMaterial);
runtimeMaterial.name = isPreviewMaterial
? $"{nameof(UIEdgeDarken)} Preview Material"
: $"{nameof(UIEdgeDarken)} Runtime Material";
if (isPreviewMaterial)
{
runtimeMaterial.hideFlags = HideFlags.DontSaveInEditor;
}
_runtimeMaterials.Add(runtimeMaterial);
}
ApplyRuntimeMaterialToTargets();
}
private void ApplyRuntimeMaterialToTargets()
{
var count = Mathf.Min(_targetGraphics.Count, _runtimeMaterials.Count);
for (var i = 0; i < count; i++)
{
var graphic = _targetGraphics[i];
if (graphic == null)
{
continue;
}
graphic.material = _runtimeMaterials[i];
graphic.SetMaterialDirty();
}
}
private void ApplyMaterialProperties(float amount)
{
if (_runtimeMaterials.Count == 0)
{
return;
}
var count = Mathf.Min(_targetGraphics.Count, _runtimeMaterials.Count);
for (var i = 0; i < count; i++)
{
var material = _runtimeMaterials[i];
var graphic = _targetGraphics[i];
if (material == null || graphic == null)
{
continue;
}
var rectTransform = graphic.rectTransform;
material.SetFloat(_amountId, Mathf.Clamp01(amount));
material.SetColor(_colorId, _color);
material.SetFloat(_edgeDarknessId, _edgeDarkness);
material.SetFloat(_centerDarknessId, _centerDarkness);
material.SetFloat(_edgeWidthId, _edgeWidth);
material.SetFloat(_softnessId, _softness);
material.SetVector(_rectSizeId, rectTransform.rect.size);
material.SetVector(_rectPivotId, rectTransform.pivot);
}
SetTargetsMaterialDirty();
}
private void SaveOriginalMaterials()
{
_originalMaterials.Clear();
foreach (var graphic in _targetGraphics)
{
_originalMaterials.Add(graphic != null ? graphic.material : null);
}
_hasOriginalMaterials = true;
}
private void RestoreOriginalMaterials()
{
if (!_hasOriginalMaterials)
{
return;
}
var count = Mathf.Min(_targetGraphics.Count, _originalMaterials.Count);
for (var i = 0; i < count; i++)
{
var graphic = _targetGraphics[i];
if (graphic == null)
{
continue;
}
graphic.material = _originalMaterials[i];
graphic.SetMaterialDirty();
}
_originalMaterials.Clear();
_hasOriginalMaterials = false;
}
private void SetTargetsMaterialDirty()
{
foreach (var graphic in _targetGraphics)
{
if (graphic != null)
{
graphic.SetMaterialDirty();
}
}
}
private void DestroyRuntimeMaterial(bool immediate)
{
if (_runtimeMaterials.Count == 0)
{
return;
}
RestoreOriginalMaterials();
foreach (var runtimeMaterial in _runtimeMaterials)
{
if (runtimeMaterial == null)
{
continue;
}
if (immediate)
{
DestroyImmediate(runtimeMaterial);
}
else
{
Destroy(runtimeMaterial);
}
}
_runtimeMaterials.Clear();
_isPreviewMaterial = false;
}
#if UNITY_EDITOR
private void RefreshEditModePreview()
{
if (Application.isPlaying)
{
return;
}
if (!_previewInEditMode)
{
RestoreEditModeMaterial();
return;
}
CreateRuntimeMaterial(true);
ApplyMaterialProperties(_previewAmount);
}
private void RestoreEditModeMaterial()
{
if (!_isPreviewMaterial)
{
return;
}
DestroyRuntimeMaterial(true);
}
#endif
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7cb0728fb51e41b2aa93ec51993d9150
timeCreated: 1770379821

View File

@@ -0,0 +1,399 @@
using System;
using System.Threading;
using BriarQueen.Framework.Services.Destruction;
using Cysharp.Threading.Tasks;
using PrimeTween;
using UnityEngine;
using UnityEngine.UI;
using VContainer;
namespace BriarQueen.Game.Effects
{
[ExecuteAlways]
public class UIFogReveal : MonoBehaviour
{
// ── Shader property IDs ───────────────────────────────────────
private static readonly int _fogAmountId = Shader.PropertyToID("_FogAmount");
private static readonly int _fogColorId = Shader.PropertyToID("_FogColor");
private static readonly int _fogIntensityId = Shader.PropertyToID("_FogIntensity");
private static readonly int _edgeSoftnessId = Shader.PropertyToID("_EdgeSoftness");
private static readonly int _noiseScaleId = Shader.PropertyToID("_NoiseScale");
private static readonly int _driftSpeedId = Shader.PropertyToID("_DriftSpeed");
private static readonly int _densityVariationId = Shader.PropertyToID("_DensityVariation");
private static readonly int _aspectRatioId = Shader.PropertyToID("_AspectRatio");
private static readonly int _fogMotionId = Shader.PropertyToID("_FogMotion");
private static readonly int _useSpriteId = Shader.PropertyToID("_UseSprite");
// ── Inspector ─────────────────────────────────────────────────
[Header("References")]
[SerializeField] private Image _image;
[SerializeField] private Material _fogMaterialTemplate;
[Header("Fog Settings")]
[SerializeField] private Color _fogColor = new(0.18f, 0.20f, 0.26f, 1f);
[SerializeField][Range(0f, 1f)] private float _fogIntensity = 0.95f;
[SerializeField][Range(0.05f, 1f)] private float _edgeSoftness = 0.55f;
[SerializeField][Range(1f, 20f)] private float _noiseScale = 5f;
[SerializeField][Range(0f, 0.5f)] private float _driftSpeed = 0.04f;
[SerializeField][Range(0f, 1f)] private float _densityVariation = 0.28f;
[SerializeField] private bool _fogMotion = true;
[SerializeField] private bool _useSprite = false;
[Header("Fog Range")]
[SerializeField][Range(0f, 1f)] private float _startFog = 0f;
[SerializeField][Range(0f, 1f)] private float _maxFog = 0.6f;
[Header("Screen")]
[SerializeField] private bool _autoAspectRatio = true;
[SerializeField] private float _aspectRatio = 1.777f;
[Header("Tween")]
[SerializeField] private float _duration = 1.5f;
[SerializeField] private Ease _ease = Ease.InOutSine;
[SerializeField] private bool _useUnscaledTime = true;
[Header("Delay")]
[SerializeField][Range(0f, 5f)] private float _fogInDelay = 0f;
[SerializeField][Range(0f, 5f)] private float _fogOutDelay = 0f;
[Header("Editor Preview")]
[SerializeField] private bool _previewInEditMode;
[SerializeField][Range(0f, 1f)] private float _previewFogAmount;
// ── Runtime state ─────────────────────────────────────────────
private Material _runtimeMaterial;
private bool _isPreviewMaterial;
private Sequence _fogSequence;
private CancellationTokenSource _fogCts;
private DestructionService _destructionService;
// ── Public properties ─────────────────────────────────────────
public float FogAmount
{
get => _runtimeMaterial != null ? _runtimeMaterial.GetFloat(_fogAmountId) : 0f;
set => SetFogAmount(value);
}
public float FogInDuration => _fogInDelay + _duration;
public float FogOutDuration => _fogOutDelay + _duration;
public bool FogMotion
{
get => _fogMotion;
set
{
_fogMotion = value;
_runtimeMaterial?.SetFloat(_fogMotionId, value ? 1f : 0f);
}
}
public bool UseSprite
{
get => _useSprite;
set
{
_useSprite = value;
_runtimeMaterial?.SetFloat(_useSpriteId, value ? 1f : 0f);
}
}
public float MaxFog
{
get => _maxFog;
set => _maxFog = value;
}
// ── DI ────────────────────────────────────────────────────────
[Inject]
public void Construct(DestructionService destructionService)
{
_destructionService = destructionService;
}
// ── Unity lifecycle ───────────────────────────────────────────
private void Awake()
{
ResolveReferences();
if (Application.isPlaying)
{
CreateRuntimeMaterial(false);
SetFogAmount(_startFog);
return;
}
#if UNITY_EDITOR
RefreshEditModePreview();
#endif
}
private void OnEnable()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
RefreshEditModePreview();
#endif
}
private void OnDisable()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
RestoreEditModeMaterial();
#endif
}
private void Update()
{
if (!Application.isPlaying) return;
if (!_autoAspectRatio) return;
var canvas = GetComponentInParent<Canvas>();
if (canvas == null) return;
var rt = canvas.GetComponent<RectTransform>();
var ratio = rt.rect.width / Mathf.Max(rt.rect.height, 0.001f);
_runtimeMaterial?.SetFloat(_aspectRatioId, ratio);
}
private void OnDestroy()
{
CancelFog();
if (Application.isPlaying)
{
DestroyRuntimeMaterial(false);
return;
}
#if UNITY_EDITOR
RestoreEditModeMaterial();
#endif
}
private void OnValidate()
{
ResolveReferences();
if (Application.isPlaying)
{
ApplyShaderSettings();
return;
}
#if UNITY_EDITOR
RestoreEditModeMaterial();
RefreshEditModePreview();
#endif
}
// ── Public API ────────────────────────────────────────────────
/// <summary>Animate fog in (startFog → maxFog). Respects _fogInDelay.</summary>
public UniTask FogIn() => TweenFogWithDelay(_startFog, _maxFog, _duration, _fogInDelay);
/// <summary>Animate fog in over a custom duration, no delay.</summary>
public UniTask FogIn(float duration) => TweenFog(_startFog, _maxFog, duration);
/// <summary>Animate fog out (maxFog → startFog). Respects _fogOutDelay.</summary>
public UniTask FogOut() => TweenFogWithDelay(_maxFog, _startFog, _duration, _fogOutDelay);
/// <summary>Animate fog out over a custom duration, no delay.</summary>
public UniTask FogOut(float duration) => TweenFog(_maxFog, _startFog, duration);
/// <summary>Animate fog to an arbitrary target amount, no delay.</summary>
public UniTask FogTo(float target, float duration) => TweenFog(FogAmount, target, duration);
/// <summary>Snap fog to a value immediately, cancels any running tween.</summary>
public void FogSet(float amount)
{
CancelFog();
SetFogAmount(amount);
}
/// <summary>Snap fog to startFog immediately.</summary>
public void FogReset()
{
CancelFog();
SetFogAmount(_startFog);
}
public void CancelFog()
{
if (_fogSequence.isAlive)
{
_fogSequence.Stop();
_fogSequence = default;
}
if (_fogCts != null)
{
_fogCts.Cancel();
_fogCts.Dispose();
_fogCts = null;
}
}
public async UniTask TweenFog(float from, float to, float duration)
{
await TweenFogWithDelay(from, to, duration, 0f);
}
public async UniTask TweenFogWithDelay(float from, float to, float duration, float delay)
{
if (_runtimeMaterial == null)
CreateRuntimeMaterial(false);
CancelFog();
_fogCts = new CancellationTokenSource();
SetFogAmount(from);
try
{
if (delay > 0f)
{
await UniTask.Delay(
TimeSpan.FromSeconds(delay),
ignoreTimeScale: _useUnscaledTime,
cancellationToken: _fogCts.Token);
}
_fogSequence = Sequence.Create(useUnscaledTime: _useUnscaledTime)
.Group(Tween.Custom(
from,
to,
Mathf.Max(0f, duration),
SetFogAmount,
_ease,
useUnscaledTime: _useUnscaledTime));
await _fogSequence.ToUniTask(cancellationToken: _fogCts.Token);
SetFogAmount(to);
}
catch (OperationCanceledException)
{
// Interrupted — normal, swallow it.
}
finally
{
_fogSequence = default;
if (_fogCts != null)
{
_fogCts.Dispose();
_fogCts = null;
}
}
}
// ── Internal ──────────────────────────────────────────────────
private void SetFogAmount(float amount)
{
if (_runtimeMaterial == null) return;
_runtimeMaterial.SetFloat(_fogAmountId, Mathf.Clamp01(amount));
_image?.SetMaterialDirty();
}
private void ApplyShaderSettings()
{
if (_runtimeMaterial == null) return;
_runtimeMaterial.SetColor(_fogColorId, _fogColor);
_runtimeMaterial.SetFloat(_fogIntensityId, _fogIntensity);
_runtimeMaterial.SetFloat(_edgeSoftnessId, _edgeSoftness);
_runtimeMaterial.SetFloat(_noiseScaleId, _noiseScale);
_runtimeMaterial.SetFloat(_driftSpeedId, _driftSpeed);
_runtimeMaterial.SetFloat(_densityVariationId, _densityVariation);
_runtimeMaterial.SetFloat(_fogMotionId, _fogMotion ? 1f : 0f);
_runtimeMaterial.SetFloat(_useSpriteId, _useSprite ? 1f : 0f);
if (!_autoAspectRatio)
_runtimeMaterial.SetFloat(_aspectRatioId, _aspectRatio);
_image?.SetMaterialDirty();
}
private void ResolveReferences()
{
if (_image == null)
_image = GetComponent<Image>();
}
private void CreateRuntimeMaterial(bool isPreviewMaterial)
{
if (_image == null) return;
var source = _fogMaterialTemplate != null
? _fogMaterialTemplate
: _image.material;
if (source == null) return;
if (_runtimeMaterial != null)
{
_image.material = _runtimeMaterial;
_image.SetMaterialDirty();
return;
}
_runtimeMaterial = Instantiate(source);
_runtimeMaterial.name = isPreviewMaterial
? $"{nameof(UIFogReveal)} Preview Material"
: $"{nameof(UIFogReveal)} Runtime Material";
_isPreviewMaterial = isPreviewMaterial;
if (isPreviewMaterial)
_runtimeMaterial.hideFlags = HideFlags.DontSaveInEditor;
_image.material = _runtimeMaterial;
_image.SetMaterialDirty();
ApplyShaderSettings();
}
private void DestroyRuntimeMaterial(bool immediate)
{
if (_runtimeMaterial == null) return;
if (_image != null)
{
_image.material = null;
_image.SetMaterialDirty();
}
if (immediate)
DestroyImmediate(_runtimeMaterial);
else
Destroy(_runtimeMaterial);
_runtimeMaterial = null;
_isPreviewMaterial = false;
}
#if UNITY_EDITOR
private void RefreshEditModePreview()
{
if (Application.isPlaying) return;
if (!_previewInEditMode)
{
RestoreEditModeMaterial();
return;
}
CreateRuntimeMaterial(true);
ApplyShaderSettings();
SetFogAmount(_previewFogAmount);
}
private void RestoreEditModeMaterial()
{
if (!_isPreviewMaterial) return;
DestroyRuntimeMaterial(true);
}
#endif
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 912c8bc1a5f84113848a078f0581c8ce
timeCreated: 1778334335

View File

@@ -0,0 +1,249 @@
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using PrimeTween;
using UnityEngine;
using UnityEngine.UI;
namespace BriarQueen.Framework.Effects
{
[RequireComponent(typeof(Image))]
public class UILightGlow : MonoBehaviour
{
private const string _lightShaderName = "BriarQueen/UI/Light Glow";
private static readonly int _lightColorId = Shader.PropertyToID("_LightColor");
private static readonly int _intensityId = Shader.PropertyToID("_Intensity");
private static readonly int _flickerOffsetId = Shader.PropertyToID("_FlickerOffset");
[Header("References")]
[SerializeField]
private Image _image;
[SerializeField]
private Material _lightMaterialTemplate;
[Header("Light")]
[SerializeField]
private Color _startingColor = new(1f, 0.78f, 0.35f, 1f);
[SerializeField]
private float _startingIntensity = 1.5f;
[SerializeField]
private bool _randomizeFlickerOffset = true;
[Header("Tween")]
[SerializeField]
private float _defaultTweenDuration = 0.35f;
[SerializeField]
private Ease _ease = Ease.InOutSine;
[SerializeField]
private bool _useUnscaledTime = true;
private Material _runtimeMaterial;
private Sequence _lightSequence;
private CancellationTokenSource _lightCts;
public Color LightColor
{
get
{
if (_runtimeMaterial == null)
{
return _startingColor;
}
return _runtimeMaterial.GetColor(_lightColorId);
}
}
public float Intensity
{
get
{
if (_runtimeMaterial == null)
{
return _startingIntensity;
}
return _runtimeMaterial.GetFloat(_intensityId);
}
}
private void Awake()
{
if (_image == null)
{
_image = GetComponent<Image>();
}
CreateRuntimeMaterial();
SetLightColor(_startingColor);
SetIntensity(_startingIntensity);
if (_randomizeFlickerOffset && _runtimeMaterial != null)
{
_runtimeMaterial.SetFloat(_flickerOffsetId, UnityEngine.Random.Range(0f, 100f));
}
}
private void OnDestroy()
{
CancelTween();
if (_runtimeMaterial != null)
{
Destroy(_runtimeMaterial);
_runtimeMaterial = null;
}
}
public UniTask ChangeColor(Color targetColor)
{
return ChangeColor(targetColor, _defaultTweenDuration);
}
public UniTask ChangeColor(Color targetColor, float duration)
{
return TweenTo(targetColor, Intensity, duration);
}
public UniTask ChangeIntensity(float targetIntensity)
{
return ChangeIntensity(targetIntensity, _defaultTweenDuration);
}
public UniTask ChangeIntensity(float targetIntensity, float duration)
{
return TweenTo(LightColor, targetIntensity, duration);
}
public UniTask TurnOff()
{
return ChangeIntensity(0f, _defaultTweenDuration);
}
public UniTask TurnOn()
{
return ChangeIntensity(_startingIntensity, _defaultTweenDuration);
}
public async UniTask TweenTo(Color targetColor, float targetIntensity, float duration)
{
if (_runtimeMaterial == null)
{
CreateRuntimeMaterial();
}
CancelTween();
var fromColor = LightColor;
var fromIntensity = Intensity;
var safeDuration = Mathf.Max(0f, duration);
_lightCts = new CancellationTokenSource();
_lightSequence = Sequence.Create(useUnscaledTime: _useUnscaledTime)
.Group(Tween.Custom(
0f,
1f,
safeDuration,
progress =>
{
SetLightColor(Color.LerpUnclamped(fromColor, targetColor, progress));
SetIntensity(Mathf.LerpUnclamped(fromIntensity, targetIntensity, progress));
},
_ease,
useUnscaledTime: _useUnscaledTime));
try
{
await _lightSequence.ToUniTask(cancellationToken: _lightCts.Token);
SetLightColor(targetColor);
SetIntensity(targetIntensity);
}
catch (OperationCanceledException)
{
// Interrupted by another light tween or object destruction.
}
finally
{
_lightSequence = default;
if (_lightCts != null)
{
_lightCts.Dispose();
_lightCts = null;
}
}
}
public void SetLightColor(Color color)
{
if (_runtimeMaterial == null)
{
return;
}
_runtimeMaterial.SetColor(_lightColorId, color);
}
public void SetIntensity(float intensity)
{
if (_runtimeMaterial == null)
{
return;
}
_runtimeMaterial.SetFloat(_intensityId, Mathf.Max(0f, intensity));
}
public void CancelTween()
{
if (_lightSequence.isAlive)
{
_lightSequence.Stop();
_lightSequence = default;
}
if (_lightCts != null)
{
_lightCts.Cancel();
_lightCts.Dispose();
_lightCts = null;
}
}
private void CreateRuntimeMaterial()
{
if (_image == null)
{
return;
}
if (_lightMaterialTemplate != null)
{
_runtimeMaterial = Instantiate(_lightMaterialTemplate);
_image.material = _runtimeMaterial;
return;
}
var shader = Shader.Find(_lightShaderName);
if (shader == null)
{
Debug.LogWarning($"[{nameof(UILightGlow)}] Could not find shader '{_lightShaderName}'.");
return;
}
_runtimeMaterial = new Material(shader)
{
name = $"{nameof(UILightGlow)} Runtime Material"
};
_image.material = _runtimeMaterial;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d3e14c91f3c942fc84e800d2fb583fb0