refactor: LightController + LightSequence, протокол 85XXYY, namespace
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
This commit is contained in:
Данил Ярош 2026-02-19 16:52:59 +03:00
parent 301e4be47d
commit 6b02c76ef3
13 changed files with 541 additions and 268 deletions

View File

@ -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;
/// <summary>Доступ к контроллеру подсветки.</summary>
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<LightController>() ?? gameObject.AddComponent<LightController>();
initPort();
// Отправляем сигнал запуска игры
Send("2171");
Blink();
_light.Blink();
// Запускаем асинхронный опрос порта
pollCancellationTokenSource = new CancellationTokenSource();
@ -146,47 +150,6 @@ namespace RR.Common.Controls
}
}
/// <summary>
/// Отправляет команду управления подсветкой на периферийную плату.
/// </summary>
/// <param name="lightMode">Режим подсветки из перечисления LightMode</param>
/// <remarks>
/// Формирует команду вида "80XXX" где XXX - код режима подсветки.
/// Для добавления новых режимов необходимо обновить вложенный метод getLightString + добавить режим в перечисление.
/// </remarks>
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("Ошибка отправки светового сигнала.");
}
}
/// <summary>
/// Выполняет списание платы за запуск игры.
@ -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"); // включаем или выключаем шляпу
}
/// <summary>
/// Запускает мигание подсветки. Делегируется в <see cref="LightController"/>.
/// </summary>
public void Blink() => _light.Blink();
/// <summary>
/// Останавливает мигание подсветки. Делегируется в <see cref="LightController"/>.
/// </summary>
public void StopBlink(bool enableHat = true) => _light.StopBlink(enableHat);
/// <summary>
/// Получаем текущий баланс кредитов
/// </summary>
@ -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
}
}

View File

@ -0,0 +1,265 @@
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;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9b5ae7d3aa86455381891033da97820b
timeCreated: 1771504477

View File

@ -0,0 +1,52 @@
namespace RR.Common.Controls
{
/// <summary>
/// Эффекты подсветки периферийной платы (протокол <c>85XXYY</c>).
/// <c>XX</c> — код эффекта, <c>YY</c> — время в секундах.
/// Команда <c>85</c> (без параметров) отключает рендеринг эффектов и освобождает память.
/// </summary>
public enum LightMode
{
/// <summary>Отключить рендеринг эффектов (освобождает память). Команда: <c>85</c></summary>
Off = -1,
/// <summary>Горизонтальная волна. XX = 40</summary>
HorizontalWave = 40,
/// <summary>Вертикальная волна. XX = 41</summary>
VerticalWave = 41,
/// <summary>Диагональная волна. XX = 42</summary>
DiagonalWave = 42,
/// <summary>Радуга. XX = 43</summary>
Rainbow = 43,
/// <summary>Волна вверх. XX = 44</summary>
WaveUp = 44,
/// <summary>Вертикальная волна вверх. XX = 45</summary>
VerticalWaveUp = 45,
/// <summary>Конечная волна. XX = 46</summary>
FinalWave = 46,
/// <summary>Синяя волна. XX = 47</summary>
BlueWave = 47,
/// <summary>Диагональная волна 2. XX = 48</summary>
DiagonalWave2 = 48,
/// <summary>Падающие ряды. XX = 49</summary>
FallingRows = 49,
/// <summary>Оранжевая волна. XX = 50</summary>
OrangeWave = 50,
/// <summary>Морская волна. XX = 51</summary>
SeaWave = 51,
/// <summary>Диагональная волна 3. XX = 52</summary>
DiagonalWave3 = 52,
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2d1d29b94c0748148217efb46bcc2816
timeCreated: 1771504563

View File

@ -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

View File

@ -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);
/// Окончание игры.
/// Загружает сцену главного меню
/// </summary>
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;
}

View File

@ -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

View File

@ -1,175 +1,176 @@
using UnityEngine;
using System;
using RR.Common.Utility;
/// <summary>
/// Продвинутый пример использования системы логирования.
/// Демонстрирует различные сценарии использования FileLogger и CompositeLogger.
/// </summary>
public class AdvancedLogging_Test : MonoBehaviour
namespace RR.Common.Utility
{
private CompositeLogger compositeLogger;
private FileLogger mainFileLogger;
private FileLogger errorFileLogger;
void Start()
{
SetupAdvancedLogging();
DemonstrateLogging();
}
/// <summary>
/// Настройка продвинутой системы логирования с несколькими выходными файлами
/// Продвинутый пример использования системы логирования.
/// Демонстрирует различные сценарии использования FileLogger и CompositeLogger.
/// </summary>
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}");
}
/// <summary>
/// Демонстрация различных типов логирования
/// </summary>
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");
}
/// <summary>
/// Настройка продвинутой системы логирования с несколькими выходными файлами
/// </summary>
private void SetupAdvancedLogging()
{
// Создаём композитный логгер
compositeLogger = new CompositeLogger();
compositeLogger.Initialize();
/// <summary>
/// Пример метода для логирования игровых событий
/// </summary>
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; // Все типы сообщений
/// <summary>
/// Пример метода для логирования производительности
/// </summary>
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
/// <summary>
/// Пример обёртки для измерения времени выполнения
/// </summary>
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))
/// <summary>
/// Демонстрация различных типов логирования
/// </summary>
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))
/// <summary>
/// Пример метода для логирования игровых событий
/// </summary>
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("Файловые логгеры закрыты");
}
/// <summary>
/// Пример метода для логирования производительности
/// </summary>
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();
/// <summary>
/// Пример обёртки для измерения времени выполнения
/// </summary>
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();
}
}
}

View File

@ -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;
}
}
}

View File

@ -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",

View File

@ -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": {

View File

@ -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)