Привет, разработчик! Сегодня разберем две важные вещи в 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.
ссылка на оригинал статьи https://habr.com/ru/articles/927308/
Добавить комментарий