From 6b02c76ef36e471f9abb634a801deeb1d66aaac1 Mon Sep 17 00:00:00 2001 From: Danil Iarosh Date: Thu, 19 Feb 2026 16:52:59 +0300 Subject: [PATCH] =?UTF-8?q?refactor:=20LightController=20+=20LightSequence?= =?UTF-8?q?,=20=D0=BF=D1=80=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=BB=2085XXYY,?= =?UTF-8?q?=20namespace=20LightController=20/=20LightMode:=20-=20=D0=9D?= =?UTF-8?q?=D0=BE=D0=B2=D1=8B=D0=B9=20LightMode.cs:=20enum=20=D1=8D=D1=84?= =?UTF-8?q?=D1=84=D0=B5=D0=BA=D1=82=D0=BE=D0=B2=20=D0=BF=D0=BE=D0=B4=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=BB=2085XXYY=20(13?= =?UTF-8?q?=20=D1=8D=D1=84=D1=84=D0=B5=D0=BA=D1=82=D0=BE=D0=B2=20+=20Off)?= =?UTF-8?q?=20=20=20=D0=B7=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20enu?= =?UTF-8?q?m=20=3D=20=D0=BA=D0=BE=D0=B4=D1=8B=20=D0=BA=D0=BE=D0=BC=D0=B0?= =?UTF-8?q?=D0=BD=D0=B4=20XX,=20Off=3D-1=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80?= =?UTF-8?q?=D0=B8=D1=80=D1=83=D0=B5=D1=82=20=D0=BF=D1=80=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=BE=20'85'=20-=20=D0=9D=D0=BE=D0=B2=D1=8B=D0=B9=20LightContr?= =?UTF-8?q?oller.cs:=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BF=D0=BE=D0=B4=D1=81=D0=B2=D0=B5=D1=82=D0=BA?= =?UTF-8?q?=D0=BE=D0=B9=20=D0=BF=D0=BB=D0=B0=D1=82=D1=8B=20=20=20*=20SetLi?= =?UTF-8?q?ght(mode,=20duration)=20=E2=86=92=20=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B8=D1=82=20=D0=BA=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4=D1=83=2085X?= =?UTF-8?q?XYY,=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=89=D0=B0=D0=B5?= =?UTF-8?q?=D1=82=20LightSequence=20=20=20*=20Blink()=20/=20StopBlink()=20?= =?UTF-8?q?/=20SetBlinkInterval()=20=E2=80=94=20=D0=BC=D0=B8=D0=B3=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=88=D0=BB=D1=8F=D0=BF=D1=8B=20=20=20*?= =?UTF-8?q?=20StopSequence()=20/=20StopAllEffects()=20=E2=80=94=20=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BA=D0=B0=20=D1=8D=D1=84?= =?UTF-8?q?=D1=84=D0=B5=D0=BA=D1=82=D0=BE=D0=B2=20=20=20*=20=D0=92=D1=81?= =?UTF-8?q?=D0=B5=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D1=8B?= =?UTF-8?q?=20(hatOn/hatOff,=20blinkInterval,=20defaultMode/Duration)=20?= =?UTF-8?q?=D0=B2=20=D0=B8=D0=BD=D1=81=D0=BF=D0=B5=D0=BA=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D0=B5=20-=20=D0=92=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=20LightSequence=20?= =?UTF-8?q?=E2=80=94=20builder=20=D1=86=D0=B5=D0=BF=D0=BE=D1=87=D0=B5?= =?UTF-8?q?=D0=BA=20=D1=8D=D1=84=D1=84=D0=B5=D0=BA=D1=82=D0=BE=D0=B2:=20?= =?UTF-8?q?=20=20SetLight(A,3).Then(B,3).Then(C,3).Loop()=20=20=20SetLight?= =?UTF-8?q?(A,3).Then(LightMode.Off)=20=20=20SetLight(A,5)=20=E2=80=94=20?= =?UTF-8?q?=D0=BE=D0=B4=D0=B8=D0=BD=D0=BE=D1=87=D0=BD=D1=8B=D0=B9=20=D1=8D?= =?UTF-8?q?=D1=84=D1=84=D0=B5=D0=BA=D1=82=20GameManager:=20-=20=D0=94?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=BE=20=D1=81=D0=B2?= =?UTF-8?q?=D0=BE=D0=B9=D1=81=D1=82=D0=B2=D0=BE=20Light=20(=E2=86=92=20Pla?= =?UTF-8?q?te.Light)=20-=20GameWin=20=20=20=E2=86=92=20SetLight(FinalWave,?= =?UTF-8?q?=2030)=20-=20GameOver=20=20=E2=86=92=20SetLight(BlueWave,=2030)?= =?UTF-8?q?=20-=20NewGame=20=20=20=E2=86=92=20SetLight(Rainbow,=2099)=20-?= =?UTF-8?q?=20LoadScene=20MainMenu=20=E2=86=92=20SetLight(Off)=20+=20Blink?= =?UTF-8?q?()=20namespace=20(=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=B2=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B1?= =?UTF-8?q?=D0=B5=D0=B7=20namespace):=20-=20Editor/MouseWorldPositionOverl?= =?UTF-8?q?ay.cs=20=20=E2=86=92=20RR.Common.InspectorOverrides=20-=20UI/St?= =?UTF-8?q?atsWindowUIController.cs=20=20=20=20=20=20=20=20=E2=86=92=20RR.?= =?UTF-8?q?Common.UI=20-=20Utilities/AdvancedLogging=5FTest.cs=20=20=20=20?= =?UTF-8?q?=E2=86=92=20RR.Common.Utility=20-=20Utilities/DebugSettings.cs?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=E2=86=92=20RR.Common.Utility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controls/Inputs/ComSystem/ComWrapper.cs | 107 ++----- .../Inputs/ComSystem/LightController.cs | 265 ++++++++++++++++ .../Inputs/ComSystem/LightController.cs.meta | 3 + .../Controls/Inputs/ComSystem/LightMode.cs | 52 +++ .../Inputs/ComSystem/LightMode.cs.meta | 3 + .../Editor/MouseWorldPositionOverlay.cs | 4 + Assets/Scripts/Managers/GameManager.cs | 12 +- Assets/Scripts/UI/StatsWindowUIController.cs | 4 + .../Scripts/Utilities/AdvancedLogging_Test.cs | 299 +++++++++--------- Assets/Scripts/Utilities/DebugSettings.cs | 16 +- Packages/manifest.json | 10 +- Packages/packages-lock.json | 30 +- ProjectSettings/ProjectVersion.txt | 4 +- 13 files changed, 541 insertions(+), 268 deletions(-) create mode 100644 Assets/Scripts/Controls/Inputs/ComSystem/LightController.cs create mode 100644 Assets/Scripts/Controls/Inputs/ComSystem/LightController.cs.meta create mode 100644 Assets/Scripts/Controls/Inputs/ComSystem/LightMode.cs create mode 100644 Assets/Scripts/Controls/Inputs/ComSystem/LightMode.cs.meta diff --git a/Assets/Scripts/Controls/Inputs/ComSystem/ComWrapper.cs b/Assets/Scripts/Controls/Inputs/ComSystem/ComWrapper.cs index 7879c8b..0fea2c1 100644 --- a/Assets/Scripts/Controls/Inputs/ComSystem/ComWrapper.cs +++ b/Assets/Scripts/Controls/Inputs/ComSystem/ComWrapper.cs @@ -1,12 +1,10 @@ using System; -using System.Collections; using System.Text; using System.Threading; using System.Threading.Tasks; using RR.Game2D.Core.Managers; using UnityEngine; using UnityEngine.Events; -using Random = UnityEngine.Random; namespace RR.Common.Controls { @@ -30,7 +28,6 @@ namespace RR.Common.Controls #region Приватные поля private const string REQUEST_INPUT = "91"; - private const string LIGHTSIGNAL_PREFIX = "80"; private static int h; // идентификатор хэндла private static readonly object bufferLock = new object(); // Блокировка для синхронизации доступа к COM-порту @@ -55,7 +52,11 @@ namespace RR.Common.Controls // ВАЖНО: 0 = кнопка нажата, 1 = кнопка отпущена, поэтому по умолчанию все единицы private static string lastInputBits = "11111111111111111111111111"; // 26 бит по умолчанию - private Coroutine blinkingCoroutine; + // Контроллер подсветки (добавляется автоматически вместе с этим компонентом) + private LightController _light; + + /// Доступ к контроллеру подсветки. + public LightController Light => _light; #endregion @@ -83,11 +84,14 @@ namespace RR.Common.Controls if (!saveMoneyBetweenSessions) PlayerPrefs.SetInt("CoinsCount", 0); CreditsCount = PlayerPrefs.GetInt("CoinsCount", 0); + // Получаем или создаём LightController + _light = GetComponent() ?? gameObject.AddComponent(); + initPort(); // Отправляем сигнал запуска игры Send("2171"); - Blink(); + _light.Blink(); // Запускаем асинхронный опрос порта pollCancellationTokenSource = new CancellationTokenSource(); @@ -146,47 +150,6 @@ namespace RR.Common.Controls } } - /// - /// Отправляет команду управления подсветкой на периферийную плату. - /// - /// Режим подсветки из перечисления LightMode - /// - /// Формирует команду вида "80XXX" где XXX - код режима подсветки. - /// Для добавления новых режимов необходимо обновить вложенный метод getLightString + добавить режим в перечисление. - /// - public static void Send(LightMode lightMode) - { - try - { - string signal = LIGHTSIGNAL_PREFIX + getLightString(lightMode); - Send(signal); - - //ВОТЭТОТ МЕТОД ПОД СВОИ ПОДСВЕТКИ НАДО ПОДГОНЯТЬ - string getLightString(LightMode mode) => - - mode switch - { - LightMode.Idle1 => "500", - LightMode.Idle2 => "507", - LightMode.Orange => "501", - LightMode.Blue => "502", - LightMode.Win => "503", - LightMode.Lose => "504", - LightMode.Prize => "505", - LightMode.Tama => "513", - LightMode.Dog => "514", - LightMode.Sam => "515", - LightMode.Mistress => "516", - - _ => "500" - }; - - } - catch - { - // DebugWindow.Message("Ошибка отправки светового сигнала."); - } - } /// /// Выполняет списание платы за запуск игры. @@ -231,22 +194,16 @@ namespace RR.Common.Controls return false; } - public void Blink() - { - StopBlink(); - - blinkingCoroutine = StartCoroutine(BlinkingCoroutine()); - - - } - - public void StopBlink(bool enableHat = true) - { - if (blinkingCoroutine != null) StopCoroutine(blinkingCoroutine); - blinkingCoroutine = null; - Send(enableHat ? "2131" : "2130"); // включаем или выключаем шляпу - } - + /// + /// Запускает мигание подсветки. Делегируется в . + /// + public void Blink() => _light.Blink(); + + /// + /// Останавливает мигание подсветки. Делегируется в . + /// + public void StopBlink(bool enableHat = true) => _light.StopBlink(enableHat); + /// /// Получаем текущий баланс кредитов /// @@ -651,34 +608,8 @@ namespace RR.Common.Controls } } - private static IEnumerator BlinkingCoroutine() - { - while (true) - { - float randomFloat = Random.Range(0f, 1f); - - Send(randomFloat < 0.5f ? "2130" : "2131"); - - yield return new WaitForSeconds(0.5f); - } - } - #endregion } - public enum LightMode - { - Idle1, - Idle2, - Orange, - Blue, - Win, - Lose, - Prize, - Tama, - Dog, - Sam, - Mistress - } } diff --git a/Assets/Scripts/Controls/Inputs/ComSystem/LightController.cs b/Assets/Scripts/Controls/Inputs/ComSystem/LightController.cs new file mode 100644 index 0000000..21b9607 --- /dev/null +++ b/Assets/Scripts/Controls/Inputs/ComSystem/LightController.cs @@ -0,0 +1,265 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using Random = UnityEngine.Random; + +namespace RR.Common.Controls +{ + /// + /// Управляет подсветкой периферийной платы по протоколу 85XXYY: + /// + /// XX — код эффекта () + /// YY — время в секундах (00–99) + /// + /// Команда 85 (без параметров) отключает рендеринг и освобождает память. + /// Поддерживает цепочки эффектов через : + /// + /// SetLight(LightMode.VerticalWave, 3).Then(LightMode.DiagonalWave, 3).Loop(); + /// + /// + 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"; + + // ───────────────────────── Свойства ───────────────────────── + + /// Время эффекта по умолчанию (используется в ). + public int DefaultDuration => defaultDuration; + + // ===========================start: PUBLIC API=========================== + + /// + /// Включает эффект по умолчанию и возвращает для построения цепочки. + /// + public LightSequence SetLight() => SetLight(defaultLightMode, defaultDuration); + + /// + /// Включает эффект на время по умолчанию и возвращает для построения цепочки. + /// + public LightSequence SetLight(LightMode mode) => SetLight(mode, defaultDuration); + + /// + /// Включает световой эффект на указанное время и возвращает для построения цепочки. + /// Если вызвать без .Then() / .Loop() — эффект запустится немедленно как одиночный. + /// + /// Эффект подсветки + /// Время эффекта (0–99 секунд) + public LightSequence SetLight(LightMode mode, int durationSeconds) + { + StopSequence(); + _activeSequence = new LightSequence(this, mode, durationSeconds); + _activeSequence.Play(); + return _activeSequence; + } + + /// + /// Останавливает текущую активную последовательность эффектов и отключает рендеринг. + /// + public void StopSequence() + { + _activeSequence?.Stop(); + _activeSequence = null; + } + + /// + /// Останавливает все эффекты и отключает рендеринг платы (85). + /// + public void StopAllEffects() + { + StopSequence(); + ComWrapper.Send(EffectPrefix); + } + + /// + /// Запускает мигание шляпы с настроенным интервалом. + /// + public void Blink() => Blink(blinkInterval); + + /// + /// Запускает мигание шляпы с явно указанным интервалом. + /// + public void Blink(float interval) + { + StopBlink(enableHat: false); + _blinkingCoroutine = StartCoroutine(BlinkingCoroutine(interval)); + } + + /// + /// Меняет интервал мигания на лету; перезапускает мигание, если оно активно. + /// + public void SetBlinkInterval(float interval) + { + blinkInterval = Mathf.Max(0.05f, interval); + if (_blinkingCoroutine != null) Blink(blinkInterval); + } + + /// + /// Останавливает мигание шляпы. + /// + /// Оставить шляпу включённой после остановки + public void StopBlink(bool enableHat = true) + { + if (_blinkingCoroutine != null) + { + StopCoroutine(_blinkingCoroutine); + _blinkingCoroutine = null; + } + + ComWrapper.Send(enableHat ? hatOnCommand : hatOffCommand); + } + + /// Возвращает true, если мигание шляпы сейчас активно. + public bool IsBlinking => _blinkingCoroutine != null; + + /// Возвращает true, если последовательность эффектов сейчас активна. + public bool IsSequencePlaying => _activeSequence?.IsPlaying ?? false; + + // ===========================end: PUBLIC API=========================== + + /// + /// Отправляет COM-команду для одного шага последовательности. + /// Используется из . + /// + 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}"; + } + + /// + /// Строитель последовательности световых эффектов. + /// Создаётся через . + /// + /// SetLight(LightMode.VerticalWave, 3).Then(LightMode.DiagonalWave, 3).Loop(); + /// SetLight(LightMode.VerticalWave, 3).Then(LightMode.Off); + /// SetLight(LightMode.Rainbow, 5); + /// + /// + 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 _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)); + } + + /// Добавляет следующий эффект с временем по умолчанию. + public LightSequence Then(LightMode mode) => Then(mode, _controller.DefaultDuration); + + /// Добавляет следующий эффект с указанным временем. + public LightSequence Then(LightMode mode, int durationSeconds) + { + _steps.Add(new Step(mode, durationSeconds)); + return this; + } + + /// + /// Зацикливает последовательность. Для остановки — . + /// + public LightSequence Loop() + { + _looping = true; + Play(); + return this; + } + + /// Запускает последовательность один раз. + public LightSequence Play() + { + Stop(); + _coroutine = _controller.StartCoroutine(RunSequence()); + return this; + } + + /// Останавливает выполнение последовательности. + public void Stop() + { + if (_coroutine == null) return; + _controller.StopCoroutine(_coroutine); + _coroutine = null; + } + + /// Возвращает true, если последовательность сейчас выполняется. + 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; + } + } + } +} diff --git a/Assets/Scripts/Controls/Inputs/ComSystem/LightController.cs.meta b/Assets/Scripts/Controls/Inputs/ComSystem/LightController.cs.meta new file mode 100644 index 0000000..dcfe4a9 --- /dev/null +++ b/Assets/Scripts/Controls/Inputs/ComSystem/LightController.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9b5ae7d3aa86455381891033da97820b +timeCreated: 1771504477 \ No newline at end of file diff --git a/Assets/Scripts/Controls/Inputs/ComSystem/LightMode.cs b/Assets/Scripts/Controls/Inputs/ComSystem/LightMode.cs new file mode 100644 index 0000000..6710e65 --- /dev/null +++ b/Assets/Scripts/Controls/Inputs/ComSystem/LightMode.cs @@ -0,0 +1,52 @@ +namespace RR.Common.Controls +{ + /// + /// Эффекты подсветки периферийной платы (протокол 85XXYY). + /// XX — код эффекта, YY — время в секундах. + /// Команда 85 (без параметров) отключает рендеринг эффектов и освобождает память. + /// + public enum LightMode + { + /// Отключить рендеринг эффектов (освобождает память). Команда: 85 + Off = -1, + + /// Горизонтальная волна. XX = 40 + HorizontalWave = 40, + + /// Вертикальная волна. XX = 41 + VerticalWave = 41, + + /// Диагональная волна. XX = 42 + DiagonalWave = 42, + + /// Радуга. XX = 43 + Rainbow = 43, + + /// Волна вверх. XX = 44 + WaveUp = 44, + + /// Вертикальная волна вверх. XX = 45 + VerticalWaveUp = 45, + + /// Конечная волна. XX = 46 + FinalWave = 46, + + /// Синяя волна. XX = 47 + BlueWave = 47, + + /// Диагональная волна 2. XX = 48 + DiagonalWave2 = 48, + + /// Падающие ряды. XX = 49 + FallingRows = 49, + + /// Оранжевая волна. XX = 50 + OrangeWave = 50, + + /// Морская волна. XX = 51 + SeaWave = 51, + + /// Диагональная волна 3. XX = 52 + DiagonalWave3 = 52, + } +} diff --git a/Assets/Scripts/Controls/Inputs/ComSystem/LightMode.cs.meta b/Assets/Scripts/Controls/Inputs/ComSystem/LightMode.cs.meta new file mode 100644 index 0000000..d87645b --- /dev/null +++ b/Assets/Scripts/Controls/Inputs/ComSystem/LightMode.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2d1d29b94c0748148217efb46bcc2816 +timeCreated: 1771504563 \ No newline at end of file diff --git a/Assets/Scripts/Editor/MouseWorldPositionOverlay.cs b/Assets/Scripts/Editor/MouseWorldPositionOverlay.cs index ad95fe8..546180d 100644 --- a/Assets/Scripts/Editor/MouseWorldPositionOverlay.cs +++ b/Assets/Scripts/Editor/MouseWorldPositionOverlay.cs @@ -4,6 +4,9 @@ using UnityEditor.Overlays; using UnityEngine; using UnityEngine.UIElements; +namespace RR.Common.InspectorOverrides +{ + [Overlay(typeof(SceneView), id: "mouse-world-position", displayName: "Mouse World Position")] [Icon("d_Grid.BoxTool")] public class MouseWorldPositionOverlay : Overlay @@ -132,4 +135,5 @@ public class MouseWorldPositionOverlay : Overlay } } +} // namespace RR.Common.InspectorOverrides #endif diff --git a/Assets/Scripts/Managers/GameManager.cs b/Assets/Scripts/Managers/GameManager.cs index 24823ff..993a907 100644 --- a/Assets/Scripts/Managers/GameManager.cs +++ b/Assets/Scripts/Managers/GameManager.cs @@ -33,6 +33,8 @@ namespace RR.Game2D.Core.Managers // Plate control for game public ComWrapper Plate { get; private set; } + // Light control for game + public LightController Light => Plate != null ? Plate.Light : null; // Globals [Header("Globals")] @@ -330,6 +332,7 @@ public void TogglePause() => SetPause(!IsPaused); Logger.Log(gameObject.name,$"Game start time = {_gameStartTime}"); if (Plate) Plate.StopBlink(); + Light?.SetLight(LightMode.Rainbow, 99); // Включаем отслеживание бездействия if (Inactivity != null) Inactivity.EnableInactivityCheck(); @@ -369,23 +372,27 @@ public void TogglePause() => SetPause(!IsPaused); /// Окончание игры. /// Загружает сцену главного меню /// - private void GameOver() // => LoadScene("MainMenu", false); + private void GameOver() { float totalTime = Time.unscaledTime - _gameStartTime; // Отключаем отслеживание бездействия if (Inactivity != null) Inactivity.DisableInactivityCheck(); + + Light?.SetLight(LightMode.BlueWave, 30); GameOverEvent?.Invoke(totalTime); } // -- - private void GameWin() // => LoadScene("MainMenu", false); + private void GameWin() { float totalTime = Time.unscaledTime - _gameStartTime; // Отключаем отслеживание бездействия if (Inactivity != null) Inactivity.DisableInactivityCheck(); + + Light?.SetLight(LightMode.FinalWave, 30); GameWonEvent?.Invoke(totalTime); } @@ -466,6 +473,7 @@ public void TogglePause() => SetPause(!IsPaused); if (useUI) SceneManager.LoadScene("Level UI", LoadSceneMode.Additive); else { + Light?.SetLight(LightMode.Off); Plate.Blink(); _shouldSpawnPlayer = false; } diff --git a/Assets/Scripts/UI/StatsWindowUIController.cs b/Assets/Scripts/UI/StatsWindowUIController.cs index 4aeedcf..c9ab7c4 100644 --- a/Assets/Scripts/UI/StatsWindowUIController.cs +++ b/Assets/Scripts/UI/StatsWindowUIController.cs @@ -5,6 +5,9 @@ using TMPro; using UnityEngine; using UnityEngine.InputSystem; +namespace RR.Common.UI +{ + public class StatsWindowUIController : MonoBehaviour { [Header("Управление")] @@ -103,3 +106,4 @@ public class StatsWindowUIController : MonoBehaviour onDone?.Invoke(); } } +} // namespace RR.Common.UI diff --git a/Assets/Scripts/Utilities/AdvancedLogging_Test.cs b/Assets/Scripts/Utilities/AdvancedLogging_Test.cs index a40b146..f7daaed 100644 --- a/Assets/Scripts/Utilities/AdvancedLogging_Test.cs +++ b/Assets/Scripts/Utilities/AdvancedLogging_Test.cs @@ -1,175 +1,176 @@ using UnityEngine; using System; -using RR.Common.Utility; -/// -/// Продвинутый пример использования системы логирования. -/// Демонстрирует различные сценарии использования FileLogger и CompositeLogger. -/// -public class AdvancedLogging_Test : MonoBehaviour +namespace RR.Common.Utility { - private CompositeLogger compositeLogger; - private FileLogger mainFileLogger; - private FileLogger errorFileLogger; - - void Start() - { - SetupAdvancedLogging(); - DemonstrateLogging(); - } - /// - /// Настройка продвинутой системы логирования с несколькими выходными файлами + /// Продвинутый пример использования системы логирования. + /// Демонстрирует различные сценарии использования FileLogger и CompositeLogger. /// - private void SetupAdvancedLogging() + public class AdvancedLogging_Test : MonoBehaviour { - // Создаём композитный логгер - compositeLogger = new CompositeLogger(); - compositeLogger.Initialize(); + private CompositeLogger compositeLogger; + private FileLogger mainFileLogger; + private FileLogger errorFileLogger; - // Главный файл - все логи - mainFileLogger = new FileLogger( - System.IO.Path.Combine(Application.persistentDataPath, "full_log.txt"), - append: true - ); - mainFileLogger.filterLogType = LogType.Log; // Все типы сообщений - - // Отдельный файл только для ошибок и исключений - errorFileLogger = new FileLogger( - System.IO.Path.Combine(Application.persistentDataPath, "errors_only.txt"), - append: true - ); - errorFileLogger.filterLogType = LogType.Warning; // Только Warning, Error, Exception - - // Добавляем оба логгера - compositeLogger.AddLoggers(mainFileLogger, errorFileLogger); - - Debug.Log($"Логирование настроено. Файлы в: {Application.persistentDataPath}"); - } - - /// - /// Демонстрация различных типов логирования - /// - private void DemonstrateLogging() - { - // Простые сообщения - compositeLogger.Log("=== Демонстрация системы логирования ==="); - - // Сообщения с тегами - compositeLogger.Log("SYSTEM", "Игра инициализирована"); - compositeLogger.LogWarning("AUDIO", "Громкость музыки установлена в 0.5"); - - // Форматированные сообщения - string playerName = "Player1"; - int score = 1000; - compositeLogger.LogFormat(LogType.Log, "Игрок {0} набрал {1} очков", playerName, score); - - // Логирование с контекстом - compositeLogger.Log("Объект создан", this.gameObject); - - // Имитация ошибки - compositeLogger.LogError("ERROR", "Это тестовая ошибка - будет в обоих файлах!"); - - // Имитация исключения - try + void Start() { - throw new InvalidOperationException("Тестовое исключение для демонстрации"); - } - catch (Exception ex) - { - compositeLogger.LogException(ex, this.gameObject); + SetupAdvancedLogging(); + DemonstrateLogging(); } - // Информация о путях - Debug.Log($"Полный лог: {Application.persistentDataPath}/full_log.txt"); - Debug.Log($"Только ошибки: {Application.persistentDataPath}/errors_only.txt"); - } + /// + /// Настройка продвинутой системы логирования с несколькими выходными файлами + /// + private void SetupAdvancedLogging() + { + // Создаём композитный логгер + compositeLogger = new CompositeLogger(); + compositeLogger.Initialize(); - /// - /// Пример метода для логирования игровых событий - /// - public void LogGameEvent(string eventName, params object[] parameters) - { - string paramStr = string.Join(", ", parameters); - compositeLogger.Log("GAME_EVENT", $"{eventName}: {paramStr}"); - } + // Главный файл - все логи + mainFileLogger = new FileLogger( + System.IO.Path.Combine(Application.persistentDataPath, "full_log.txt"), + append: true + ); + mainFileLogger.filterLogType = LogType.Log; // Все типы сообщений - /// - /// Пример метода для логирования производительности - /// - public void LogPerformance(string operation, float duration) - { - if (duration > 0.016f) // Больше 16мс (60 FPS) - { - compositeLogger.LogWarning("PERFORMANCE", $"{operation} занял {duration * 1000:F2}мс"); - } - else - { - compositeLogger.Log("PERFORMANCE", $"{operation} выполнен за {duration * 1000:F2}мс"); - } - } + // Отдельный файл только для ошибок и исключений + errorFileLogger = new FileLogger( + System.IO.Path.Combine(Application.persistentDataPath, "errors_only.txt"), + append: true + ); + errorFileLogger.filterLogType = LogType.Warning; // Только Warning, Error, Exception - /// - /// Пример обёртки для измерения времени выполнения - /// - public void PerformOperationWithLogging(string operationName, Action operation) - { - float startTime = Time.realtimeSinceStartup; - - try - { - operation.Invoke(); - float duration = Time.realtimeSinceStartup - startTime; - LogPerformance(operationName, duration); - } - catch (Exception ex) - { - compositeLogger.LogException(ex); - compositeLogger.LogError("OPERATION", $"Операция '{operationName}' завершилась с ошибкой"); - } - } + // Добавляем оба логгера + compositeLogger.AddLoggers(mainFileLogger, errorFileLogger); - void Update() - { - // Пример: логируем нажатия клавиш - if (Input.GetKeyDown(KeyCode.L)) - { - LogGameEvent("KeyPress", "L", Time.time); + Debug.Log($"Логирование настроено. Файлы в: {Application.persistentDataPath}"); } - if (Input.GetKeyDown(KeyCode.F)) + /// + /// Демонстрация различных типов логирования + /// + private void DemonstrateLogging() { - // Принудительная запись на диск - mainFileLogger.Flush(); - errorFileLogger.Flush(); - compositeLogger.Log("SYSTEM", "Логи принудительно сохранены на диск"); + // Простые сообщения + compositeLogger.Log("=== Демонстрация системы логирования ==="); + + // Сообщения с тегами + compositeLogger.Log("SYSTEM", "Игра инициализирована"); + compositeLogger.LogWarning("AUDIO", "Громкость музыки установлена в 0.5"); + + // Форматированные сообщения + string playerName = "Player1"; + int score = 1000; + compositeLogger.LogFormat(LogType.Log, "Игрок {0} набрал {1} очков", playerName, score); + + // Логирование с контекстом + compositeLogger.Log("Объект создан", this.gameObject); + + // Имитация ошибки + compositeLogger.LogError("ERROR", "Это тестовая ошибка - будет в обоих файлах!"); + + // Имитация исключения + try + { + throw new InvalidOperationException("Тестовое исключение для демонстрации"); + } + catch (Exception ex) + { + compositeLogger.LogException(ex, this.gameObject); + } + + // Информация о путях + Debug.Log($"Полный лог: {Application.persistentDataPath}/full_log.txt"); + Debug.Log($"Только ошибки: {Application.persistentDataPath}/errors_only.txt"); } - if (Input.GetKeyDown(KeyCode.C)) + /// + /// Пример метода для логирования игровых событий + /// + public void LogGameEvent(string eventName, params object[] parameters) { - // Очистка логов - mainFileLogger.Clear(); - errorFileLogger.Clear(); - compositeLogger.Log("SYSTEM", "Файлы логов очищены"); + string paramStr = string.Join(", ", parameters); + compositeLogger.Log("GAME_EVENT", $"{eventName}: {paramStr}"); } - } - void OnApplicationQuit() - { - compositeLogger.Log("SYSTEM", "Приложение завершается..."); - - // Важно: освобождаем ресурсы - mainFileLogger?.Dispose(); - errorFileLogger?.Dispose(); - - Debug.Log("Файловые логгеры закрыты"); - } + /// + /// Пример метода для логирования производительности + /// + public void LogPerformance(string operation, float duration) + { + if (duration > 0.016f) // Больше 16мс (60 FPS) + { + compositeLogger.LogWarning("PERFORMANCE", $"{operation} занял {duration * 1000:F2}мс"); + } + else + { + compositeLogger.Log("PERFORMANCE", $"{operation} выполнен за {duration * 1000:F2}мс"); + } + } - void OnDestroy() - { - // На всякий случай - mainFileLogger?.Dispose(); - errorFileLogger?.Dispose(); + /// + /// Пример обёртки для измерения времени выполнения + /// + public void PerformOperationWithLogging(string operationName, Action operation) + { + float startTime = Time.realtimeSinceStartup; + + try + { + operation.Invoke(); + float duration = Time.realtimeSinceStartup - startTime; + LogPerformance(operationName, duration); + } + catch (Exception ex) + { + compositeLogger.LogException(ex); + compositeLogger.LogError("OPERATION", $"Операция '{operationName}' завершилась с ошибкой"); + } + } + + void Update() + { + // Пример: логируем нажатия клавиш + if (Input.GetKeyDown(KeyCode.L)) + { + LogGameEvent("KeyPress", "L", Time.time); + } + + if (Input.GetKeyDown(KeyCode.F)) + { + // Принудительная запись на диск + mainFileLogger.Flush(); + errorFileLogger.Flush(); + compositeLogger.Log("SYSTEM", "Логи принудительно сохранены на диск"); + } + + if (Input.GetKeyDown(KeyCode.C)) + { + // Очистка логов + mainFileLogger.Clear(); + errorFileLogger.Clear(); + compositeLogger.Log("SYSTEM", "Файлы логов очищены"); + } + } + + void OnApplicationQuit() + { + compositeLogger.Log("SYSTEM", "Приложение завершается..."); + + // Важно: освобождаем ресурсы + mainFileLogger?.Dispose(); + errorFileLogger?.Dispose(); + + Debug.Log("Файловые логгеры закрыты"); + } + + void OnDestroy() + { + // На всякий случай + mainFileLogger?.Dispose(); + errorFileLogger?.Dispose(); + } } } - diff --git a/Assets/Scripts/Utilities/DebugSettings.cs b/Assets/Scripts/Utilities/DebugSettings.cs index 2852847..d88710d 100644 --- a/Assets/Scripts/Utilities/DebugSettings.cs +++ b/Assets/Scripts/Utilities/DebugSettings.cs @@ -1,14 +1,16 @@ using UnityEngine; - using RR.Game2D.Core.Managers; -public class DebugSettings : MonoBehaviour +namespace RR.Common.Utility { - [SerializeField] private int livesCount = 99; - [SerializeField] private int coinsCount; - // Start is called once before the first execution of Update after the MonoBehaviour is created - void Start() + public class DebugSettings : MonoBehaviour { - GameManager.Instance.Lives = livesCount; + [SerializeField] private int livesCount = 99; + [SerializeField] private int coinsCount; + + void Start() + { + GameManager.Instance.Lives = livesCount; + } } } diff --git a/Packages/manifest.json b/Packages/manifest.json index f47dbca..3efee6b 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -1,17 +1,17 @@ { "dependencies": { "com.github-glitchenzo.nugetforunity": "https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity", - "com.unity.ai.inference": "2.4.1", + "com.unity.ai.inference": "2.5.0", "com.unity.cinemachine": "3.1.5", - "com.unity.collab-proxy": "2.10.2", + "com.unity.collab-proxy": "2.11.3", "com.unity.feature.2d": "2.0.2", "com.unity.ide.rider": "3.0.39", - "com.unity.ide.visualstudio": "2.0.25", - "com.unity.inputsystem": "1.17.0", + "com.unity.ide.visualstudio": "2.0.26", + "com.unity.inputsystem": "1.18.0", "com.unity.multiplayer.center": "1.0.1", "com.unity.render-pipelines.universal": "17.3.0", "com.unity.test-framework": "1.6.0", - "com.unity.timeline": "1.8.9", + "com.unity.timeline": "1.8.10", "com.unity.ugui": "2.0.0", "com.unity.visualeffectgraph": "17.3.0", "com.unity.visualscripting": "1.9.9", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index e271208..51ac49d 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -8,11 +8,11 @@ "hash": "34432703a177c97631a4eb1d908f086037c67969" }, "com.unity.2d.animation": { - "version": "13.0.2", + "version": "13.0.4", "depth": 1, "source": "registry", "dependencies": { - "com.unity.2d.common": "12.0.1", + "com.unity.2d.common": "12.0.2", "com.unity.2d.sprite": "1.0.0", "com.unity.collections": "2.4.3", "com.unity.modules.animation": "1.0.0", @@ -34,7 +34,7 @@ "url": "https://packages.unity.com" }, "com.unity.2d.common": { - "version": "12.0.1", + "version": "12.0.2", "depth": 2, "source": "registry", "dependencies": { @@ -106,22 +106,22 @@ "url": "https://packages.unity.com" }, "com.unity.2d.tooling": { - "version": "1.0.0", + "version": "1.0.2", "depth": 1, "source": "registry", "dependencies": { - "com.unity.2d.common": "12.0.1", + "com.unity.2d.common": "12.0.2", "com.unity.modules.uielements": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.ai.inference": { - "version": "2.4.1", + "version": "2.5.0", "depth": 0, "source": "registry", "dependencies": { "com.unity.burst": "1.8.17", - "com.unity.dt.app-ui": "1.3.1", + "com.unity.dt.app-ui": "1.3.3", "com.unity.collections": "2.4.3", "com.unity.nuget.newtonsoft-json": "3.2.1", "com.unity.modules.imageconversion": "1.0.0" @@ -129,7 +129,7 @@ "url": "https://packages.unity.com" }, "com.unity.burst": { - "version": "1.8.26", + "version": "1.8.27", "depth": 1, "source": "registry", "dependencies": { @@ -149,7 +149,7 @@ "url": "https://packages.unity.com" }, "com.unity.collab-proxy": { - "version": "2.10.2", + "version": "2.11.3", "depth": 0, "source": "registry", "dependencies": {}, @@ -191,7 +191,7 @@ "depth": 0, "source": "builtin", "dependencies": { - "com.unity.2d.animation": "13.0.2", + "com.unity.2d.animation": "13.0.4", "com.unity.2d.pixel-perfect": "5.1.1", "com.unity.2d.psdimporter": "12.0.1", "com.unity.2d.sprite": "1.0.0", @@ -199,7 +199,7 @@ "com.unity.2d.tilemap": "1.0.0", "com.unity.2d.tilemap.extras": "6.0.1", "com.unity.2d.aseprite": "3.0.1", - "com.unity.2d.tooling": "1.0.0" + "com.unity.2d.tooling": "1.0.2" } }, "com.unity.ide.rider": { @@ -212,16 +212,16 @@ "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { - "version": "2.0.25", + "version": "2.0.26", "depth": 0, "source": "registry", "dependencies": { - "com.unity.test-framework": "1.1.31" + "com.unity.test-framework": "1.1.33" }, "url": "https://packages.unity.com" }, "com.unity.inputsystem": { - "version": "1.17.0", + "version": "1.18.0", "depth": 0, "source": "registry", "dependencies": { @@ -345,7 +345,7 @@ "url": "https://packages.unity.com" }, "com.unity.timeline": { - "version": "1.8.9", + "version": "1.8.10", "depth": 0, "source": "registry", "dependencies": { diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index 879b68f..aad22be 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 6000.3.2f1 -m_EditorVersionWithRevision: 6000.3.2f1 (a9779f353c9b) +m_EditorVersion: 6000.3.8f1 +m_EditorVersionWithRevision: 6000.3.8f1 (1c7db571dde0)