Restructured for new direction.
This commit is contained in:
499
Assets/Scripts/Framework/Effects/UIDissolveImage.cs
Normal file
499
Assets/Scripts/Framework/Effects/UIDissolveImage.cs
Normal 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
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Framework/Effects/UIDissolveImage.cs.meta
Normal file
2
Assets/Scripts/Framework/Effects/UIDissolveImage.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1ca11eb5d39d4da19232afd2e808c96
|
||||
502
Assets/Scripts/Framework/Effects/UIEdgeDarken.cs
Normal file
502
Assets/Scripts/Framework/Effects/UIEdgeDarken.cs
Normal 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
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Framework/Effects/UIEdgeDarken.cs.meta
Normal file
3
Assets/Scripts/Framework/Effects/UIEdgeDarken.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7cb0728fb51e41b2aa93ec51993d9150
|
||||
timeCreated: 1770379821
|
||||
399
Assets/Scripts/Framework/Effects/UIFogReveal.cs
Normal file
399
Assets/Scripts/Framework/Effects/UIFogReveal.cs
Normal 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
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Framework/Effects/UIFogReveal.cs.meta
Normal file
3
Assets/Scripts/Framework/Effects/UIFogReveal.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 912c8bc1a5f84113848a078f0581c8ce
|
||||
timeCreated: 1778334335
|
||||
249
Assets/Scripts/Framework/Effects/UILightGlow.cs
Normal file
249
Assets/Scripts/Framework/Effects/UILightGlow.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Framework/Effects/UILightGlow.cs.meta
Normal file
2
Assets/Scripts/Framework/Effects/UILightGlow.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3e14c91f3c942fc84e800d2fb583fb0
|
||||
Reference in New Issue
Block a user