using System.Collections; using UnityEngine; using Random = UnityEngine.Random; namespace RR.Common.Controls { /// /// Управляет подсветкой периферийной платы по протоколу 85XXYY: /// /// XX — код эффекта () /// YY — время в секундах (00–99) /// /// Команда 85 (без параметров) отключает рендеринг и освобождает память. /// Поддерживает цепочки эффектов через : /// /// SetLight(LightMode.VerticalWave, 3).Then(LightMode.DiagonalWave, 3).Loop(); /// /// public class LightController : MonoBehaviour { // ───────────────────────── Инспектор ───────────────────────── [Header("Команды шляпы")] [Tooltip("COM-команда для включения шляпы")] [SerializeField] private string hatOnCommand = "2131"; [Tooltip("COM-команда для выключения шляпы")] [SerializeField] private string hatOffCommand = "2130"; [Header("Мигание")] [Tooltip("Интервал между переключениями при мигании (секунды)")] [SerializeField] [Range(0.05f, 5f)] private float blinkInterval = 0.5f; [Tooltip("Случайный разброс интервала (±секунды). 0 = строго равномерно")] [SerializeField] [Range(0f, 2f)] private float blinkIntervalVariance; [Header("Световые эффекты")] [Tooltip("Эффект по умолчанию при вызове SetLight без аргументов")] [SerializeField] private LightMode defaultLightMode = LightMode.Rainbow; [Tooltip("Время эффекта по умолчанию (секунды, 0–99)")] [SerializeField] [Range(0, 99)] private int defaultDuration = 3; // ───────────────────────── Приватное состояние ───────────────────────── private Coroutine _blinkingCoroutine; private LightSequence _activeSequence; private const string EffectPrefix = "85"; // ───────────────────────── Свойства ───────────────────────── /// Время эффекта по умолчанию (используется в ). public int DefaultDuration => defaultDuration; // ===========================start: PUBLIC API=========================== /// /// Включает эффект по умолчанию и возвращает для построения цепочки. /// public LightSequence SetLight() => SetLight(defaultLightMode, defaultDuration); /// /// Включает эффект на время по умолчанию и возвращает для построения цепочки. /// public LightSequence SetLight(LightMode mode) => SetLight(mode, defaultDuration); /// /// Включает световой эффект на указанное время и возвращает для построения цепочки. /// Если вызвать без .Then() / .Loop() — эффект запустится немедленно как одиночный. /// /// Эффект подсветки /// Время эффекта (0–99 секунд, либо -1 для команды без YY) public LightSequence SetLight(LightMode mode, int durationSeconds) { StopSequence(); _activeSequence = new LightSequence(this, mode, durationSeconds); _activeSequence.Play(); return _activeSequence; } /// /// Останавливает текущую активную последовательность эффектов и отключает рендеринг. /// public void StopSequence() { _activeSequence?.Stop(); _activeSequence = null; } /// /// Останавливает все эффекты и отключает рендеринг платы (85). /// public void StopAllEffects() { StopSequence(); ComSystemManager.Send(EffectPrefix); } /// /// Запускает мигание шляпы с настроенным интервалом. /// public void Blink() => Blink(blinkInterval); /// /// Запускает мигание шляпы с явно указанным интервалом. /// public void Blink(float interval) { StopBlink(enableHat: false); _blinkingCoroutine = StartCoroutine(BlinkingCoroutine(interval)); } /// /// Меняет интервал мигания на лету; перезапускает мигание, если оно активно. /// public void SetBlinkInterval(float interval) { blinkInterval = Mathf.Max(0.05f, interval); if (_blinkingCoroutine != null) Blink(blinkInterval); } /// /// Останавливает мигание шляпы. /// /// Оставить шляпу включённой после остановки public void StopBlink(bool enableHat = true) { if (_blinkingCoroutine != null) { StopCoroutine(_blinkingCoroutine); _blinkingCoroutine = null; } ComSystemManager.Send(enableHat ? hatOnCommand : hatOffCommand); } /// Возвращает true, если мигание шляпы сейчас активно. public bool IsBlinking => _blinkingCoroutine != null; /// Возвращает true, если последовательность эффектов сейчас активна. public bool IsSequencePlaying => _activeSequence?.IsPlaying ?? false; // ===========================end: PUBLIC API=========================== /// /// Отправляет COM-команду для одного шага последовательности. /// Используется из . /// public void SendCommand(LightMode mode, int durationSeconds) { ComSystemManager.Send(BuildCommand(mode, durationSeconds)); } // ReSharper disable once IteratorNeverReturns — корутина намеренно бесконечная private IEnumerator BlinkingCoroutine(float interval) { while (true) { ComSystemManager.Send(Random.Range(0f, 1f) < 0.5f ? hatOnCommand : hatOffCommand); float wait = blinkIntervalVariance > 0f ? Mathf.Max(0.05f, interval + Random.Range(-blinkIntervalVariance, blinkIntervalVariance)) : interval; yield return new WaitForSecondsRealtime(wait); } } private static string BuildCommand(LightMode mode, int durationSeconds) { if (mode == LightMode.Off) return EffectPrefix; int xx = (int)mode; if (durationSeconds == -1) return $"{EffectPrefix}{xx:D2}"; int yy = Mathf.Clamp(durationSeconds, 0, 99); return $"{EffectPrefix}{xx:D2}{yy:D2}"; } } }