Подробный список изменений: - Перенесены эффекты частиц из 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-индекс проекта.
148 lines
6.4 KiB
Markdown
148 lines
6.4 KiB
Markdown
# Полное руководство по 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)`.
|
||
|
||
```csharp
|
||
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 может легко возвращать значения:
|
||
|
||
```csharp
|
||
public async UniTask<int> CalculateScoreAsync()
|
||
{
|
||
await UniTask.Delay(500);
|
||
return 100;
|
||
}
|
||
```
|
||
|
||
Если вы вызываете асинхронный метод из синхронного кода (например, из клика по кнопке) и вам не нужно дожидаться результата, метод следует пометить как `UniTaskVoid` или вызвать у него `.Forget()`. Это гарантирует, что в случае ошибки (Exception), она будет выведена в консоль Unity.
|
||
|
||
```csharp
|
||
public void OnButtonClick()
|
||
{
|
||
// Правильный вызов (Fire and Forget)
|
||
PlayAnimationAsync().Forget();
|
||
}
|
||
```
|
||
|
||
## 3. Отмена (Cancellation) — КРИТИЧЕСКИ ВАЖНО
|
||
|
||
В Unity объекты часто уничтожаются (например, при смене сцены). Если ваша таска в этот момент "спит" в `await UniTask.Delay`, она проснется и попытается обратиться к уничтоженному объекту, вызвав `MissingReferenceException`.
|
||
|
||
Всегда прокидывайте `CancellationToken` во все асинхронные методы!
|
||
|
||
```csharp
|
||
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)
|
||
Если нужно загрузить три ресурса одновременно и подождать завершения всех:
|
||
|
||
```csharp
|
||
public async UniTask LoadLevelAsync()
|
||
{
|
||
var task1 = LoadTextureAsync();
|
||
var task2 = LoadAudioAsync();
|
||
var task3 = LoadDataAsync();
|
||
|
||
// Ждем, пока завершатся все три параллельные таски
|
||
await UniTask.WhenAll(task1, task2, task3);
|
||
Debug.Log("Всё загружено!");
|
||
}
|
||
```
|
||
|
||
### Гонка тасок (WhenAny)
|
||
Если нужно дождаться ПЕРВОГО события (например, игрок нажал кнопку ИЛИ истек таймер):
|
||
|
||
```csharp
|
||
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("Игрок ничего не сделал, время вышло.");
|
||
}
|
||
```
|
||
|
||
### Таймауты
|
||
Любой метод можно ограничить по времени:
|
||
|
||
```csharp
|
||
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), а затем вернуться обратно!
|
||
|
||
```csharp
|
||
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` — это мощнейший инструмент оптимизации, который позволяет избавиться от фризов (лагов) при подгрузке тяжелых данных, оставляя код линейным и легко читаемым.
|