Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
- Интегрирована система кастомных визуальных эффектов (VFX): - Добавлена новая утилита MvpVFXUtility и настройки MvpVFXSpawnSettings для спавна эффектов. - Реализован пул эффектов VFXPool с поддержкой отслеживания версий спавна, сброса оверрайдов и анимации масштаба по времени. - Добавлены эффекты прыжка и приземления в PlayerAnimationEvents. - Интегрирован эффект DeathVignetteController для плавного затемнения экрана при смерти игрока. - Добавлены частицы stompImpactVFX при уничтожении врагов прыжком сверху (EnemyController). - Реализован эффект конфетти finishVFX при успешном завершении уровня (LevelFinish). - Интегрированы эффекты искр при сборе монеток, кристаллов и других собираемых предметов (Collectible, EnvironmentalCollectibleBase). - Улучшен и визуально доработан пользовательский интерфейс (UI): - Добавлен скрипт QRCodeTierHighlight для цветовой подсветки QR-кодов в зависимости от тира приза. - Реализована пульсация блока приза в баннере победы (WinBannerController). - Обновлено обозначение валюты в BalanceDisplay с "₽" на "руб.". - Добавлены новые префабы, сцены, ассеты тайлсетов и шейдеров для настройки визуального стиля. - Обновлён протокол и инициализация подсветки COM-платы: - Изменен префикс команд подсветки с 85/86 на 8550, а диапазон эффектов обновлен с 40...52 на 10...22 (LightController, LightMode, LightCycleDurations). - Исправлена хардкод-команда сброса подсветки при инициализации в ComDeviceClient с устаревшей 8600 на новую 8550 для предотвращения рассинхронизации буфера обмена платы.
252 lines
11 KiB
C#
252 lines
11 KiB
C#
using System.Collections;
|
||
using UnityEngine;
|
||
using Random = UnityEngine.Random;
|
||
|
||
namespace RR.Common.Controls
|
||
{
|
||
/// <summary>
|
||
/// Управляет подсветкой периферийной платы по протоколу <c>8550XXYY</c>:
|
||
/// <list type="bullet">
|
||
/// <item><c>XX</c> — код эффекта (<see cref="LightMode"/>)</item>
|
||
/// <item><c>YY</c> — время в секундах (00–99)</item>
|
||
/// </list>
|
||
/// Команда <c>8550</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("Количество циклов эффекта по умолчанию")]
|
||
[SerializeField] private float defaultCycles = 1f;
|
||
|
||
// ───────────────────────── Приватное состояние ─────────────────────────
|
||
|
||
private Coroutine _blinkingCoroutine;
|
||
private LightSequence _activeSequence;
|
||
private bool _blinkWasPaused;
|
||
private float _pausedBlinkInterval;
|
||
private bool _hatOn;
|
||
|
||
private const string EffectPrefix = "8550";
|
||
|
||
// ───────────────────────── Свойства ─────────────────────────
|
||
|
||
/// <summary>Количество циклов по умолчанию (используется в <see cref="LightSequence.Then(LightMode)"/>).</summary>
|
||
public float DefaultCycles => defaultCycles;
|
||
|
||
// ===========================start: PUBLIC API===========================
|
||
|
||
/// <summary>
|
||
/// Включает эффект по умолчанию и возвращает <see cref="LightSequence"/> для построения цепочки.
|
||
/// </summary>
|
||
public LightSequence SetLight() => SetLight(defaultLightMode, defaultCycles);
|
||
|
||
/// <summary>
|
||
/// Включает эффект с количеством циклов по умолчанию и возвращает <see cref="LightSequence"/> для построения цепочки.
|
||
/// </summary>
|
||
public LightSequence SetLight(LightMode mode) => SetLight(mode, defaultCycles);
|
||
|
||
/// <summary>
|
||
/// Включает световой эффект на указанное количество циклов и возвращает <see cref="LightSequence"/> для построения цепочки.
|
||
/// <para>Если вызвать без <c>.Then()</c> / <c>.Loop()</c> — эффект запустится немедленно как одиночный.</para>
|
||
/// </summary>
|
||
/// <param name="mode">Эффект подсветки</param>
|
||
/// <param name="cycles">Количество циклов (дробное: 0.5 = половина цикла)</param>
|
||
public LightSequence SetLight(LightMode mode, float cycles)
|
||
{
|
||
StopSequence();
|
||
_activeSequence = new LightSequence(this, mode, cycles);
|
||
_activeSequence.Play();
|
||
return _activeSequence;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Останавливает текущую активную последовательность эффектов и отключает рендеринг.
|
||
/// </summary>
|
||
public void StopSequence()
|
||
{
|
||
_activeSequence?.Stop();
|
||
_activeSequence = null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Останавливает все эффекты и отключает рендеринг платы (<c>8550</c>).
|
||
/// </summary>
|
||
public void StopAllEffects()
|
||
{
|
||
StopSequence();
|
||
ComSystemManager.Send(EffectPrefix + "00");
|
||
}
|
||
|
||
/// <summary>
|
||
/// Запускает мигание шляпы с настроенным интервалом.
|
||
/// </summary>
|
||
public void Blink() => Blink(blinkInterval);
|
||
|
||
/// <summary>
|
||
/// Запускает мигание шляпы с явно указанным интервалом.
|
||
/// </summary>
|
||
public void Blink(float interval)
|
||
{
|
||
StopBlink(enableHat: false);
|
||
_hatOn = true;
|
||
_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;
|
||
}
|
||
|
||
_hatOn = enableHat;
|
||
ComSystemManager.Send(enableHat ? hatOnCommand : hatOffCommand);
|
||
}
|
||
|
||
/// <summary>Возвращает <c>true</c>, если мигание шляпы сейчас активно.</summary>
|
||
public bool IsBlinking => _blinkingCoroutine != null;
|
||
|
||
/// <summary>Возвращает <c>true</c>, если шляпа включена (постоянно, без мигания).</summary>
|
||
public bool IsHatOn => _hatOn && _blinkingCoroutine == null;
|
||
|
||
/// <summary>Возвращает <c>true</c>, если последовательность эффектов сейчас активна.</summary>
|
||
public bool IsSequencePlaying => _activeSequence?.IsPlaying ?? false;
|
||
|
||
/// <summary>Возвращает <c>true</c>, если последовательность эффектов приостановлена.</summary>
|
||
public bool IsSequencePaused => _activeSequence?.IsPaused ?? false;
|
||
|
||
/// <summary>Возвращает <c>true</c>, если мигание было приостановлено.</summary>
|
||
public bool IsBlinkPaused => _blinkWasPaused;
|
||
|
||
/// <summary>
|
||
/// Повторно отправляет текущее состояние шляпы на плату.
|
||
/// Используется при переподключении, когда шляпа была просто включена (не моргала).
|
||
/// </summary>
|
||
public void ResendHatState()
|
||
{
|
||
ComSystemManager.Send(_hatOn ? hatOnCommand : hatOffCommand);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Приостанавливает все активные эффекты (подсветку и мигание).
|
||
/// Вызывается при отвале платы контроллера.
|
||
/// </summary>
|
||
public void PauseAll()
|
||
{
|
||
if (_blinkingCoroutine != null)
|
||
{
|
||
_blinkWasPaused = true;
|
||
_pausedBlinkInterval = blinkInterval;
|
||
StopCoroutine(_blinkingCoroutine);
|
||
_blinkingCoroutine = null;
|
||
}
|
||
else
|
||
{
|
||
_blinkWasPaused = false;
|
||
}
|
||
|
||
// _hatOn сохраняется как есть — при Resume будет восстановлено
|
||
|
||
_activeSequence?.Pause();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Возобновляет приостановленные эффекты (подсветку и мигание).
|
||
/// Вызывается при переподключении платы контроллера.
|
||
/// </summary>
|
||
public void ResumeAll()
|
||
{
|
||
if (_blinkWasPaused)
|
||
{
|
||
_blinkWasPaused = false;
|
||
_blinkingCoroutine = StartCoroutine(BlinkingCoroutine(_pausedBlinkInterval));
|
||
}
|
||
else if (_hatOn)
|
||
{
|
||
// Шляпа была просто включена (не моргала) — включаем обратно
|
||
ComSystemManager.Send(hatOnCommand);
|
||
}
|
||
|
||
if (_activeSequence != null && _activeSequence.IsPaused)
|
||
{
|
||
_activeSequence.Resume();
|
||
}
|
||
}
|
||
|
||
// ===========================end: PUBLIC API===========================
|
||
|
||
/// <summary>
|
||
/// Отправляет COM-команду для одного шага последовательности.
|
||
/// Используется из <see cref="LightSequence"/>.
|
||
/// </summary>
|
||
public void SendCommand(LightMode mode, int durationSeconds)
|
||
{
|
||
ComSystemManager.Send(BuildCommand(mode, durationSeconds));
|
||
}
|
||
|
||
// ReSharper disable once IteratorNeverReturns — корутина намеренно бесконечная
|
||
private IEnumerator BlinkingCoroutine(float interval)
|
||
{
|
||
while (true)
|
||
{
|
||
ComSystemManager.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 WaitForSecondsRealtime(wait);
|
||
}
|
||
}
|
||
|
||
private static string BuildCommand(LightMode mode, int durationSeconds)
|
||
{
|
||
if (mode == LightMode.Off)
|
||
return EffectPrefix;
|
||
|
||
int xx = (int)mode;
|
||
if (durationSeconds == -1)
|
||
return $"{EffectPrefix}{xx:D2}";
|
||
|
||
int yy = Mathf.Clamp(durationSeconds, 0, 99);
|
||
return $"{EffectPrefix}{xx:D2}{yy:D2}";
|
||
}
|
||
|
||
}
|
||
}
|
||
|