Danil Iarosh 839a68aef1 Improve VFX, UI overlays, session statistics, COM reliability, audio mixer, and expand architecture guides
Подробный список изменений:
- Перенесены эффекты частиц из 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-индекс проекта.
2026-07-03 15:04:08 +03:00

6.4 KiB
Raw Permalink Blame History

Полное руководство по UniTask

UniTask (Cysharp.Threading.Tasks) — это библиотека, которая заменяет стандартные Unity Coroutine и System.Threading.Tasks.Task.

Главное преимущество UniTask — полное отсутствие аллокаций памяти (Zero Allocation). Структуры UniTask созданы специально для Unity, они интегрированы с PlayerLoop и работают быстрее стандартных тасок C#.

1. Базовые принципы

  • Если метод возвращает Task или IEnumerator, перепишите его на UniTask.
  • Вместо yield return null используйте await UniTask.Yield().
  • Вместо yield return new WaitForSeconds(1) используйте await UniTask.Delay(1000).
using Cysharp.Threading.Tasks;

public async UniTaskVoid StartGameSequenceAsync()
{
    Debug.Log("Start");
    // Ждем 1 секунду игрового времени (учитывает Time.timeScale)
    await UniTask.Delay(TimeSpan.FromSeconds(1));
    Debug.Log("End");
}

2. Возврат значений и Forget()

В отличие от корутин, UniTask может легко возвращать значения:

public async UniTask<int> CalculateScoreAsync()
{
    await UniTask.Delay(500);
    return 100;
}

Если вы вызываете асинхронный метод из синхронного кода (например, из клика по кнопке) и вам не нужно дожидаться результата, метод следует пометить как UniTaskVoid или вызвать у него .Forget(). Это гарантирует, что в случае ошибки (Exception), она будет выведена в консоль Unity.

public void OnButtonClick()
{
    // Правильный вызов (Fire and Forget)
    PlayAnimationAsync().Forget();
}

3. Отмена (Cancellation) — КРИТИЧЕСКИ ВАЖНО

В Unity объекты часто уничтожаются (например, при смене сцены). Если ваша таска в этот момент "спит" в await UniTask.Delay, она проснется и попытается обратиться к уничтоженному объекту, вызвав MissingReferenceException.

Всегда прокидывайте CancellationToken во все асинхронные методы!

private async UniTaskVoid MoveObjectAsync(CancellationToken token)
{
    while (true)
    {
        // Если токен отменен, Delay выкинет OperationCanceledException и цикл прервется
        await UniTask.Delay(100, cancellationToken: token);
        transform.position += Vector3.forward;
    }
}

private void Start()
{
    // Получаем токен, который АВТОМАТИЧЕСКИ отменится при OnDestroy() этого MonoBehaviour
    MoveObjectAsync(this.GetCancellationTokenOnDestroy()).Forget();
}

4. Продвинутые конструкции (Когда нужна мощь)

Параллельное выполнение (WhenAll)

Если нужно загрузить три ресурса одновременно и подождать завершения всех:

public async UniTask LoadLevelAsync()
{
    var task1 = LoadTextureAsync();
    var task2 = LoadAudioAsync();
    var task3 = LoadDataAsync();

    // Ждем, пока завершатся все три параллельные таски
    await UniTask.WhenAll(task1, task2, task3);
    Debug.Log("Всё загружено!");
}

Гонка тасок (WhenAny)

Если нужно дождаться ПЕРВОГО события (например, игрок нажал кнопку ИЛИ истек таймер):

public async UniTask WaitForInteractionAsync(CancellationToken ct)
{
    var clickTask = WaitUserClickAsync(ct);
    var timeoutTask = UniTask.Delay(TimeSpan.FromSeconds(5), cancellationToken: ct);

    int finishedIndex = await UniTask.WhenAny(clickTask, timeoutTask);

    if (finishedIndex == 0) Debug.Log("Игрок нажал кнопку!");
    else Debug.Log("Игрок ничего не сделал, время вышло.");
}

Таймауты

Любой метод можно ограничить по времени:

try
{
    // Если метод не завершится за 2 секунды, будет брошено исключение TimeoutException
    await DownloadDataAsync().Timeout(TimeSpan.FromSeconds(2));
}
catch (TimeoutException)
{
    Debug.LogError("Слишком долго!");
}

5. Многопоточность (Переключение потоков)

Unity API (например, transform.position, Instantiate) можно использовать только в Главном Потоке (Main Thread). Но если вам нужно сделать тяжелые вычисления (генерация уровня, парсинг JSON 20MB), вы можете временно "прыгнуть" в фоновый поток (Thread Pool), а затем вернуться обратно!

public async UniTask ProcessHeavyDataAsync()
{
    // Мы в главном потоке (Main Thread)
    var json = Resources.Load<TextAsset>("heavy_data").text;

    // ПРЫГАЕМ В ФОНОВЫЙ ПОТОК (Thread Pool)
    await UniTask.SwitchToThreadPool();

    // Тут нельзя трогать Unity API, но можно грузить процессор на 100%
    var parsedData = Newtonsoft.Json.JsonConvert.DeserializeObject<Data>(json);
    HeavyCalculations(parsedData);

    // ВОЗВРАЩАЕМСЯ В ГЛАВНЫЙ ПОТОК
    await UniTask.SwitchToMainThread();

    // Снова можно трогать Unity API!
    transform.position = parsedData.startPosition;
}

Important

Переключение потоков с помощью UniTask — это мощнейший инструмент оптимизации, который позволяет избавиться от фризов (лагов) при подгрузке тяжелых данных, оставляя код линейным и легко читаемым.