Restructured for new direction.
This commit is contained in:
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user