Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
LightController / LightMode: - Новый LightMode.cs: enum эффектов под протокол 85XXYY (13 эффектов + Off) значения enum = коды команд XX, Off=-1 генерирует просто '85' - Новый LightController.cs: управление подсветкой платы * SetLight(mode, duration) → строит команду 85XXYY, возвращает LightSequence * Blink() / StopBlink() / SetBlinkInterval() — мигание шляпы * StopSequence() / StopAllEffects() — остановка эффектов * Все параметры (hatOn/hatOff, blinkInterval, defaultMode/Duration) в инспекторе - Вложенный класс LightSequence — builder цепочек эффектов: SetLight(A,3).Then(B,3).Then(C,3).Loop() SetLight(A,3).Then(LightMode.Off) SetLight(A,5) — одиночный эффект GameManager: - Добавлено свойство Light (→ Plate.Light) - GameWin → SetLight(FinalWave, 30) - GameOver → SetLight(BlueWave, 30) - NewGame → SetLight(Rainbow, 99) - LoadScene MainMenu → SetLight(Off) + Blink() namespace (добавлены в файлы без namespace): - Editor/MouseWorldPositionOverlay.cs → RR.Common.InspectorOverrides - UI/StatsWindowUIController.cs → RR.Common.UI - Utilities/AdvancedLogging_Test.cs → RR.Common.Utility - Utilities/DebugSettings.cs → RR.Common.Utility
266 lines
12 KiB
C#
266 lines
12 KiB
C#
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using Random = UnityEngine.Random;
|
||
|
||
namespace RR.Common.Controls
|
||
{
|
||
/// <summary>
|
||
/// Управляет подсветкой периферийной платы по протоколу <c>85XXYY</c>:
|
||
/// <list type="bullet">
|
||
/// <item><c>XX</c> — код эффекта (<see cref="LightMode"/>)</item>
|
||
/// <item><c>YY</c> — время в секундах (00–99)</item>
|
||
/// </list>
|
||
/// Команда <c>85</c> (без параметров) отключает рендеринг и освобождает память.
|
||
/// <para>Поддерживает цепочки эффектов через <see cref="LightSequence"/>:</para>
|
||
/// <code>
|
||
/// SetLight(LightMode.VerticalWave, 3).Then(LightMode.DiagonalWave, 3).Loop();
|
||
/// </code>
|
||
/// </summary>
|
||
public class LightController : MonoBehaviour
|
||
{
|
||
// ───────────────────────── Инспектор ─────────────────────────
|
||
|
||
[Header("Команды шляпы")]
|
||
[Tooltip("COM-команда для включения шляпы")]
|
||
[SerializeField] private string hatOnCommand = "2131";
|
||
[Tooltip("COM-команда для выключения шляпы")]
|
||
[SerializeField] private string hatOffCommand = "2130";
|
||
|
||
[Header("Мигание")]
|
||
[Tooltip("Интервал между переключениями при мигании (секунды)")]
|
||
[SerializeField] [Range(0.05f, 5f)] private float blinkInterval = 0.5f;
|
||
[Tooltip("Случайный разброс интервала (±секунды). 0 = строго равномерно")]
|
||
[SerializeField] [Range(0f, 2f)] private float blinkIntervalVariance;
|
||
|
||
[Header("Световые эффекты")]
|
||
[Tooltip("Эффект по умолчанию при вызове SetLight без аргументов")]
|
||
[SerializeField] private LightMode defaultLightMode = LightMode.Rainbow;
|
||
[Tooltip("Время эффекта по умолчанию (секунды, 0–99)")]
|
||
[SerializeField] [Range(0, 99)] private int defaultDuration = 3;
|
||
|
||
// ───────────────────────── Приватное состояние ─────────────────────────
|
||
|
||
private Coroutine _blinkingCoroutine;
|
||
private LightSequence _activeSequence;
|
||
|
||
private const string EffectPrefix = "85";
|
||
|
||
// ───────────────────────── Свойства ─────────────────────────
|
||
|
||
/// <summary>Время эффекта по умолчанию (используется в <see cref="LightSequence.Then(LightMode)"/>).</summary>
|
||
public int DefaultDuration => defaultDuration;
|
||
|
||
// ===========================start: PUBLIC API===========================
|
||
|
||
/// <summary>
|
||
/// Включает эффект по умолчанию и возвращает <see cref="LightSequence"/> для построения цепочки.
|
||
/// </summary>
|
||
public LightSequence SetLight() => SetLight(defaultLightMode, defaultDuration);
|
||
|
||
/// <summary>
|
||
/// Включает эффект на время по умолчанию и возвращает <see cref="LightSequence"/> для построения цепочки.
|
||
/// </summary>
|
||
public LightSequence SetLight(LightMode mode) => SetLight(mode, defaultDuration);
|
||
|
||
/// <summary>
|
||
/// Включает световой эффект на указанное время и возвращает <see cref="LightSequence"/> для построения цепочки.
|
||
/// <para>Если вызвать без <c>.Then()</c> / <c>.Loop()</c> — эффект запустится немедленно как одиночный.</para>
|
||
/// </summary>
|
||
/// <param name="mode">Эффект подсветки</param>
|
||
/// <param name="durationSeconds">Время эффекта (0–99 секунд)</param>
|
||
public LightSequence SetLight(LightMode mode, int durationSeconds)
|
||
{
|
||
StopSequence();
|
||
_activeSequence = new LightSequence(this, mode, durationSeconds);
|
||
_activeSequence.Play();
|
||
return _activeSequence;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Останавливает текущую активную последовательность эффектов и отключает рендеринг.
|
||
/// </summary>
|
||
public void StopSequence()
|
||
{
|
||
_activeSequence?.Stop();
|
||
_activeSequence = null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Останавливает все эффекты и отключает рендеринг платы (<c>85</c>).
|
||
/// </summary>
|
||
public void StopAllEffects()
|
||
{
|
||
StopSequence();
|
||
ComWrapper.Send(EffectPrefix);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Запускает мигание шляпы с настроенным интервалом.
|
||
/// </summary>
|
||
public void Blink() => Blink(blinkInterval);
|
||
|
||
/// <summary>
|
||
/// Запускает мигание шляпы с явно указанным интервалом.
|
||
/// </summary>
|
||
public void Blink(float interval)
|
||
{
|
||
StopBlink(enableHat: false);
|
||
_blinkingCoroutine = StartCoroutine(BlinkingCoroutine(interval));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Меняет интервал мигания на лету; перезапускает мигание, если оно активно.
|
||
/// </summary>
|
||
public void SetBlinkInterval(float interval)
|
||
{
|
||
blinkInterval = Mathf.Max(0.05f, interval);
|
||
if (_blinkingCoroutine != null) Blink(blinkInterval);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Останавливает мигание шляпы.
|
||
/// </summary>
|
||
/// <param name="enableHat">Оставить шляпу включённой после остановки</param>
|
||
public void StopBlink(bool enableHat = true)
|
||
{
|
||
if (_blinkingCoroutine != null)
|
||
{
|
||
StopCoroutine(_blinkingCoroutine);
|
||
_blinkingCoroutine = null;
|
||
}
|
||
|
||
ComWrapper.Send(enableHat ? hatOnCommand : hatOffCommand);
|
||
}
|
||
|
||
/// <summary>Возвращает <c>true</c>, если мигание шляпы сейчас активно.</summary>
|
||
public bool IsBlinking => _blinkingCoroutine != null;
|
||
|
||
/// <summary>Возвращает <c>true</c>, если последовательность эффектов сейчас активна.</summary>
|
||
public bool IsSequencePlaying => _activeSequence?.IsPlaying ?? false;
|
||
|
||
// ===========================end: PUBLIC API===========================
|
||
|
||
/// <summary>
|
||
/// Отправляет COM-команду для одного шага последовательности.
|
||
/// Используется из <see cref="LightSequence"/>.
|
||
/// </summary>
|
||
public void SendCommand(LightMode mode, int durationSeconds)
|
||
{
|
||
ComWrapper.Send(BuildCommand(mode, durationSeconds));
|
||
}
|
||
|
||
// ReSharper disable once IteratorNeverReturns — корутина намеренно бесконечная
|
||
private IEnumerator BlinkingCoroutine(float interval)
|
||
{
|
||
while (true)
|
||
{
|
||
ComWrapper.Send(Random.Range(0f, 1f) < 0.5f ? hatOnCommand : hatOffCommand);
|
||
|
||
float wait = blinkIntervalVariance > 0f
|
||
? Mathf.Max(0.05f, interval + Random.Range(-blinkIntervalVariance, blinkIntervalVariance))
|
||
: interval;
|
||
|
||
yield return new WaitForSeconds(wait);
|
||
}
|
||
}
|
||
|
||
private static string BuildCommand(LightMode mode, int durationSeconds)
|
||
{
|
||
if (mode == LightMode.Off)
|
||
return EffectPrefix;
|
||
|
||
int xx = (int)mode;
|
||
int yy = Mathf.Clamp(durationSeconds, 0, 99);
|
||
return $"{EffectPrefix}{xx:D2}{yy:D2}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// Строитель последовательности световых эффектов.
|
||
/// Создаётся через <see cref="LightController.SetLight(LightMode,int)"/>.
|
||
/// <example><code>
|
||
/// SetLight(LightMode.VerticalWave, 3).Then(LightMode.DiagonalWave, 3).Loop();
|
||
/// SetLight(LightMode.VerticalWave, 3).Then(LightMode.Off);
|
||
/// SetLight(LightMode.Rainbow, 5);
|
||
/// </code></example>
|
||
/// </summary>
|
||
public class LightSequence
|
||
{
|
||
private readonly struct Step
|
||
{
|
||
public readonly LightMode Mode;
|
||
public readonly int DurationSeconds;
|
||
public Step(LightMode mode, int durationSeconds) { Mode = mode; DurationSeconds = durationSeconds; }
|
||
}
|
||
|
||
private readonly List<Step> _steps = new();
|
||
private readonly LightController _controller;
|
||
private bool _looping;
|
||
private Coroutine _coroutine;
|
||
|
||
internal LightSequence(LightController controller, LightMode firstMode, int firstDuration)
|
||
{
|
||
_controller = controller;
|
||
_steps.Add(new Step(firstMode, firstDuration));
|
||
}
|
||
|
||
/// <summary>Добавляет следующий эффект с временем по умолчанию.</summary>
|
||
public LightSequence Then(LightMode mode) => Then(mode, _controller.DefaultDuration);
|
||
|
||
/// <summary>Добавляет следующий эффект с указанным временем.</summary>
|
||
public LightSequence Then(LightMode mode, int durationSeconds)
|
||
{
|
||
_steps.Add(new Step(mode, durationSeconds));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Зацикливает последовательность. Для остановки — <see cref="LightController.StopSequence"/>.
|
||
/// </summary>
|
||
public LightSequence Loop()
|
||
{
|
||
_looping = true;
|
||
Play();
|
||
return this;
|
||
}
|
||
|
||
/// <summary>Запускает последовательность один раз.</summary>
|
||
public LightSequence Play()
|
||
{
|
||
Stop();
|
||
_coroutine = _controller.StartCoroutine(RunSequence());
|
||
return this;
|
||
}
|
||
|
||
/// <summary>Останавливает выполнение последовательности.</summary>
|
||
public void Stop()
|
||
{
|
||
if (_coroutine == null) return;
|
||
_controller.StopCoroutine(_coroutine);
|
||
_coroutine = null;
|
||
}
|
||
|
||
/// <summary>Возвращает <c>true</c>, если последовательность сейчас выполняется.</summary>
|
||
public bool IsPlaying => _coroutine != null;
|
||
|
||
// ReSharper disable once IteratorNeverReturns — цикл намеренно бесконечный при _looping=true
|
||
private IEnumerator RunSequence()
|
||
{
|
||
do
|
||
{
|
||
foreach (Step step in _steps)
|
||
{
|
||
_controller.SendCommand(step.Mode, step.DurationSeconds);
|
||
|
||
if (step.Mode != LightMode.Off && step.DurationSeconds > 0)
|
||
yield return new WaitForSeconds(step.DurationSeconds);
|
||
}
|
||
}
|
||
while (_looping);
|
||
|
||
_coroutine = null;
|
||
}
|
||
}
|
||
}
|
||
}
|