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"); // ---- 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(); } // Выключить все подсветки (и остановить корутины) public static void TurnAllOff() { Debug.Log("BlinkController: TurnAllOff"); // Остановим все мигания StopAll(); // Отправим команды выключения адресно для игроков 1 и 2 try { LightAOff(1); LightAOff(2); LightBOff(1); LightBOff(2); // C — глобально LightCOff(); } 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; } } }