Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
Добавлены новые UI-компоненты и исправлена надёжность подсветки - Добавлен UIElementAnimator — универсальный аниматор UI-элементов (покачивание, движение вверх-вниз, пульсация масштаба) - Добавлен GamePriceDisplay — отображение стоимости игры из настроек (payment.startGameCost, payment.currency) - Добавлен метод StartIdleEffects() в GameManager для гарантированного запуска мигания шапки и последовательности эффектов - Исправлена подсветка, не включавшаяся при переходе в главное меню: OnSceneLoaded теперь вызывает StartIdleEffects() - Исправлено восстановление подсветки при переподключении платы: ComSystemManager использует StartIdleEffects() для MainMenu - Добавлены LightController.IsHatOn и ResendHatState() для корректного отслеживания состояния шапки - Исправлено зависание спецэффектов при мигании шапки: LightSequence теперь переотправляет команду эффекта каждые 2 сек - Убраны дублирующиеся вызовы Blink/SetLight из LoadScene (теперь обрабатывается в OnSceneLoaded) - Обновлена сцена MainMenu, ассеты шрифтов, удалён старый префаб Coin
158 lines
6.5 KiB
C#
158 lines
6.5 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;
|
||
|
||
/// <summary>
|
||
/// Интервал переотправки команды эффекта (секунды).
|
||
/// Нужно, чтобы команды шляпы (мигание) не «заглушали» длительные эффекты:
|
||
/// плата получает напоминание об активном эффекте каждые N секунд.
|
||
/// </summary>
|
||
private const float ResendInterval = 2f;
|
||
|
||
// 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)
|
||
{
|
||
// Разбиваем ожидание на короткие интервалы и периодически
|
||
// переотправляем команду эффекта, чтобы параллельные команды
|
||
// шляпы не сбрасывали текущий эффект на плате.
|
||
float elapsed = 0f;
|
||
while (elapsed < waitSeconds)
|
||
{
|
||
float chunk = Mathf.Min(ResendInterval, waitSeconds - elapsed);
|
||
yield return new WaitForSecondsRealtime(chunk);
|
||
elapsed += chunk;
|
||
|
||
// Переотправляем, если ещё не дождались конца шага
|
||
if (elapsed < waitSeconds)
|
||
{
|
||
int remainSeconds = Mathf.CeilToInt(waitSeconds - elapsed);
|
||
if (remainSeconds > 0)
|
||
_controller.SendCommand(step.Mode, remainSeconds);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
_currentStepIndex = 0;
|
||
}
|
||
while (_looping);
|
||
|
||
_coroutine = null;
|
||
}
|
||
}
|
||
}
|
||
|