Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- Рефакторинг системы подсветки: вместо длительности в секундах теперь используется количество циклов (float). Добавлена таблица длительности циклов эффектов `LightCycleDurations` для пересчёта циклов в секунды. Префикс команд подсветки изменён с `85` на `86`. - `LightSequence`: добавлены `Pause()` / `Resume()` с запоминанием текущего шага последовательности; возобновление продолжает с сохранённой позиции. - `LightController`: добавлены `PauseAll()` / `ResumeAll()` для приостановки и восстановления подсветки и мигания шляпы при отключении/переподключении платы. Свойства `IsSequencePaused`, `IsBlinkPaused`. - `ComDeviceClient`: после успешной авторизации отправляются команды `890003` и `8600`. Добавлен запрос MAC-адреса через команду `06` (ответ — 12 hex-символов без префикса). Новое событие `ConnectionLost` при начале KeepAlive-реконнекта. - `ComHub`: очередь `_disconnectedQueue` с типом `DeviceDisconnectMessage` (DeviceId + Role + Mac) — сохраняет идентификатор устройства до его удаления из словаря. Подписка на `ConnectionLost`. Добавлен `GetDeviceMac()`. - `ComSystemManager`: при инициализации, если плата не обнаружена, подсветка сразу ставится на паузу. При отвале дефолтного устройства — `PauseAll()`, при переподключении — сравнение по MAC и роли через `IsSameDevice()`: та же плата → `ResumeAll()`, другая → `Blink()` с нуля. Добавлены событие `OnDeviceDisconnected` и метод `GetDeviceMac()`. - `COMInput`: при событии `OnDeviceDisconnected` сбрасываются все состояния кнопок через `ResetAllButtons()`, генерируя `buttonUp` для зажатых кнопок. Устраняет баг залипания управления после переподключения платы. - `GameManager`: расширена последовательность световых эффектов в меню (добавлены `DiagonalWave3`, `WaveUp`, `OrangeWave`); вызовы `SetLight` для game over/won переведены на float-циклы. - Обновлены `ARCHITECTURE.md` и `README.md`: переписана документация на актуальную структуру проекта и текущие возможности игры.
132 lines
4.9 KiB
C#
132 lines
4.9 KiB
C#
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
namespace RR.Common.Controls
|
||
{
|
||
/// <summary>
|
||
/// Строитель последовательности световых эффектов.
|
||
/// Создаётся через <see cref="LightController"/>.
|
||
/// <example><code>
|
||
/// SetLight(LightMode.VerticalWave, 3f).Then(LightMode.DiagonalWave, 1.5f).Loop();
|
||
/// SetLight(LightMode.VerticalWave, 3f).Then(LightMode.Off);
|
||
/// SetLight(LightMode.Rainbow, 2f);
|
||
/// </code></example>
|
||
/// </summary>
|
||
public class LightSequence
|
||
{
|
||
private readonly struct Step
|
||
{
|
||
public readonly LightMode Mode;
|
||
public readonly float Cycles;
|
||
public Step(LightMode mode, float cycles) { Mode = mode; Cycles = cycles; }
|
||
}
|
||
|
||
private readonly List<Step> _steps = new();
|
||
private readonly LightController _controller;
|
||
private bool _looping;
|
||
private Coroutine _coroutine;
|
||
private int _currentStepIndex;
|
||
private bool _paused;
|
||
|
||
internal LightSequence(LightController controller, LightMode firstMode, float firstCycles)
|
||
{
|
||
_controller = controller;
|
||
_steps.Add(new Step(firstMode, firstCycles));
|
||
}
|
||
|
||
/// <summary>Добавляет следующий эффект с количеством циклов по умолчанию.</summary>
|
||
public LightSequence Then(LightMode mode) => Then(mode, _controller.DefaultCycles);
|
||
|
||
/// <summary>Добавляет следующий эффект с указанным количеством циклов.</summary>
|
||
public LightSequence Then(LightMode mode, float cycles)
|
||
{
|
||
_steps.Add(new Step(mode, cycles));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Зацикливает последовательность. Для остановки — <see cref="LightController.StopSequence"/>.
|
||
/// </summary>
|
||
public LightSequence Loop()
|
||
{
|
||
_looping = true;
|
||
Play();
|
||
return this;
|
||
}
|
||
|
||
/// <summary>Запускает последовательность один раз.</summary>
|
||
public LightSequence Play()
|
||
{
|
||
Stop();
|
||
_currentStepIndex = 0;
|
||
_paused = false;
|
||
_coroutine = _controller.StartCoroutine(RunSequence());
|
||
return this;
|
||
}
|
||
|
||
/// <summary>Останавливает выполнение последовательности.</summary>
|
||
public void Stop()
|
||
{
|
||
if (_coroutine == null) return;
|
||
_controller.StopCoroutine(_coroutine);
|
||
_coroutine = null;
|
||
_paused = false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Приостанавливает последовательность с сохранением текущей позиции.
|
||
/// </summary>
|
||
public void Pause()
|
||
{
|
||
if (_coroutine == null) return;
|
||
_controller.StopCoroutine(_coroutine);
|
||
_coroutine = null;
|
||
_paused = true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Возобновляет приостановленную последовательность с сохранённой позиции.
|
||
/// </summary>
|
||
public void Resume()
|
||
{
|
||
if (!_paused) return;
|
||
_paused = false;
|
||
_coroutine = _controller.StartCoroutine(RunSequence());
|
||
}
|
||
|
||
/// <summary>Возвращает <c>true</c>, если последовательность сейчас выполняется.</summary>
|
||
public bool IsPlaying => _coroutine != null;
|
||
|
||
/// <summary>Возвращает <c>true</c>, если последовательность приостановлена.</summary>
|
||
public bool IsPaused => _paused;
|
||
|
||
// ReSharper disable once IteratorNeverReturns — цикл намеренно бесконечный при _looping=true
|
||
private IEnumerator RunSequence()
|
||
{
|
||
do
|
||
{
|
||
for (int i = _currentStepIndex; i < _steps.Count; i++)
|
||
{
|
||
_currentStepIndex = i;
|
||
Step step = _steps[i];
|
||
|
||
int commandSeconds = LightCycleDurations.CyclesToCommandSeconds(step.Mode, step.Cycles);
|
||
_controller.SendCommand(step.Mode, commandSeconds);
|
||
|
||
float waitSeconds = LightCycleDurations.GetCycleSeconds(step.Mode) * step.Cycles;
|
||
|
||
if (step.Mode != LightMode.Off && waitSeconds > 0f)
|
||
yield return new WaitForSecondsRealtime(waitSeconds);
|
||
}
|
||
|
||
_currentStepIndex = 0;
|
||
}
|
||
while (_looping);
|
||
|
||
_coroutine = null;
|
||
}
|
||
}
|
||
}
|
||
|