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; [SerializeField] private bool _useStartingValues; [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(); } CreateRuntimeMaterial(); if(_useStartingValues) { 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; } } }