Подробный список изменений: - Перенесены эффекты частиц из 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-индекс проекта.
6.4 KiB
Полное руководство по 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— это мощнейший инструмент оптимизации, который позволяет избавиться от фризов (лагов) при подгрузке тяжелых данных, оставляя код линейным и легко читаемым.