Files

257 lines
6.9 KiB
C#

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<Image>();
}
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;
}
}
}