Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- Добавил новую систему настроек, возможности:
- Динамически создает список настроек
- Сохраняет настройки в JSON-файл
- Возможность группировать настройки
- Широкие возможности по кастомизации
- Применение настроек в реальном времени (частично)
- Добавил префабы для новых элементов настроек
- Изменил поведение игры исходя из нового формата настроек
666 lines
30 KiB
C#
666 lines
30 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using RR.Game2D.Core.Managers;
|
||
using UnityEngine;
|
||
using UnityEngine.Events;
|
||
using Random = UnityEngine.Random;
|
||
|
||
namespace RR.Common.Controls
|
||
{
|
||
/// <summary>
|
||
/// Основной класс-обертка для работы с COM-портом и периферийной платой.
|
||
/// Управляет всей коммуникацией с аппаратным обеспечением: прием монет, чтение кнопок, управление подсветкой, отправка сигналов.
|
||
/// </summary>
|
||
public class ComWrapper : MonoBehaviour
|
||
{
|
||
//Настраиваем из инспектора
|
||
[SerializeField] private bool saveMoneyBetweenSessions;
|
||
[SerializeField] private bool enablePrizeChecking = false; // Если хопер с проверкой наличия приза ставим true
|
||
[SerializeField] private bool keepAlive = true;
|
||
|
||
|
||
public static UnityEvent<int> OnCreditAdded;
|
||
public static UnityEvent<int> OnCreditRemoved;
|
||
public static UnityEvent<string> OnInputReceived;
|
||
public static Action OnInitialize;
|
||
|
||
#region Приватные поля
|
||
|
||
private const string REQUEST_INPUT = "91";
|
||
private const string LIGHTSIGNAL_PREFIX = "80";
|
||
|
||
private static int h; // идентификатор хэндла
|
||
private static readonly object bufferLock = new object(); // Блокировка для синхронизации доступа к COM-порту
|
||
|
||
private static int CreditsCount;
|
||
|
||
// Асинхронная работа
|
||
private CancellationTokenSource pollCancellationTokenSource;
|
||
private int inputToggle; // Флаг для Проверки получения INPUT от платы
|
||
|
||
// Очередь для обработки COIN в главном потоке
|
||
private static readonly System.Collections.Concurrent.ConcurrentQueue<int> coinQueue = new System.Collections.Concurrent.ConcurrentQueue<int>();
|
||
|
||
// Защита от спама COIN: отслеживание времени последней монеты
|
||
private static long lastCoinTime; // в миллисекундах
|
||
private const long COIN_DEBOUNCE_MS = 250; // Минимальный интервал между монетами (250ms)
|
||
|
||
// StringBuilder для эффективного формирования строки INPUT без лишних аллокаций
|
||
private static readonly StringBuilder inputBuilder = new StringBuilder(32);
|
||
|
||
// Кэш последнего состояния INPUT
|
||
// ВАЖНО: 0 = кнопка нажата, 1 = кнопка отпущена, поэтому по умолчанию все единицы
|
||
private static string lastInputBits = "11111111111111111111111111"; // 26 бит по умолчанию
|
||
|
||
private Coroutine blinkingCoroutine;
|
||
|
||
#endregion
|
||
|
||
//===========================start: PUBLIC API===========================//
|
||
|
||
/// <summary>
|
||
/// Инициализирует систему работы с COM-портом.
|
||
/// Открывает соединение, настраивает события и запускает асинхронные запросы состояния.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Обязателен к вызову при старте приложения. Выполняет:
|
||
/// - Создание UnityEvent'ов для уведомлений
|
||
/// - Загрузку количества кредитов из PlayerPrefs
|
||
/// - Открытие COM-порта
|
||
/// - Запуск асинхронного опроса пинов (если включена проверка призов)
|
||
/// </remarks>
|
||
public void Initialize()
|
||
{
|
||
//Создаем ивенты
|
||
OnCreditAdded = new UnityEvent<int>();
|
||
OnCreditRemoved = new UnityEvent<int>();
|
||
OnInputReceived = new UnityEvent<string>();
|
||
|
||
//Начальная настройка
|
||
if (!saveMoneyBetweenSessions) PlayerPrefs.SetInt("CoinsCount", 0);
|
||
CreditsCount = PlayerPrefs.GetInt("CoinsCount", 0);
|
||
|
||
initPort();
|
||
|
||
// Отправляем сигнал запуска игры
|
||
Send("2171");
|
||
Blink();
|
||
|
||
// Запускаем асинхронный опрос порта
|
||
pollCancellationTokenSource = new CancellationTokenSource();
|
||
StartAsyncPolling();
|
||
|
||
// Запускаем keep-alive механизм если включен
|
||
if (keepAlive || enablePrizeChecking)
|
||
{
|
||
StartKeepAliveProcess();
|
||
}
|
||
|
||
OnInitialize?.Invoke();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Добавляет один кредит в систему и сохраняет обновленное количество.
|
||
/// Вызывается автоматически событием при получении сигнала "COIN" из COM-порта.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Увеличивает счетчик кредитов и вызывает событие onCoinAdded для уведомления подписчиков.
|
||
/// Автоматически сохраняет состояние в PlayerPrefs.
|
||
/// </remarks>
|
||
public static void AddCredit()
|
||
{
|
||
CreditsCount++;
|
||
PlayerPrefs.SetInt("CoinsCount", CreditsCount);
|
||
OnCreditAdded?.Invoke(CreditsCount);
|
||
|
||
// PlayerPrefs.SetInt(PrizeSystem.key_starts, PlayerPrefs.GetInt(PrizeSystem.key_starts) + 1);
|
||
// PlayerPrefs.SetInt(PrizeSystem.key_startsNul, PlayerPrefs.GetInt(PrizeSystem.key_startsNul) + 1);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Отправляет произвольную строковую команду на периферийную плату через COM-порт.
|
||
/// </summary>
|
||
/// <param name="message">Команда для отправки</param>
|
||
/// <remarks>
|
||
/// Используется для отправки пользовательских команд на плату.
|
||
/// Команда должна быть в формате, ожидаемом прошивкой платы.
|
||
/// </remarks>
|
||
public static void Send(string message)
|
||
{
|
||
try
|
||
{
|
||
if (h <= 0)
|
||
{
|
||
GameManager.Logger.Log("ComWrapper: Попытка отправки при закрытом порте");
|
||
return;
|
||
}
|
||
|
||
ComNativeWrapper.Serial_WriteStringUTF8(h, message, 1);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
GameManager.Logger.Log($"Ошибка отправки сигнала: {e.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Отправляет команду управления подсветкой на периферийную плату.
|
||
/// </summary>
|
||
/// <param name="lightMode">Режим подсветки из перечисления LightMode</param>
|
||
/// <remarks>
|
||
/// Формирует команду вида "80XXX" где XXX - код режима подсветки.
|
||
/// Для добавления новых режимов необходимо обновить вложенный метод getLightString + добавить режим в перечисление.
|
||
/// </remarks>
|
||
public static void Send(LightMode lightMode)
|
||
{
|
||
try
|
||
{
|
||
string signal = LIGHTSIGNAL_PREFIX + getLightString(lightMode);
|
||
Send(signal);
|
||
|
||
//ВОТЭТОТ МЕТОД ПОД СВОИ ПОДСВЕТКИ НАДО ПОДГОНЯТЬ
|
||
string getLightString(LightMode mode) =>
|
||
|
||
mode switch
|
||
{
|
||
LightMode.Idle1 => "500",
|
||
LightMode.Idle2 => "507",
|
||
LightMode.Orange => "501",
|
||
LightMode.Blue => "502",
|
||
LightMode.Win => "503",
|
||
LightMode.Lose => "504",
|
||
LightMode.Prize => "505",
|
||
LightMode.Tama => "513",
|
||
LightMode.Dog => "514",
|
||
LightMode.Sam => "515",
|
||
LightMode.Mistress => "516",
|
||
|
||
_ => "500"
|
||
};
|
||
|
||
}
|
||
catch
|
||
{
|
||
// DebugWindow.Message("Ошибка отправки светового сигнала.");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Выполняет списание платы за запуск игры.
|
||
/// Проверяет наличие доступных кредитов или режим бесплатной игры.
|
||
/// При успешном списании увеличивает счетчики запусков.
|
||
/// </summary>
|
||
/// <returns>
|
||
/// true - если списание прошло успешно (бесплатный режим или есть кредиты),
|
||
/// false - если кредитов недостаточно и платный режим
|
||
/// </returns>
|
||
/// <remarks>
|
||
/// В случае успеха:
|
||
/// - Увеличивает общие счетчики запусков (key_starts и key_startsNul)
|
||
/// - При платном режиме уменьшает количество кредитов на 1
|
||
/// - Вызывает событие onCoinRemoved при списании кредита
|
||
/// </remarks>
|
||
public static bool Pay(int amount = 1)
|
||
{
|
||
// блок проверки оплаты
|
||
// if (SettingsManager.instance.GetSettingValue("Бесплатная игра") == "Вкл")
|
||
// {
|
||
// //Счетчики пробега
|
||
// PlayerPrefs.SetInt(PrizeSystem.key_starts, PlayerPrefs.GetInt(PrizeSystem.key_starts) + 1);
|
||
// PlayerPrefs.SetInt(PrizeSystem.key_startsNul, PlayerPrefs.GetInt(PrizeSystem.key_startsNul) + 1);
|
||
//
|
||
// return true;
|
||
// }
|
||
|
||
if (CreditsCount >= amount)
|
||
{
|
||
CreditsCount -= amount;
|
||
PlayerPrefs.SetInt("CoinsCount", CreditsCount);
|
||
|
||
//счетчики пробега
|
||
PlayerPrefs.SetInt(PrizeSystem.key_starts, PlayerPrefs.GetInt(PrizeSystem.key_starts) + 1);
|
||
PlayerPrefs.SetInt(PrizeSystem.key_startsNul, PlayerPrefs.GetInt(PrizeSystem.key_startsNul) + 1);
|
||
|
||
OnCreditRemoved?.Invoke(CreditsCount);
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
public void Blink()
|
||
{
|
||
StopBlink();
|
||
|
||
blinkingCoroutine = StartCoroutine(BlinkingCoroutine());
|
||
|
||
|
||
}
|
||
|
||
public void StopBlink(bool enableHat = true)
|
||
{
|
||
if (blinkingCoroutine != null) StopCoroutine(blinkingCoroutine);
|
||
blinkingCoroutine = null;
|
||
Send(enableHat ? "2131" : "2130"); // включаем или выключаем шляпу
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получаем текущий баланс кредитов
|
||
/// </summary>
|
||
public static int GetCredits() => CreditsCount;
|
||
|
||
//===========================end: PUBLIC API===========================//
|
||
|
||
private void Update()
|
||
{
|
||
// Обрабатываем COIN из очереди в главном потоке
|
||
int coinsProcessed = 0;
|
||
|
||
while (coinQueue.TryDequeue(out _))
|
||
{
|
||
AddCredit();
|
||
coinsProcessed++;
|
||
}
|
||
|
||
if (coinsProcessed > 0)
|
||
{
|
||
GameManager.Logger.Log($"Обработано монет: {coinsProcessed}");
|
||
}
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
StopAsyncPolling();
|
||
ClosePort();
|
||
}
|
||
|
||
private void OnApplicationQuit()
|
||
{
|
||
Send("2170"); // Выключаем кнопки
|
||
Send("2130"); // Выключаем шапку
|
||
|
||
StopAsyncPolling();
|
||
ClosePort();
|
||
}
|
||
|
||
private void StopAsyncPolling()
|
||
{
|
||
pollCancellationTokenSource?.Cancel();
|
||
pollCancellationTokenSource?.Dispose();
|
||
pollCancellationTokenSource = null;
|
||
}
|
||
|
||
private async void StartAsyncPolling()
|
||
{
|
||
try
|
||
{
|
||
GameManager.Logger.Log("Запуск асинхронного опроса порта");
|
||
|
||
while (pollCancellationTokenSource != null &&
|
||
!pollCancellationTokenSource.Token.IsCancellationRequested)
|
||
{
|
||
await Task.Delay(1, pollCancellationTokenSource.Token); // Опрос каждые 1ms для уменьшения задержки
|
||
|
||
string comInput = await ReadBufferLineAsync(pollCancellationTokenSource.Token);
|
||
if (!string.IsNullOrEmpty(comInput))
|
||
{
|
||
ProcessComInputImmediate(comInput);
|
||
}
|
||
}
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
GameManager.Logger.Log("Асинхронный опрос порта остановлен");
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
GameManager.Logger.Log($"Ошибка асинхронного опроса: {e.Message}");
|
||
}
|
||
}
|
||
|
||
private void ProcessComInputImmediate(string comInput)
|
||
{
|
||
// Debug.Log("COM: " + comInput);
|
||
|
||
// INPUT обработка
|
||
if (comInput.StartsWith("INPUT"))
|
||
{
|
||
inputToggle = 1; // Обновляем флаг для keep-alive
|
||
|
||
// Проверяем, что после "INPUT" есть данные
|
||
if (comInput.Length <= 5)
|
||
{
|
||
GameManager.Logger.Log("INPUT получен, но без данных");
|
||
return;
|
||
}
|
||
|
||
string rawData = comInput.Substring(5);
|
||
|
||
// Проверяем, что rawData не пустой
|
||
if (string.IsNullOrWhiteSpace(rawData))
|
||
{
|
||
GameManager.Logger.Log("INPUT получен с пустым значением");
|
||
return;
|
||
}
|
||
|
||
// Конвертируем десятичное число в двоичную строку
|
||
string binaryValue = DecimalToBinary(rawData);
|
||
|
||
// Обновляем кэш последнего состояния
|
||
lastInputBits = binaryValue;
|
||
|
||
// Debug.Log($"Decimal: {rawData} → Binary: {binaryValue}");
|
||
OnInputReceived?.Invoke(binaryValue);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Преобразует строку с десятичным числом в двоичное представление.
|
||
/// Младшие биты слева, старшие справа (перевёрнутый порядок для соответствия индексам пинов).
|
||
/// </summary>
|
||
/// <param name="decimalString">Строка с десятичным числом</param>
|
||
/// <returns>Строка с двоичным представлением</returns>
|
||
private static string DecimalToBinary(string decimalString)
|
||
{
|
||
try
|
||
{
|
||
if (long.TryParse(decimalString, out long decimalValue))
|
||
{
|
||
// Стандартное преобразование: старшие биты слева
|
||
string binary = Convert.ToString(decimalValue, 2);
|
||
|
||
// Переворачиваем строку: младшие биты слева, старшие справа
|
||
char[] chars = binary.ToCharArray();
|
||
Array.Reverse(chars);
|
||
return new string(chars);
|
||
}
|
||
|
||
Debug.Log($"Не удалось распарсить значение INPUT: {decimalString}");
|
||
return "11111111111111111111111111"; // 26 единиц по умолчанию
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.Log($"Ошибка преобразования в двоичную систему: {e.Message}");
|
||
return "11111111111111111111111111"; // 26 единиц по умолчанию
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получает последнее кэшированное состояние INPUT.
|
||
/// Полезно когда нужно узнать состояние кнопок без ожидания нового события.
|
||
/// </summary>
|
||
/// <returns>Строка с двоичным представлением состояния кнопок</returns>
|
||
public static string GetLastInputState() => lastInputBits;
|
||
|
||
#region Приватные методы
|
||
//Ну тут по дебаг логам все должно быть понятно
|
||
//Если интересно, то h - это хэндл. Что-то на плюсовом, типа ID соединения или что-то. Если 0, то нету соед. если меньше то ошибка, больше 0 - норм.
|
||
private void initPort()
|
||
{
|
||
GameManager.Logger.Log("ComWrapper", "Старт программы");
|
||
ComNativeWrapper.Serial_CloseAll();
|
||
var ports = ComNativeWrapper.ListPorts();
|
||
GameManager.Logger.Log("ComWrapper", "Ports: " + string.Join(", ", ports));
|
||
if (ports.Length == 0) { GameManager.Logger.Log("Нет портов, завершаю"); return; }
|
||
|
||
string port = ports[^1];
|
||
GameManager.Logger.Log("ComWrapper", "Открываю порт: " + port);
|
||
|
||
|
||
//вот эти настройки лучше не ковырять, но если очень хочется можно поменять readTimeoutMs и writeTimeoutMs
|
||
//Если read поставить слишком маленьким оно проебует кредиты когда они идут большой пачкой например при оплате через вендисту.
|
||
//Там в порт спамит "COIN" в больших количествах и процентов 10 из этих строк не успевают прочитаться при таймауте 1мс, соотв. монеты проебуются.
|
||
//Слишком большой таймаут будет тормозить игру потому что оно все выполняется синхронно и каждый кадр забирает столько мс сколько в переменной указано.
|
||
h = ComNativeWrapper.Serial_Open(port, 115200, 8, 0, 1, 5, 10);
|
||
|
||
|
||
GameManager.Logger.Log("ComWrapper", $"handle: {h}");
|
||
if (h <= 0)
|
||
{
|
||
GameManager.Logger.Log("ComWrapper", "Open failed: " + ComNativeWrapper.GetLastError());
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
GameManager.Logger.Log("ComWrapper", $"Открыл порт: {port}");
|
||
}
|
||
|
||
// Очищаем буфер порта от старых данных
|
||
GameManager.Logger.Log("Очистка буфера порта от старых данных...");
|
||
FlushPortBuffer();
|
||
|
||
// Инициализируем lastCoinTime текущим временем чтобы не обработать старые COIN
|
||
lastCoinTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||
GameManager.Logger.Log($"Порт инициализирован, debounce активирован");
|
||
}
|
||
|
||
/// <summary>
|
||
/// Очищает буфер порта от накопившихся старых данных
|
||
/// </summary>
|
||
private static void FlushPortBuffer()
|
||
{
|
||
lock (bufferLock)
|
||
{
|
||
try
|
||
{
|
||
// Проверяем валидность handle
|
||
if (h <= 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// Используем локальный буфер
|
||
byte[] localBuf = new byte[1024];
|
||
|
||
int flushedCount = 0;
|
||
|
||
// Читаем все накопившиеся данные из порта
|
||
int read;
|
||
while ((read = ComNativeWrapper.Serial_ReadLineUTF8_bytes(h, localBuf, localBuf.Length)) > 0)
|
||
{
|
||
flushedCount++;
|
||
if (flushedCount > 100) break; // Защита от бесконечного цикла
|
||
}
|
||
|
||
GameManager.Logger.Log($"Очищено: {flushedCount} сообщений");
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
GameManager.Logger.Log($"FlushPortBuffer exception: {e.Message}");
|
||
}
|
||
}
|
||
}
|
||
|
||
private static void ClosePort()
|
||
{
|
||
Debug.Log("Закрываю все порты");
|
||
if (h > 0)
|
||
{
|
||
ComNativeWrapper.Serial_Close(h);
|
||
h = 0;
|
||
}
|
||
}
|
||
|
||
|
||
//Чтение с порта
|
||
private static string ReadBufferLine()
|
||
{
|
||
lock (bufferLock)
|
||
{
|
||
try
|
||
{
|
||
// Проверяем валидность handle перед вызовом нативной функции
|
||
if (h <= 0)
|
||
{
|
||
return "";
|
||
}
|
||
|
||
// Используем локальный буфер для полной изоляции
|
||
byte[] localBuf = new byte[1024];
|
||
|
||
int read = ComNativeWrapper.Serial_ReadLineUTF8_bytes(h, localBuf, localBuf.Length);
|
||
|
||
// Проверяем валидность значения read ПЕРЕД использованием
|
||
if (read <= 0)
|
||
{
|
||
if (read < 0)
|
||
{
|
||
GameManager.Logger.Log("ComWrapper", $"ReadLine err={read} :: {ComNativeWrapper.GetLastError()}");
|
||
}
|
||
return "";
|
||
}
|
||
|
||
// Защита от выхода за границы массива
|
||
if (read > localBuf.Length)
|
||
{
|
||
GameManager.Logger.Log($"ReadBufferLine: read ({read}) превышает размер буфера ({localBuf.Length}), обрезаем");
|
||
read = localBuf.Length;
|
||
}
|
||
|
||
string s = Encoding.UTF8.GetString(localBuf, 0, read);
|
||
|
||
// Обрабатываем COIN сразу, но добавляем в очередь для обработки в главном потоке
|
||
if (s == "COIN")
|
||
{
|
||
long currentTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||
long timeSinceLastCoin = currentTime - lastCoinTime;
|
||
|
||
// Проверяем debounce: если прошло меньше 250ms - это спам от той же монеты
|
||
if (timeSinceLastCoin < COIN_DEBOUNCE_MS)
|
||
{
|
||
GameManager.Logger.Log($"COIN проигнорирован (спам через {timeSinceLastCoin}ms < 250ms)");
|
||
// НЕ отправляем подтверждение для спама!
|
||
}
|
||
else
|
||
{
|
||
// Это РЕАЛЬНАЯ новая монета - обрабатываем
|
||
lastCoinTime = currentTime;
|
||
|
||
// Отправляем подтверждение
|
||
int confirmSent = ComNativeWrapper.Serial_WriteStringUTF8(h, "10", 1);
|
||
GameManager.Logger.Log($"COIN принят → Подтверждение '10' отправлено (bytes: {confirmSent})");
|
||
|
||
// Добавляем в очередь для обработки в главном потоке
|
||
coinQueue.Enqueue(1);
|
||
}
|
||
return "";
|
||
}
|
||
|
||
return s;
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
GameManager.Logger.Log($"ReadBufferLine exception: {e.Message}");
|
||
return "";
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Асинхронное чтение с порта. Выполняется в фоновом потоке.
|
||
/// </summary>
|
||
private static async Task<string> ReadBufferLineAsync(CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
return await Task.Run(() =>
|
||
{
|
||
if (cancellationToken.IsCancellationRequested)
|
||
return string.Empty;
|
||
|
||
return ReadBufferLine();
|
||
}, cancellationToken);
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
return string.Empty;
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
GameManager.Logger.Log($"Ошибка асинхронного чтения порта: {e.Message}");
|
||
return string.Empty;
|
||
}
|
||
}
|
||
|
||
//просим прислать состояние цифровых входов. Приходит что то вроде "INPUT111111101"
|
||
private static void requestInputs() => Send(REQUEST_INPUT);
|
||
|
||
/// <summary>
|
||
/// Асинхронный keep-alive процесс для проверки соединения с платой
|
||
/// </summary>
|
||
private async void StartKeepAliveProcess()
|
||
{
|
||
try
|
||
{
|
||
GameManager.Logger.Log("Запуск асинхронной проверки соединения");
|
||
|
||
while (pollCancellationTokenSource != null &&
|
||
!pollCancellationTokenSource.Token.IsCancellationRequested)
|
||
{
|
||
await Task.Delay(2000, pollCancellationTokenSource.Token); // Ждем 2 секунды
|
||
|
||
// Запрашиваем INPUT
|
||
requestInputs();
|
||
inputToggle = 0;
|
||
|
||
await Task.Delay(1000, pollCancellationTokenSource.Token); // Ждем ответа 1 секунду
|
||
|
||
// Проверяем, пришел ли ответ
|
||
if (inputToggle == 0)
|
||
{
|
||
GameManager.Logger.Log("Плата не отвечает! Попытка переподключения...");
|
||
await Task.Run(() =>
|
||
{
|
||
ClosePort();
|
||
initPort();
|
||
}, pollCancellationTokenSource.Token);
|
||
}
|
||
|
||
await Task.Delay(7000, pollCancellationTokenSource.Token); // Итого ~10 секунд между проверками
|
||
}
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
GameManager.Logger.Log("Асинхронная проверка соединения остановлена");
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
GameManager.Logger.Log($"Ошибка проверки соединения: {e.Message}");
|
||
}
|
||
}
|
||
|
||
private static IEnumerator BlinkingCoroutine()
|
||
{
|
||
while (true)
|
||
{
|
||
float randomFloat = Random.Range(0f, 1f);
|
||
|
||
Send(randomFloat < 0.5f ? "2130" : "2131");
|
||
|
||
yield return new WaitForSeconds(0.5f);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
}
|
||
public enum LightMode
|
||
{
|
||
Idle1,
|
||
Idle2,
|
||
Orange,
|
||
Blue,
|
||
Win,
|
||
Lose,
|
||
Prize,
|
||
Tama,
|
||
Dog,
|
||
Sam,
|
||
Mistress
|
||
}
|
||
}
|
||
|