Привет, разработчик! Сегодня разберем две важные вещи в Unity - корутины и UniTask. Представь, что ты готовишь обед. Корутины - это как готовить по старинке, а UniTask - как современная мультиварка. Давай разберемся, что лучше.
Что такое корутины?
Корутины - это способ Unity выполнять задачи по частям, не останавливая всю игру. Представь, что ты готовишь обед. Вместо того чтобы стоять у плиты и ждать, пока сварится суп, ты можешь поставить его вариться, пойти нарезать овощи для салата, потом вернуться и проверить суп, снова отойти и накрыть на стол. Так и корутины - они позволяют игре делать несколько дел одновременно.
Важно: Корутины работают только с MonoBehaviour (компонентами Unity).
using System.Collections;
using UnityEngine;
public class SimpleCoroutine : MonoBehaviour
{
void Start()
{
StartCoroutine(CountNumbers());
}
IEnumerator CountNumbers()
{
for (int i = 1; i <= 5; i++)
{
Debug.Log($"Число: {i}");
yield return new WaitForSeconds(1f);
}
Debug.Log("Счет закончен!");
}
}
Что здесь происходит:
Мы создали функцию
CountNumbers()
, которая возвращаетIEnumerator
Внутри цикла мы выводим число и ждем секунду
yield return new WaitForSeconds(1f)
говорит Unity: "Подожди секунду, потом продолжай"Корутина выполняется по частям, не блокируя игру
Что такое UniTask?
UniTask - это современная библиотека для асинхронного программирования. Она работает быстрее корутин и дает больше возможностей. Представь, что корутины - это старый телефон, а UniTask - это смартфон.
Важно: UniTask нужно установить отдельно через Package Manager Unity. Это не встроенная функция, как корутины.
Примечание: В примерах мы используем async UniTaskVoid
вместо async void
для большей безопасности. async void
может привести к необработанным исключениям.
using Cysharp.Threading.Tasks;
using UnityEngine;
public class SimpleUniTask : MonoBehaviour
{
async UniTaskVoid Start()
{
await CountNumbersAsync();
}
async UniTask CountNumbersAsync()
{
for (int i = 1; i <= 5; i++)
{
Debug.Log($"Число: {i}");
await UniTask.Delay(1000);
}
Debug.Log("Счет закончен!");
}
}
Что здесь происходит:
Мы используем
async
иawait
- современный способ писать асинхронный кодUniTask.Delay(1000)
ждет 1000 миллисекунд (1 секунда)Код выглядит как обычный, но выполняется асинхронно
UniTask работает быстрее корутин для сложных задач
Сравнение производительности
Корутины создают объекты в памяти, но Unity их кэширует (запоминает) для повторного использования. UniTask работает еще эффективнее и не создает лишних объектов. Представь разницу между покупкой новой коробки для каждой вещи и использованием одной коробки много раз.
// Корутина
yield return new WaitForSeconds(1f);
// UniTask
await UniTask.Delay(1000);
Обработка ошибок
В корутинах сложно обрабатывать ошибки. UniTask делает это просто. Представь разницу между старым телефоном, где нельзя перезвонить, и смартфоном с историей звонков.
Проблема с корутинами
IEnumerator LoadDataCoroutine()
{
// Если здесь произойдет ошибка, корутина просто остановится
// и никто об этом не узнает
yield return new WaitForSeconds(1f);
// Этот код может не выполниться из-за ошибки выше
Debug.Log("Данные загружены");
}
// Вызываем корутину
void Start()
{
StartCoroutine(LoadDataCoroutine());
// Мы не знаем, успешно ли выполнилась корутина
}
Что здесь происходит:
Если в корутине произойдет ошибка, она просто остановится
Код, который вызвал корутину, не узнает об ошибке
Нет способа узнать, что пошло не так
Решение с UniTask
using Cysharp.Threading.Tasks;
using UnityEngine;
async UniTask LoadDataAsync()
{
try
{
await UniTask.Delay(1000);
Debug.Log("Данные загружены");
}
catch (System.Exception e)
{
Debug.LogError($"Ошибка загрузки: {e.Message}");
// Можно попробовать загрузить данные снова
await RetryLoadData();
}
}
async UniTask RetryLoadData()
{
Debug.Log("Пробуем загрузить данные снова...");
await UniTask.Delay(2000);
Debug.Log("Данные загружены со второй попытки");
}
// Вызываем UniTask
async UniTaskVoid Start()
{
try
{
await LoadDataAsync();
Debug.Log("Все прошло успешно!");
}
catch (System.Exception e)
{
Debug.LogError($"Критическая ошибка: {e.Message}");
}
}
Что здесь происходит:
Если произойдет ошибка, мы сразу об этом узнаем
Можем обработать ошибку и попробовать снова
Код, который вызвал функцию, тоже узнает об ошибке
Есть полный контроль над тем, что делать при ошибке
Отмена операций
UniTask позволяет легко отменить операцию. Корутины этого не умеют.
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
public class CancellationExample : MonoBehaviour
{
private CancellationTokenSource _cancellationTokenSource;
async UniTaskVoid Start()
{
_cancellationTokenSource = new CancellationTokenSource();
await LongOperation(_cancellationTokenSource.Token);
}
async UniTask LongOperation(CancellationToken cancellationToken)
{
for (int i = 0; i < 100; i++)
{
// Проверяем, не отменили ли операцию
cancellationToken.ThrowIfCancellationRequested();
Debug.Log($"Шаг {i}");
await UniTask.Delay(100, cancellationToken: cancellationToken);
}
}
void OnDestroy()
{
// Отменяем операцию при уничтожении объекта
_cancellationTokenSource?.Cancel();
}
}
Что здесь происходит:
CancellationTokenSource
- это как пульт управления для отмены операцийcancellationToken
- это сигнал, который говорит "отмени операцию"cancellationToken.ThrowIfCancellationRequested()
- проверяет, не нажали ли кнопку отменыЕсли операцию отменили, код выбрасывает исключение и останавливается
await UniTask.Delay(100, cancellationToken: cancellationToken)
- ждет 100 миллисекунд, но может отмениться раньше_cancellationTokenSource?.Cancel()
- нажимает кнопку отмены при уничтожении объекта
Когда использовать корутины?
Корутины подходят для простых задач:
Анимации
Простые задержки
Когда не нужна отмена операции
Когда ты делаешь простую игру
Когда не хочешь устанавливать дополнительные библиотеки
Плюсы корутин:
Уже есть в Unity
Простые в использовании
Подходят для начинающих
Когда использовать UniTask?
UniTask лучше для сложных задач:
Загрузка данных из интернета
Работа с файлами
Когда нужна отмена операции
Когда важна производительность
Работа с ECS (Entity Component System)
Большие проекты
Плюсы UniTask:
Быстрее работает
Лучше обрабатывает ошибки
Можно отменять операции
Работает везде, не только с MonoBehaviour
ECS и асинхронность
ECS (Entity Component System) - это другой способ создавать игры в Unity. Вместо MonoBehaviour здесь используются чистые C# классы. Это как разница между готовкой по рецепту (MonoBehaviour) и готовкой по интуиции (ECS).
Проблема с корутинами в ECS
using System.Collections;
using UnityEngine;
// Корутины работают только с MonoBehaviour
public class PlayerSystem : MonoBehaviour
{
private float playerHealth = 50f;
private float maxHealth = 100f;
private float healAmount = 10f;
IEnumerator HealPlayerCoroutine()
{
while (playerHealth < maxHealth)
{
playerHealth += healAmount;
yield return new WaitForSeconds(1f);
}
}
}
// В ECS нет MonoBehaviour, поэтому корутины не работают!
public class PlayerSystem : SystemBase
{
// Здесь нельзя использовать корутины
// IEnumerator HealPlayerCoroutine() - НЕ РАБОТАЕТ!
}
Что здесь происходит:
Корутины требуют MonoBehaviour для работы
В ECS используются чистые C# классы без MonoBehaviour
Корутины просто не запустятся в ECS системах
Решение с UniTask в ECS
using Unity.Entities;
using Unity.Collections;
using Cysharp.Threading.Tasks;
using UnityEngine;
public class PlayerSystem : SystemBase
{
private bool _isHealing = false; // Флаг, чтобы не запускать лечение много раз
protected override void OnUpdate()
{
// Запускаем лечение только один раз
if (!_isHealing)
{
_isHealing = true;
_ = HealPlayerAsync(); // _ = означает "запустить и забыть"
}
}
private async UniTask HealPlayerAsync()
{
var playerQuery = GetEntityQuery(typeof(PlayerComponent));
var playerEntities = playerQuery.ToEntityArray(Allocator.Temp);
foreach (var entity in playerEntities)
{
var playerComponent = EntityManager.GetComponentData<PlayerComponent>(entity);
while (playerComponent.health < playerComponent.maxHealth)
{
playerComponent.health += playerComponent.healAmount;
EntityManager.SetComponentData(entity, playerComponent);
await UniTask.Delay(1000); // Ждем 1 секунду
}
}
playerEntities?.Dispose();
_isHealing = false; // Сбрасываем флаг
}
}
// Компонент для игрока
public struct PlayerComponent : IComponentData
{
public float health;
public float maxHealth;
public float healAmount;
}
Что здесь происходит:
UniTask работает в любом C# классе, включая ECS системы
Мы можем использовать
await
в ECS системахАсинхронные операции работают с компонентами данных
Нет зависимости от MonoBehaviour
_ =
означает "запустить задачу и не ждать её завершения"Allocator.Temp
- это способ выделить память для временных данных
Преимущества UniTask в ECS
using Unity.Entities;
using Cysharp.Threading.Tasks;
using UnityEngine;
public class GameManager : SystemBase
{
private bool _isInitialized = false;
protected override void OnUpdate()
{
// Запускаем инициализацию только один раз
if (!_isInitialized)
{
_isInitialized = true;
_ = InitializeGameAsync();
}
}
private async UniTask InitializeGameAsync()
{
// Запускаем несколько асинхронных операций одновременно
var loadLevelTask = LoadLevelAsync();
var updateUITask = UpdateUIAsync();
var saveGameTask = SaveGameAsync();
// Ждем завершения всех задач
await UniTask.WhenAll(loadLevelTask, updateUITask, saveGameTask);
Debug.Log("Все задачи завершены!");
}
private async UniTask LoadLevelAsync()
{
// Загружаем уровень
await UniTask.Delay(2000);
Debug.Log("Уровень загружен");
}
private async UniTask UpdateUIAsync()
{
// Обновляем интерфейс
await UniTask.Delay(500);
Debug.Log("UI обновлен");
}
private async UniTask SaveGameAsync()
{
// Сохраняем игру
await UniTask.Delay(1000);
Debug.Log("Игра сохранена");
}
}
Что здесь происходит:
В ECS можно запускать несколько асинхронных операций одновременно
Каждая операция работает независимо
Нет блокировки основного потока
Код остается чистым и понятным
UniTask.WhenAll()
ждет завершения всех задач
Установка UniTask
Чтобы использовать UniTask, нужно его установить:
Открой Window → Package Manager в Unity
Нажми "+" → "Add package from git URL"
Введи:
https://github.com/Cysharp/UniTask.git
Нажми "Add"
Или через OpenUPM:
Открой Window → Package Manager
Нажми "+" → "Add package from git URL"
Введи:
com.cysharp.unitask
Нажми "Add"
Или скачай с GitHub и добавь в проект вручную.
Заключение
Корутины - это старый, но надежный способ. UniTask - современный и быстрый.
Выбирай так:
Корутины - для простых игр, анимаций, когда ты только учишься
UniTask - для сложных проектов, когда нужна производительность и контроль
Помни: UniTask нужно установить отдельно, корутины уже есть в Unity.
Комментарии (2)
Lekret
16.07.2025 12:11Корутины можно остановить через StopCoroutine, имхо это даже проще токенов.
На пример с "ECS" больно смотреть, написали бы лучше пример с заведомо async вещами, тот же http-запрос, потому что игровую логику тасками в ECS не пишут, но окей.
Пример с ECS не раскрывает минуса корутин, свой глобальный CoroutineManager для сцены пишется за 5 минут. Я бы наоборот выделял привязку к объекту как возможный плюс, потому что таскаться с токенами, либо ловить нулрефы в повисших тасках не прикольно.
NeriaLab
У корутин есть еще одна болячка: она не может запустится в том
MonoBehaviour, чей GameObject не активен и сразу падает с ошибкой, так что приходится проводить доп. проверку на
activeInHierarchy