168 lines
7.4 KiB
C#
168 lines
7.4 KiB
C#
using UnityEngine;
|
||
using UnityEngine.Rendering.Universal;
|
||
|
||
namespace RR.Common.Entities
|
||
{
|
||
/// <summary>
|
||
/// Управляет режимами анимации света (мигание, пульс, шум, кривая) для 2D/3D источников и эмиссивных материалов.
|
||
/// </summary>
|
||
public class LightActionsController : MonoBehaviour
|
||
{
|
||
private enum Mode { Blink, Pulse, Noise, Curve }
|
||
|
||
[Header("Target")]
|
||
[Tooltip("Список 3D-источников света, на которые будет применяться модуляция интенсивности/цвета.")]
|
||
[SerializeField] private Light[] lights3D;
|
||
[Tooltip("2D-источники URP, подконтрольные этому скрипту.")]
|
||
[SerializeField] private Light2D[] lights2D;
|
||
[Tooltip("Опционально: Renderer с материалом, чью эмиссию нужно анимировать.")]
|
||
[SerializeField] private Renderer emissiveRenderer;
|
||
private static readonly int EmissionColorID = Shader.PropertyToID("_EmissionColor");
|
||
|
||
[Header("Mode")]
|
||
[SerializeField] private Mode mode = Mode.Blink;
|
||
|
||
[Header("Intensity")]
|
||
[SerializeField] private float minIntensity = 0f;
|
||
[SerializeField] private float maxIntensity = 1f;
|
||
|
||
[Header("Timings")]
|
||
[SerializeField] private float frequency = 2f; // Гц для Pulse/Noise, миганий/сек для Blink
|
||
[SerializeField, Range(0f, 1f)] private float duty = 0.2f; // Доля времени ON для Blink [0..1]
|
||
[SerializeField] private AnimationCurve curve = AnimationCurve.EaseInOut(0, 0, 1, 1);
|
||
[SerializeField] private bool useUnscaledTime = false;
|
||
|
||
[Header("Randomness (optional)")]
|
||
[SerializeField, Range(0f, 1f)] private float noiseAmplitude = 0.3f; // для Noise (0..1)
|
||
[SerializeField] private float noiseSpeed = 1.0f; // скорость «пламени»
|
||
[SerializeField] private Vector2 blinkJitter = new(-0.1f, 0.1f); // случайный сдвиг фазы Blink
|
||
|
||
[Header("Color (optional)")]
|
||
[SerializeField] private bool modulateColor = false;
|
||
[SerializeField] private Gradient colorOverCycle;
|
||
|
||
private float _blinkOffset;
|
||
private Material _matInstance;
|
||
private Color _baseEmission = Color.white;
|
||
|
||
/// <summary>
|
||
/// Подбирает источники света в дочерних объектах при сбросе компонента.
|
||
/// </summary>
|
||
private void Reset()
|
||
{
|
||
lights3D = GetComponentsInChildren<Light>();
|
||
lights2D = GetComponentsInChildren<Light2D>();
|
||
emissiveRenderer = GetComponent<Renderer>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Кэширует экземпляр материала и рассчитывает индивидуальный сдвиг для режима мигания.
|
||
/// </summary>
|
||
private void Awake()
|
||
{
|
||
if (emissiveRenderer && emissiveRenderer.sharedMaterial && _matInstance == null)
|
||
{
|
||
_matInstance = emissiveRenderer.material;
|
||
if (_matInstance.HasProperty(EmissionColorID))
|
||
{
|
||
_baseEmission = _matInstance.GetColor(EmissionColorID);
|
||
_matInstance.EnableKeyword("_EMISSION");
|
||
}
|
||
}
|
||
_blinkOffset = Random.Range(blinkJitter.x, blinkJitter.y);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Каждый кадр пересчитывает активный режим анимации и применяет интенсивность/цвет.
|
||
/// </summary>
|
||
private void Update()
|
||
{
|
||
float time = useUnscaledTime ? Time.unscaledTime : Time.time;
|
||
|
||
float phase;
|
||
float k; // 0..1 коэффициент заполнения цикла
|
||
|
||
switch (mode)
|
||
{
|
||
case Mode.Blink:
|
||
// зубчатая волна с джиттером по фазе
|
||
phase = Mathf.Repeat((time + _blinkOffset) * Mathf.Max(0.0001f, frequency), 1f);
|
||
k = phase < Mathf.Clamp01(duty) ? 1f : 0f;
|
||
break;
|
||
|
||
case Mode.Pulse:
|
||
// синус, нормированный в диапазон 0..1
|
||
k = 0.5f * (1f + Mathf.Sin(time * Mathf.PI * 2f * frequency));
|
||
break;
|
||
|
||
case Mode.Noise:
|
||
// Перлин: базовый пульс + шумовая компонента
|
||
float u = time * noiseSpeed;
|
||
float n = Mathf.PerlinNoise(u, 0.123f); // 0..1
|
||
float baseK = 0.5f * (1f + Mathf.Sin(time * Mathf.PI * 2f * frequency)); // мягкий пульс
|
||
k = Mathf.Clamp01(baseK * (1f - noiseAmplitude) + n * noiseAmplitude);
|
||
break;
|
||
|
||
case Mode.Curve:
|
||
// нормализованный цикл 0..1, форма задаётся анимационной кривой
|
||
phase = Mathf.Repeat(time * Mathf.Max(0.0001f, frequency), 1f);
|
||
k = Mathf.Clamp01(curve.Evaluate(phase));
|
||
break;
|
||
|
||
default:
|
||
k = 1f;
|
||
break;
|
||
}
|
||
|
||
float intensity = Mathf.Lerp(minIntensity, maxIntensity, k);
|
||
ApplyIntensity(intensity);
|
||
if (modulateColor && colorOverCycle != null)
|
||
{
|
||
ApplyColor(colorOverCycle.Evaluate(k));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Применяет рассчитанную интенсивность ко всем источникам света и эмиссивному материалу.
|
||
/// </summary>
|
||
private void ApplyIntensity(float value)
|
||
{
|
||
if (lights3D != null)
|
||
foreach (var t in lights3D)
|
||
if (t) t.intensity = value;
|
||
|
||
if (lights2D != null)
|
||
foreach (var t in lights2D)
|
||
if (t) t.intensity = value;
|
||
|
||
if (_matInstance && _matInstance.HasProperty(EmissionColorID))
|
||
_matInstance.SetColor(EmissionColorID, _baseEmission * value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Модулирует цвет всех привязанных источников и эмиссии материала.
|
||
/// </summary>
|
||
private void ApplyColor(Color c)
|
||
{
|
||
if (lights3D != null)
|
||
foreach (var t in lights3D)
|
||
if (t) t.color = c;
|
||
|
||
if (lights2D != null)
|
||
foreach (var t in lights2D)
|
||
if (t) t.color = c;
|
||
|
||
if (_matInstance && _matInstance.HasProperty(EmissionColorID))
|
||
_matInstance.SetColor(EmissionColorID, c * Mathf.Max(0.0001f, _matInstance.GetColor(EmissionColorID).maxColorComponent));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Освобождает созданный экземпляр материала, чтобы избежать утечек.
|
||
/// </summary>
|
||
private void OnDestroy()
|
||
{
|
||
if (_matInstance) Destroy(_matInstance);
|
||
}
|
||
}
|
||
}
|