Danil Iarosh 839a68aef1 Improve VFX, UI overlays, session statistics, COM reliability, audio mixer, and expand architecture guides
Подробный список изменений:
- Перенесены эффекты частиц из C#-кода в готовые префабы-ассеты (Vfx Prefabs) в папке Assets/Resources/Vfx/ с автоматической генерацией материалов в Editor.
- Исправлены ошибки duration на играющих системах частиц и розовые текстуры в GameVfxService.cs.
- Исправлена навигация и фокус кнопок на оверлеях ContinueGameOverlay и PrizeResultOverlay с автоматическим подсвечиванием кнопок лимонно-зеленым цветом при фокусе и проигрыванием звука.
- Добавлена проверка настроек в GameplayHudController.cs для скрытия ContinueGameOverlay, если продолжение игры отключено.
- Расширен сбор статистики: создана структура GameplaySessionRecord, добавлена история до 50 раундов в PrizeSessionStateService и JsonPrizeSessionStateRepository, выведен расчет средних показателей и список 5 последних раундов в сервисной статистике.
- Повышена надежность COM-транспорта: добавлен sleep (80мс) в Dispose для graceful shutdown потоков, реализован цикл переподключения до 10 попыток при обрыве связи.
- Интегрированы настройки громкости с микшером: изменен шаг изменения громкости на 5% (0.05) для удобства, добавлен статус-чек и логи в ApplyServiceVolumes, расширены юнит-тесты на GetFloat параметры.
- Созданы новые архитектурные гайды по аппаратному COM-транспорту и призовой экономике автомата в папке GUIDES/architecture/.
- Оптимизирован и обновлен RAG-индекс проекта.
2026-07-03 15:04:08 +03:00

