329 lines
14 KiB
C#
329 lines
14 KiB
C#
using System.Collections;
|
||
using System.Text;
|
||
using RR.Game2D.Core.Managers;
|
||
using Unity.VisualScripting;
|
||
using UnityEngine;
|
||
using UnityEngine.Events;
|
||
using UnityEngine.UI;
|
||
|
||
/// <summary>
|
||
/// Основной класс-обертка для работы с COM-портом и периферийной платой.
|
||
/// Управляет всей коммуникацией с аппаратным обеспечением: прием монет, чтение кнопок, управление подсветкой, отправка сигналов.
|
||
/// </summary>
|
||
public class ComWrapper : MonoBehaviour
|
||
{
|
||
//Настраиваем из инспектора
|
||
[SerializeField] bool saveMoneyBetweenSessions;
|
||
[SerializeField] bool enablePrizeChecking = false; // Если хопер с проверкой наличия приза ставим true
|
||
|
||
|
||
public static UnityEvent<int> OnCreditAdded;
|
||
public static UnityEvent<int> OnCreditRemoved;
|
||
public static UnityEvent<string> OnInputReceived;
|
||
|
||
#region Приватные поля
|
||
private const string REQUEST_INPUT = "91";
|
||
private const string DEFAULT_INPUT = "INPUT1111111111111111";
|
||
private const string LIGHTSIGNAL_PREFIX = "80";
|
||
|
||
private static int h; // идентификатор хэндла
|
||
private static byte[] buf = new byte[1024]; // Байт-буфер куда будут приходить данные с порта
|
||
|
||
private static int CreditsCount;
|
||
private string inputString = DEFAULT_INPUT;
|
||
#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(); //вот это важный метод который собственно порт открывает.
|
||
|
||
//Каждые 5 сек запрашиваем инпуты у платы. Надо потому что при заходе в игру с пинов еще не пришла инфа
|
||
if (enablePrizeChecking) StartCoroutine(KeepPortAliveProcess());
|
||
}
|
||
|
||
/// <summary>
|
||
/// Добавляет один кредит в систему и сохраняет обновленное количество.
|
||
/// Вызывается автоматически событием при получении сигнала "COIN" из COM-порта.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Увеличивает счетчик кредитов и вызывает событие onCoinAdded для уведомления подписчиков.
|
||
/// Автоматически сохраняет состояние в PlayerPrefs.
|
||
/// </remarks>
|
||
public static void AddCredit()
|
||
{
|
||
CreditsCount++;
|
||
PlayerPrefs.SetInt("CoinsCount", CreditsCount);
|
||
OnCreditAdded?.Invoke(CreditsCount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Отправляет произвольную строковую команду на периферийную плату через COM-порт.
|
||
/// </summary>
|
||
/// <param name="message">Команда для отправки</param>
|
||
/// <remarks>
|
||
/// Используется для отправки пользовательских команд на плату.
|
||
/// Команда должна быть в формате, ожидаемом прошивкой платы.
|
||
/// В случае ошибки отправки выводит предупреждение в DebugWindow.
|
||
/// </remarks>
|
||
public static void Send(string message)
|
||
{
|
||
try
|
||
{
|
||
int w = ComNativeWrapper.Serial_WriteStringUTF8(h, message,
|
||
1); //w-количество записаных символов в порт. можно обработать
|
||
//GameManager.Logger.Log("Wrote: " + w);
|
||
|
||
}
|
||
catch
|
||
{
|
||
// DebugWindow.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()
|
||
{
|
||
// блок проверки оплаты
|
||
// TODO: Сделать свою систему для проверки оплаты и бесплатной игры
|
||
// 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 > 0)
|
||
{
|
||
CreditsCount--;
|
||
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;
|
||
}
|
||
|
||
//===========================end: PUBLIC API===========================//
|
||
|
||
private void Update() => pollPort();
|
||
private void OnDisable() => ClosePort();
|
||
private void OnApplicationQuit() => ClosePort();
|
||
|
||
private IEnumerator KeepPortAliveProcess()
|
||
{
|
||
OnInputReceived?.AddListener(InputReceivedCather);
|
||
GameManager.Logger.Log("Подключаю проверку порта.");
|
||
|
||
while (true)
|
||
{
|
||
requestInputs();
|
||
yield return new WaitForSeconds(5f);
|
||
|
||
//нет инпута
|
||
if (GetInputToggle() == 0)
|
||
{
|
||
GameManager.Logger.Log("Плата отвалилась. Запускаю переподключение.");
|
||
initPort();
|
||
|
||
}
|
||
//есть инпут
|
||
else
|
||
{
|
||
ToggleInputReceived(0);
|
||
}
|
||
|
||
yield return new WaitForSeconds(5);
|
||
}
|
||
|
||
}
|
||
|
||
void ToggleInputReceived(int value) => PlayerPrefs.SetInt("inputToggle", value);
|
||
int GetInputToggle() => PlayerPrefs.GetInt("inputToggle", 0);
|
||
|
||
void InputReceivedCather(string input)
|
||
{
|
||
if (!string.IsNullOrEmpty(input))
|
||
{
|
||
//пришел инпут
|
||
ToggleInputReceived(1);
|
||
}
|
||
|
||
}
|
||
|
||
#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}");
|
||
}
|
||
}
|
||
|
||
private static void ClosePort()
|
||
{
|
||
Debug.Log("Закрываю все порты");
|
||
if (h > 0)
|
||
{
|
||
ComNativeWrapper.Serial_Close(h);
|
||
h = 0;
|
||
}
|
||
}
|
||
|
||
|
||
//Тут чтение, не трогаем(опционально)
|
||
private static string ReadBufferLine()
|
||
{
|
||
|
||
int read = ComNativeWrapper.Serial_ReadLineUTF8_bytes(h, buf, buf.Length);
|
||
if (read > 0)
|
||
{
|
||
string s = Encoding.UTF8.GetString(buf, 0, read);
|
||
//GameManager.Logger.Log($"ReadLine len={read} data=[{s}]");
|
||
return s;
|
||
}
|
||
else if (read < 0)
|
||
{
|
||
GameManager.Logger.Log("ComWrapper",$"ReadLine err={read} :: {ComNativeWrapper.GetLastError()}");
|
||
}
|
||
return "";
|
||
}
|
||
//просим прислать состояние цифровых входов. Приходит что то вроде "INPUT111111101"
|
||
private void requestInputs() => Send(REQUEST_INPUT);
|
||
|
||
private void pollPort()
|
||
{
|
||
//Тут все просто, читаем что нам приходит с порта и сравниваем.
|
||
string comInput = ReadBufferLine();
|
||
if (comInput != "")
|
||
{
|
||
// DebugWindow.Message("COM: " + comInput);
|
||
|
||
if (comInput == "COIN") AddCredit();
|
||
if (comInput.StartsWith("INPUT")) OnInputReceived?.Invoke(comInput.Substring(5)); //инвокаю сразу без префикса "INPUT"
|
||
}
|
||
}
|
||
#endregion
|
||
}
|
||
public enum LightMode
|
||
{
|
||
Idle1,
|
||
Idle2,
|
||
Orange,
|
||
Blue,
|
||
Win,
|
||
Lose,
|
||
Prize,
|
||
Tama,
|
||
Dog,
|
||
Sam,
|
||
Mistress
|
||
} |