Danro/Assets/Scripts/Entities/LightActionsController.cs

168 lines
7.4 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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