138 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Архитектура аппаратной интеграции и COM-взаимодействия
В этом руководстве описывается архитектура аппаратной интеграции в `Danro Jump` — принципы взаимодействия Unity-приложения с физической платой управления аркадного автомата через последовательный COM-порт.
---
## 1. Обзор аппаратной платформы
Аркадный автомат управляется специализированным контроллером (микроконтроллером), который опрашивает органы управления автомата и управляет периферийными устройствами:
- **Органы ввода (Inputs):** джойстик (влево, вправо), кнопка старта, монетоприемник (Coin Acceptor), датчики выдачи призов (оптопары хопперов и ячеек витрины).
- **Органы вывода (Outputs):** светодиодная подсветка автомата (LED-ленты), моторы выдачи призов (хоппер монет, диспенсер призов витрины).
Связь между игровым ПК и контроллером осуществляется по кабелю USB-to-UART (виртуальный COM-порт) на скорости `115200` бод.
---
## 2. Протокол обмена и команды
Обмен информацией ведется текстовыми строками, разделяемыми символами перевода строки `\n` или `\r\n`. Каждая команда или сообщение представляет собой текстовый пакет.
### 2.1. Инициализация и Авторизация (Handshake)
Для предотвращения использования неавторизованного оборудования и защиты прошивки контроллера реализована двухсторонняя криптографическая проверка на базе алгоритма AES-128:
1. **Запрос роли:** При открытии порта ПК отправляет команду запроса роли `97` (команда `roleRequestCommand`).
2. **Ответ платы:** Плата возвращает строку `ROLE:<role>`, например `ROLE:main` для главного игрового контроллера или `ROLE:vitrina` для контроллера призового стенда.
3. **Защищенное рукопожатие (CHAL / RAES):**
- Компьютер генерирует случайный 16-байтовый массив, шифрует его по алгоритму AES-128 с секретным ключом проекта и отправляет Hex-строку плате в виде команды `CHAL:<hex_block>`.
- Плата расшифровывает блок на своей стороне, прибавляет к нему контрольную соль, шифрует обратно и возвращает ответ `RAES:<hex_block>`.
- Компьютер расшифровывает ответ платы и сравнивает с исходными данными. В случае успеха плата переводится в состояние **Operational** (рабочее состояние).
- Если проверка провалена, подключение закрывается.
### 2.2. Потоки данных в рабочем режиме
- **Запрос состояния ввода (Пинг/Keep-Alive):** Компьютер с интервалом `8` секунд шлет команду опроса ввода (обычно `01`).
- **События ввода (Input Messages):** Плата отправляет строку состояния кнопок в формате `INPUT:<bitmask>`, где каждый бит соответствует нажатой кнопке или датчику:
- Бит 0: кнопка влево
- Бит 1: кнопка вправо
- Бит 2: кнопка старта/выбора
- Бит 3: сигнал монетоприемника (импульс зачисления кредита)
- Бит 4: оптический датчик выдачи приза хоппером
- **Команды вывода (Output Commands):**
- Освещение: команды управления цветом и анимацией подсветки (например, переключение на зеленый при старте).
- Выдача приза: команда запуска мотора хоппера `12` или открытие конкретной ячейки призовой витрины.
---
## 3. Многопоточная архитектура в Unity
Последовательные порты в .NET/Unity работают медленно и синхронно. Блокировка главного потока Unity операциями ввода-вывода приведет к микрофризам игры (Stuttering), что недопустимо для динамичного платформера.
Для решения этой проблемы в `ComSystem` реализована полностью асинхронная многопоточная модель на базе `UniTask` и пула потоков (`ThreadPool`):
```mermaid
graph TD
subgraph Unity Main Thread
A[ComSystem Update] -->|Опрос очередей| B(coinQueue / inputQueue / mainThreadQueue)
B -->|Вызов событий| C[Игровая логика / UI]
end
subgraph ThreadPool
D[ReadLoopAsync] -->|Синхронное блокирующее чтение| E[serialPort.ReadLine]
E -->|Данные получены| F[ProcessLine]
F -->|Enqueue| B
G[KeepAliveLoopAsync] -->|Каждые 8 сек| H[Send '01' пинг]
I[ReconnectAsync] -->|При сбое связи| J[Переоткрытие порта / Повторный Handshake]
end
```
### 3.1. Основные циклы (Tasks)
1. **ReadLoopAsync (Цикл чтения):**
Выполняется в фоновом потоке на `ThreadPool`. Вызывает синхронный блокирующий метод `ReadLine()`.
Чтобы поток не висел заблокированным вечно при отсутствии данных, таймаут чтения (`ReadTimeout`) в настройках последовательного порта выставлен на `50` миллисекунд. Каждые 50мс вызов прерывается по `TimeoutException`, поток просыпается, проверяет токен отмены (`CancellationToken.IsCancellationRequested`) и уходит на следующий цикл ожидания.
2. **KeepAliveLoopAsync (Цикл контроля связи):**
Каждые 8 секунд отправляет на плату запрос ввода. Если плата не присылает ответный `INPUT` пакет в течение 3 попыток подряд (`missedInputResponsesBeforeReconnect = 3`), это классифицируется как потеря связи, и запускается процедура переподключения.
3. **ReconnectAsync (Цикл автореконнекта):**
При потере пингов или возникновении системной ошибки ввода-вывода (`IOException` в `ReadLoop` при отключении USB-кабеля):
- Переводит устройство в статус `isOperational = false`.
- Сбрасывает имя роли и MAC-адрес.
- Закрывает порт.
- Запускает цикл из 10 попыток открыть порт заново с интервалом в 1 секунду. При каждой попытке проверяется отмена токена, чтобы не открыть порт в фоне во время выхода из игры.
- При успешном переоткрытии порта автоматически инициируется повторная авторизация (Handshake) и плата возвращается в строй без перезапуска игры.
### 3.2. Graceful Shutdown (Безопасный выход)
В .NET на Windows вызов `SerialPort.Close()` или `.Dispose()` во время активного блокирующего чтения в параллельном потоке гарантированно приводит к зависанию (Deadlock) драйвера порта и зависанию всего приложения Unity при выходе.
Для предотвращения этого в методе `ComDeviceConnection.Dispose` реализован двухэтапный выход:
1. Вызывается `localCts.Cancel()`, сигнализируя фоновым потокам о необходимости завершения.
2. Главный поток делает паузу `System.Threading.Thread.Sleep(80)`. Так как таймаут чтения порта равен 50мс, за 80мс фоновый поток чтения гарантированно успевает проснуться по таймауту, обнаружить отмену токена и безопасно завершить работу.
3. Только после этого вызывается `ClosePort()`, закрывающий и освобождающий последовательный порт под блокировкой `lock (ioLock)`.
---
## 4. Потокобезопасная маршрутизация
Поскольку фоновые циклы работают в потоках `ThreadPool`, они не могут напрямую вызывать методы Unity API (изменение UI, спавн объектов, проигрывание звуков), так как Unity API не является потокобезопасным.
Для передачи данных в главный поток используются потокобезопасные очереди `ConcurrentQueue`:
- `coinQueue` — очередь зачислений монет.
- `inputQueue` — очередь нажатий клавиш управления.
- `mainThreadQueue` — очередь произвольных делегатов `Action` (для логов, статусов подключения и т.д.).
Метод `Update()` класса `ComSystem` выполняется в главном потоке Unity каждый кадр. Он опрашивает эти очереди и безопасно вызывает соответствующие Unity-события:
```csharp
private void Update()
{
// Перенос общих действий в главный поток
while (mainThreadQueue.TryDequeue(out Action action))
{
action?.Invoke();
}
// Зачисление кредитов
while (coinQueue.TryDequeue(out _))
{
AddPaymentImpulse(1);
}
// Обработка нажатий управления
while (inputQueue.TryDequeue(out ComInputMessage message))
{
if (message.DeviceId == CurrentMainDeviceId)
{
mainInputReceived.Invoke(message.InputBits);
}
}
}
```
---
## 5. Рекомендации по разработке и отладке
1. **Эмуляция клавиатурой:** Для тестирования без физической платы в Unity Editor или Development-сборках включена эмуляция: нажатие клавиши `F6` генерирует импульс монеты.
2. **Логирование:** В инспекторе `ComSystem` можно включать каналы отладки (`logLifecycle`, `logCredits`, `logRawEvents`), чтобы изолировать проблемы с подключением, зачислением денег или обработкой протокола.
3. **Использование блокировок:** Любой доступ к объекту `SerialPort` (запись `WriteLine` или закрытие `Close`) должен производиться строго внутри блока `lock (ioLock)` для исключения одновременного обращения из разных потоков.