using System.Collections; using System.Collections.Generic; using UnityEngine; public static class BlinkController { // Интервал мигания по умолчанию (в секундах) public static float DefaultInterval = 0.5f; // Внутренний раннер для запуска корутин (динамически создаётся при первом использовании) private class Runner : MonoBehaviour { // При выходе из приложения выключаем все подсветки private void OnApplicationQuit() { BlinkController.TurnAllOff(); } } private static Runner _runner; // Хранилище запущенных корутин: ключ формируем как "A_1" (тип_игрок) private static readonly Dictionary _coroutines = new Dictionary(); // Убедимся, что раннер существует и живёт на сцене private static void EnsureRunner() { if (_runner != null) return; var go = new GameObject("_BlinkControllerRunner"); Object.DontDestroyOnLoad(go); _runner = go.AddComponent(); go.hideFlags = HideFlags.HideAndDontSave; } // ---- Адресные команды для подсветок A и B (по игроку) ---- public static void LightAOn(int playerId) => ComWrapper.Send("2171", playerId); public static void LightAOff(int playerId) => ComWrapper.Send("2170", playerId); public static void LightBOn(int playerId) => ComWrapper.Send("2131", playerId); public static void LightBOff(int playerId) => ComWrapper.Send("2130", playerId); // ---- Глобальная подсветка C (broadcast) ---- public static void LightCOn() => ComWrapper.Send("401"); public static void LightCOff() => ComWrapper.Send("400"); // ---- Адресные LED диоды (команды 8xxx) ---- // 81хх - канал 1, 82хх - канал 2, 83хх - канал 3, 84хх - канал 4 // 80хх - все каналы сразу // Программы: 0-None, 1-Repeat, 2-Off, 3-Red, 4-Green, 5-Blue, 6-Gray, 7-White, 9-Rainbow, 10+-Custom /// Установить программу на канале LED (1-4) для конкретного игрока public static void SetLEDChannel(int channel, int program, int playerId) { if (channel < 1 || channel > 4) { Debug.LogWarning($"BlinkController: Неверный канал LED {channel}. Допустимо 1-4"); return; } if (program < 0 || program > 99) { Debug.LogWarning($"BlinkController: Неверная программа LED {program}. Допустимо 0-99"); return; } string cmd = $"{80 + channel}{program:D2}"; ComWrapper.Send(cmd, playerId); Debug.Log($"BlinkController: LED канал {channel} -> программа {program} для игрока {playerId} (cmd: {cmd})"); } /// Установить программу на всех LED каналах для конкретного игрока public static void SetLEDAll(int program, int playerId) { if (program < 0 || program > 99) { Debug.LogWarning($"BlinkController: Неверная программа LED {program}. Допустимо 0-99"); return; } string cmd = $"80{program:D2}"; ComWrapper.Send(cmd, playerId); Debug.Log($"BlinkController: LED все каналы -> программа {program} для игрока {playerId} (cmd: {cmd})"); } /// Установить программу на всех LED каналах глобально (broadcast) public static void SetLEDAllGlobal(int program) { if (program < 0 || program > 99) { Debug.LogWarning($"BlinkController: Неверная программа LED {program}. Допустимо 0-99"); return; } string cmd = $"80{program:D2}"; ComWrapper.Send(cmd); Debug.Log($"BlinkController: LED все каналы -> программа {program} глобально (cmd: {cmd})"); } // Быстрые команды для LED (адресные для игрока) public static void LEDOff(int channel, int playerId) => SetLEDChannel(channel, 2, playerId); // Погасить канал public static void LEDRed(int channel, int playerId) => SetLEDChannel(channel, 3, playerId); // Красный public static void LEDGreen(int channel, int playerId) => SetLEDChannel(channel, 4, playerId); // Зеленый public static void LEDBlue(int channel, int playerId) => SetLEDChannel(channel, 5, playerId); // Синий public static void LEDWhite(int channel, int playerId) => SetLEDChannel(channel, 7, playerId); // Белый public static void LEDRainbow(int channel, int playerId) => SetLEDChannel(channel, 9, playerId); // Радуга public static void LEDWave(int channel, int playerId) => SetLEDChannel(channel, 11, playerId); // волна public static void LEDOffAll(int playerId) => SetLEDAll(2, playerId); // Погасить все для игрока public static void LEDRedAll(int playerId) => SetLEDAll(3, playerId); // Красный на всех для игрока public static void LEDGreenAll(int playerId) => SetLEDAll(4, playerId); // Зеленый на всех для игрока public static void LEDBlueAll(int playerId) => SetLEDAll(5, playerId); // Синий на всех для игрока public static void LEDWhiteAll(int playerId) => SetLEDAll(7, playerId); // Белый на всех для игрока public static void LEDRainbowAll(int playerId) => SetLEDAll(9, playerId); // Радуга на всех для игрока // Глобальные команды (broadcast для всех игроков) public static void LEDOffAllGlobal() => SetLEDAllGlobal(2); // Погасить все глобально public static void LEDRedAllGlobal() => SetLEDAllGlobal(3); // Красный на всех глобально public static void LEDGreenAllGlobal() => SetLEDAllGlobal(4); // Зеленый на всех глобально public static void LEDBlueAllGlobal() => SetLEDAllGlobal(5); // Синий на всех глобально public static void LEDWhiteAllGlobal() => SetLEDAllGlobal(7); // Белый на всех глобально public static void LEDRainbowAllGlobal() => SetLEDAllGlobal(9); // Радуга на всех глобально // ---- Переключение цветового чередования (команды 89x) ---- public enum ColorSequence { RGB = 0, // 890 - Red, Green, Blue RBG = 1, // 891 - Red, Blue, Green BRG = 2, // 892 - Blue, Red, Green BGR = 3, // 893 - Blue, Green, Red GRB = 4, // 894 - Green, Red, Blue GBR = 5 // 895 - Green, Blue, Red } /// Установить цветовое чередование. Если playerId = 0 или не указан - отправка глобально public static void SetColorSequence(ColorSequence sequence, int playerId = 0) { string cmd = $"89{(int)sequence}"; if (playerId > 0) { ComWrapper.Send(cmd, playerId); Debug.Log($"BlinkController: Цветовое чередование {sequence} для игрока {playerId} (cmd: {cmd})"); } else { ComWrapper.Send(cmd); Debug.Log($"BlinkController: Цветовое чередование {sequence} глобально (cmd: {cmd})"); } } // ---- Произвольный RGB цвет (команда 08RRRGGGBBB) ---- /// Установить произвольный RGB цвет для LED. Если playerId = 0 - отправка глобально /// Красный канал (0-255) /// Зеленый канал (0-255) /// Синий канал (0-255) /// ID игрока (0 = глобально) /// Установить произвольный RGB цвет на конкретный канал (81-84) public static void SetLEDChannelCustomColor(int channel, int r, int g, int b, int playerId = 0) { // Ограничиваем значения channel = Mathf.Clamp(channel, 81, 84); r = Mathf.Clamp(r, 0, 255); g = Mathf.Clamp(g, 0, 255); b = Mathf.Clamp(b, 0, 255); // Формат: 8X (канал) + 08 (программа произвольный цвет) + RRRGGGBBB string cmd = $"{channel}08{r:D3}{g:D3}{b:D3}"; if (playerId > 0) { ComWrapper.Send(cmd, playerId); Debug.Log($"BlinkController: LED канал {channel} произвольный цвет RGB({r},{g},{b}) для игрока {playerId} (cmd: {cmd})"); } else { ComWrapper.Send(cmd); Debug.Log($"BlinkController: LED канал {channel} произвольный цвет RGB({r},{g},{b}) глобально (cmd: {cmd})"); } } /// Установить произвольный RGB цвет на все каналы (80) public static void SetLEDCustomColor(int r, int g, int b, int playerId = 0) { // Ограничиваем значения 0-255 r = Mathf.Clamp(r, 0, 255); g = Mathf.Clamp(g, 0, 255); b = Mathf.Clamp(b, 0, 255); // Формат: 80 (все каналы) + 08 (программа произвольный цвет) + RRRGGGBBB string cmd = $"8008{r:D3}{g:D3}{b:D3}"; if (playerId > 0) { ComWrapper.Send(cmd, playerId); Debug.Log($"BlinkController: LED произвольный цвет RGB({r},{g},{b}) для игрока {playerId} (cmd: {cmd})"); } else { ComWrapper.Send(cmd); Debug.Log($"BlinkController: LED произвольный цвет RGB({r},{g},{b}) глобально (cmd: {cmd})"); } } // Предустановленные цвета public static void LEDGold(int playerId = 0) => SetLEDCustomColor(255, 215, 0, playerId); // Золотой public static void LEDOrange(int playerId = 0) => SetLEDCustomColor(255, 165, 0, playerId); // Оранжевый public static void LEDPurple(int playerId = 0) => SetLEDCustomColor(128, 0, 128, playerId); // Фиолетовый public static void LEDPink(int playerId = 0) => SetLEDCustomColor(255, 192, 203, playerId); // Розовый public static void LEDCyan(int playerId = 0) => SetLEDCustomColor(0, 255, 255, playerId); // Голубой public static void LEDYellow(int playerId = 0) => SetLEDCustomColor(255, 255, 0, playerId); // Желтый // ---- Мигание LED с возвратом к базовому цвету ---- private static readonly Dictionary _playerBaseLEDColor = new Dictionary(); // Храним кастомные RGB цвета для каждого игрока (если установлен) private static readonly Dictionary _playerCustomLEDColor = new Dictionary(); /// Установить базовый цвет LED для игрока (к которому возвращаемся после мигания) public static void SetLEDBaseColor(int playerId, int program) { _playerBaseLEDColor[playerId] = program; _playerCustomLEDColor.Remove(playerId); // Убираем кастомный цвет SetLEDAll(program, playerId); Debug.Log($"BlinkController: Базовый LED цвет для игрока {playerId} установлен: {program}"); } /// Установить базовый кастомный RGB цвет LED для игрока public static void SetLEDBaseCustomColor(int playerId, int r, int g, int b) { _playerBaseLEDColor[playerId] = -1; // Маркер кастомного цвета _playerCustomLEDColor[playerId] = new Vector3Int(r, g, b); SetLEDCustomColor(r, g, b, playerId); Debug.Log($"BlinkController: Базовый кастомный LED цвет для игрока {playerId} установлен: RGB({r},{g},{b})"); } /// Получить базовый цвет LED для игрока public static int GetLEDBaseColor(int playerId) { return _playerBaseLEDColor.TryGetValue(playerId, out int color) ? color : 5; // По умолчанию синий (5) } /// Восстановить базовый цвет LED для игрока (включая кастомные RGB) public static void RestoreLEDBaseColor(int playerId) { if (_playerCustomLEDColor.TryGetValue(playerId, out Vector3Int rgb)) { // Восстанавливаем кастомный цвет SetLEDCustomColor(rgb.x, rgb.y, rgb.z, playerId); } else { // Восстанавливаем обычный цвет по программе int baseColor = GetLEDBaseColor(playerId); SetLEDAll(baseColor, playerId); } } /// Мигание LED N раз цветом, затем возврат к базовому цвету public static void BlinkLED(int playerId, int colorProgram, int times, float interval = 0.3f) { EnsureRunner(); _runner.StartCoroutine(BlinkLEDRoutine(playerId, colorProgram, times, interval)); } private static IEnumerator BlinkLEDRoutine(int playerId, int colorProgram, int times, float interval) { for (int i = 0; i < times; i++) { // Включить цвет SetLEDAll(colorProgram, playerId); yield return new WaitForSeconds(interval); // Вернуть базовый цвет (включая кастомные RGB) RestoreLEDBaseColor(playerId); // Пауза между миганиями (кроме последнего) if (i < times - 1) yield return new WaitForSeconds(interval); } } // Быстрые команды мигания public static void BlinkLEDRed(int playerId, int times = 1, float interval = 0.3f) => BlinkLED(playerId, 3, times, interval); public static void BlinkLEDGreen(int playerId, int times = 1, float interval = 0.3f) => BlinkLED(playerId, 4, times, interval); public static void BlinkLEDBlue(int playerId, int times = 1, float interval = 0.3f) => BlinkLED(playerId, 5, times, interval); // ---- Start/Stop мигания ---- public static void StartBlinkA(int playerId, float interval = -1f) { // Оставляем обратную совместимость: бесконечное мигание StartBlinkATimes(playerId, -1, interval); } // Мигание N раз (N <= 0 = бесконечно) public static void StartBlinkATimes(int playerId, int times, float interval = -1f) { StopBlinkA(playerId); EnsureRunner(); string key = KeyFor("A", playerId); var coro = _runner.StartCoroutine(BlinkRoutinePlayer(key, playerId, "2170", "2171", interval > 0 ? interval : DefaultInterval, times)); _coroutines[key] = coro; } public static void StopBlinkA(int playerId) { string key = KeyFor("A", playerId); StopAndRemove(key); LightAOff(playerId); } public static void StartBlinkB(int playerId, float interval = -1f) { // Оставляем обратную совместимость: бесконечное мигание StartBlinkBTimes(playerId, -1, interval); } // Мигание N раз (N <= 0 = бесконечно) public static void StartBlinkBTimes(int playerId, int times, float interval = -1f) { StopBlinkB(playerId); EnsureRunner(); string key = KeyFor("B", playerId); var coro = _runner.StartCoroutine(BlinkRoutinePlayer(key, playerId, "2130", "2131", interval > 0 ? interval : DefaultInterval, times)); _coroutines[key] = coro; } public static void StopBlinkB(int playerId) { string key = KeyFor("B", playerId); StopAndRemove(key); LightBOff(playerId); } // Глобальная C public static void StartBlinkC(float interval = -1f) { // Оставляем обратную совместимость: бесконечное мигание StartBlinkCTimes(-1, interval); } // Мигание N раз глобально (N <= 0 = бесконечно) public static void StartBlinkCTimes(int times, float interval = -1f) { StopBlinkC(); EnsureRunner(); string key = KeyFor("C", 0); var coro = _runner.StartCoroutine(BlinkRoutineGlobal(key, "400", "401", interval > 0 ? interval : DefaultInterval, times)); _coroutines[key] = coro; } public static void StopBlinkC() { string key = KeyFor("C", 0); StopAndRemove(key); LightCOff(); } private static void StopAndRemove(string key) { if (_coroutines.TryGetValue(key, out Coroutine c)) { if (_runner != null && c != null) _runner.StopCoroutine(c); _coroutines.Remove(key); } } private static string KeyFor(string type, int playerId) => $"{type}_{playerId}"; private static IEnumerator BlinkRoutinePlayer(string key, int playerId, string onCmd, string offCmd, float interval, int repeatCount) { // repeatCount <= 0 => бесконечно if (repeatCount <= 0) { while (true) { ComWrapper.Send(onCmd, playerId); yield return new WaitForSeconds(interval); ComWrapper.Send(offCmd, playerId); yield return new WaitForSeconds(interval); } } // Повторяем заданное количество раз for (int i = 0; i < repeatCount; i++) { ComWrapper.Send(onCmd, playerId); yield return new WaitForSeconds(interval); ComWrapper.Send(offCmd, playerId); // Между итерациями делаем паузу, как в обычном мигании, кроме последней if (i < repeatCount - 1) yield return new WaitForSeconds(interval); } // Гарантируем, что команда выключения послана ComWrapper.Send(offCmd, playerId); // Удаляем запись о корутине try { _coroutines.Remove(key); } catch { } } private static IEnumerator BlinkRoutineGlobal(string key, string onCmd, string offCmd, float interval, int repeatCount) { if (repeatCount <= 0) { while (true) { ComWrapper.Send(onCmd); yield return new WaitForSeconds(interval); ComWrapper.Send(offCmd); yield return new WaitForSeconds(interval); } } for (int i = 0; i < repeatCount; i++) { ComWrapper.Send(onCmd); yield return new WaitForSeconds(interval); ComWrapper.Send(offCmd); if (i < repeatCount - 1) yield return new WaitForSeconds(interval); } ComWrapper.Send(offCmd); try { _coroutines.Remove(key); } catch { } } // Вспомогательные: мгновенный вкл/выкл всех public static void StopAll() { var keys = new List(_coroutines.Keys); foreach (var k in keys) StopAndRemove(k); } // Включить все подсветки (A и B — для обоих игроков, C — глобально) public static void TurnAllOn() { Debug.Log("BlinkController: TurnAllOn"); // A и B адресные — для игроков 1 и 2 LightAOn(1); LightAOn(2); LightBOn(1); LightBOn(2); // C — глобально LightCOn(); // LED - при старте: канал 1 волна, канал 2 радуга для обоих игроков LEDWave(1, 1); // Игрок 1: канал 1 -> волна LEDRainbow(2, 1); // Игрок 1: канал 2 -> радуга LEDWave(1, 2); // Игрок 2: канал 1 -> волна LEDRainbow(2, 2); // Игрок 2: канал 2 -> радуга // Устанавливаем базовый цвет - радуга (для мигания будет возврат к ней) _playerBaseLEDColor[1] = 9; _playerBaseLEDColor[2] = 9; } // Выключить все подсветки (и остановить корутины) public static void TurnAllOff() { Debug.Log("BlinkController: TurnAllOff"); // Остановим все мигания StopAll(); // Отправим команды выключения адресно для игроков 1 и 2 try { LightAOff(1); LightAOff(2); LightBOff(1); LightBOff(2); // C — глобально LightCOff(); // LED - выключить все каналы глобально LEDOffAllGlobal(); } catch (System.Exception e) { Debug.LogWarning($"BlinkController: ошибка при TurnAllOff: {e.Message}"); } } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] private static void OnRuntimeInitialized() { // Подписываемся на событие инициализации ComWrapper — включим подсветки, когда COM будет готов try { ComWrapper.OnInitialize += TurnAllOn; // Подписываемся, чтобы выключить подсветки ДО закрытия портов ComWrapper.OnBeforeClose += TurnAllOff; } catch { // Если ComWrapper ещё не существует или не определён, проигнорируем — вызов произойдёт при инициализации } // Создаём раннер заранее, чтобы он был доступен и гарантированно существовал EnsureRunner(); // Подпишемся на событие выхода приложения — гарантированно выключит подсветки и почистит подписки Application.quitting += OnApplicationQuitting; } // Обработчик выхода приложения: отписываемся от событий ComWrapper private static void OnApplicationQuitting() { try { ComWrapper.OnInitialize -= TurnAllOn; } catch { } try { ComWrapper.OnBeforeClose -= TurnAllOff; } catch { } try { TurnAllOff(); } catch { } if (_runner != null) { Object.Destroy(_runner.gameObject); _runner = null; } } }