Подробный список изменений: - Перенесены эффекты частиц из C#-кода в готовые префабы-ассеты (Vfx Prefabs) в папке Assets/Resources/Vfx/ с автоматической генерацией материалов в Editor. - Исправлены ошибки duration на играющих системах частиц и розовые текстуры в GameVfxService.cs. - Исправлена навигация и фокус кнопок на оверлеях ContinueGameOverlay и PrizeResultOverlay с автоматическим подсвечиванием кнопок лимонно-зеленым цветом при фокусе и проигрыванием звука. - Добавлена проверка настроек в GameplayHudController.cs для скрытия ContinueGameOverlay, если продолжение игры отключено. - Расширен сбор статистики: создана структура GameplaySessionRecord, добавлена история до 50 раундов в PrizeSessionStateService и JsonPrizeSessionStateRepository, выведен расчет средних показателей и список 5 последних раундов в сервисной статистике. - Повышена надежность COM-транспорта: добавлен sleep (80мс) в Dispose для graceful shutdown потоков, реализован цикл переподключения до 10 попыток при обрыве связи. - Интегрированы настройки громкости с микшером: изменен шаг изменения громкости на 5% (0.05) для удобства, добавлен статус-чек и логи в ApplyServiceVolumes, расширены юнит-тесты на GetFloat параметры. - Созданы новые архитектурные гайды по аппаратному COM-транспорту и призовой экономике автомата в папке GUIDES/architecture/. - Оптимизирован и обновлен RAG-индекс проекта.
5.9 KiB
Комбинированный пример: Zenject + UniTask + Addressables для старта игры
В современных проектах старт игры — это не просто SceneManager.LoadScene(). Это цепочка асинхронных операций: проверка обновлений, загрузка баз данных (баланс, цены), авторизация, инициализация железа (как в Danro Jump) и только затем показ меню.
Этот гайд показывает, как объединить 3 главные библиотеки проекта в одну элегантную систему загрузки.
1. Интерфейс Загрузочной Задачи (Task)
Для начала, сделаем систему расширяемой. Вместо жестко закодированных вызовов, создадим интерфейс IStartupTask.
using Cysharp.Threading.Tasks;
public interface IStartupTask
{
// Вес задачи для прогресс-бара (например, 10%)
float ProgressWeight { get; }
// Выполнение задачи с возможностью отмены
UniTask ExecuteAsync(System.Threading.CancellationToken ct);
}
2. Реализация задач (Примеры)
Задача 1: Загрузка конфигураций (Addressables)
using UnityEngine.AddressableAssets;
public class LoadConfigTask : IStartupTask
{
public float ProgressWeight => 30f;
private readonly ConfigManager configManager;
// Внедряем зависимость!
public LoadConfigTask(ConfigManager configManager) => this.configManager = configManager;
public async UniTask ExecuteAsync(System.Threading.CancellationToken ct)
{
// Загружаем глобальные настройки из Addressables
var config = await Addressables.LoadAssetAsync<GameConfig>("GlobalConfig")
.WithCancellation(ct); // Привязываем токен отмены
configManager.Initialize(config);
}
}
Задача 2: Подключение оборудования (COM-порт / UniTask)
public class HardwareInitTask : IStartupTask
{
public float ProgressWeight => 40f;
private readonly ComSystem comSystem;
public HardwareInitTask(ComSystem comSystem) => this.comSystem = comSystem;
public async UniTask ExecuteAsync(System.Threading.CancellationToken ct)
{
// Инициализация железа с таймаутом в 5 секунд!
try
{
await comSystem.InitializeHardwareAsync().Timeout(System.TimeSpan.FromSeconds(5));
}
catch (System.TimeoutException)
{
UnityEngine.Debug.LogError("Железо не ответило вовремя!");
// Показать ошибку на экране
}
}
}
3. Регистрация задач в Zenject (Installer)
В BootstrapInstaller (привязанном к первой пустой сцене) мы регистрируем все наши задачи.
public class BootstrapInstaller : MonoInstaller
{
public override void InstallBindings()
{
// Биндим сам менеджер загрузки
Container.BindInterfacesAndSelfTo<AppBootstrapper>().AsSingle();
// Биндим задачи как список. Zenject соберет их все в один List<IStartupTask>
Container.Bind<IStartupTask>().To<LoadConfigTask>().AsCached();
Container.Bind<IStartupTask>().To<HardwareInitTask>().AsCached();
}
}
4. Исполнитель (AppBootstrapper)
Этот класс запустится автоматически благодаря интерфейсу IInitializable.
using System.Collections.Generic;
using Zenject;
using Cysharp.Threading.Tasks;
public class AppBootstrapper : IInitializable, System.IDisposable
{
private readonly List<IStartupTask> tasks;
private readonly ISceneFlowService sceneFlow;
private System.Threading.CancellationTokenSource cts;
// Zenject автоматически передаст все классы, которые мы привязали к IStartupTask
public AppBootstrapper(List<IStartupTask> tasks, ISceneFlowService sceneFlow)
{
this.tasks = tasks;
this.sceneFlow = sceneFlow;
}
public void Initialize()
{
cts = new System.Threading.CancellationTokenSource();
RunStartupFlowAsync(cts.Token).Forget();
}
private async UniTaskVoid RunStartupFlowAsync(System.Threading.CancellationToken ct)
{
float totalProgress = 0f;
foreach (var task in tasks)
{
// Выполняем задачу последовательно
await task.ExecuteAsync(ct);
// Здесь можно вызывать сигнал для обновления UI прогресс-бара
totalProgress += task.ProgressWeight;
UnityEngine.Debug.Log($"Progress: {totalProgress}%");
}
// 4. Когда все задачи выполнены — переходим в меню!
sceneFlow.OpenMainMenu();
}
public void Dispose()
{
cts?.Cancel();
cts?.Dispose();
}
}
Итог: Мы получили масштабируемую, потокобезопасную (в рамках UniTask) и легко тестируемую систему инициализации. Чтобы добавить новую логику (например, проверку лицензии), нужно просто создать новый класс IStartupTask и забиндить его в инсталлере в 1 строчку кода! Никаких гигантских God-объектов.