← Назад к вопросам

Для чего использовал UniRx на прошлой работе?

1.0 Junior🔥 112 комментариев
#Асинхронность и многопоточность#Паттерны проектирования

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Использование UniRx в Unity-проектах

На предыдущей работе UniRx (Reactive Extensions для Unity) был одним из ключевых инструментов в архитектуре клиентской части проекта. Мы использовали его для решения широкого спектра задач, где традиционные подходы с событиями (UnityEvent) или коллбэками приводили к сложному и запутанному коду. Основное преимущество, ради которого мы внедрили UniRx — это реактивное программирование, позволяющее работать с асинхронными операциями и потоками данных как с коллекциями.

Основные сценарии применения

1. Обработка пользовательского ввода и UI

Мы заменили стандартные UnityEvent в UI на реактивные потоки, что сделало код более декларативным и удобным для композиции. Например, обработка ввода с кнопки с дополнительной логикой (например, предотвращение двойных нажатий) выглядела так:

// Классический подход с UnityEvent мог создать спагетти-код
// С UniRx это стало чище
button.OnClickAsObservable()
    .ThrottleFirst(TimeSpan.FromSeconds(0.5)) // Игнорируем повторные нажатия в течение 0.5 секунд
    .Subscribe(_ => ExecuteAction())
    .AddTo(disposables);

2. Управление состоянием приложения и данных

Мы использовали ReactiveProperty<T> для создания реактивных моделей данных. Когда данные менялись, все зависимые представления (UI) автоматически обновлялись без необходимости ручного отслеживания изменений:

public class PlayerModel {
    public ReactiveProperty<int> Health { get; } = new ReactiveProperty<int>(100);
    public ReactiveProperty<bool> IsAlive { get; } = new ReactiveProperty<bool>(true);
    
    public PlayerModel() {
        // Автоматическое обновление IsAlive при изменении Health
        Health.Select(h => h > 0)
              .SubscribeToReactiveProperty(IsAlive);
    }
}

3. Асинхронные операции и таймеры

UniRx предоставляет отличные инструменты для работы с асинхронностью. Мы активно использовали Observable.Timer, Observable.Interval и операторы для задержек:

// Плавное обновление здоровья с анимацией
Health.Pairwise()
      .SelectMany(pair => Observable
          .Timer(TimeSpan.Zero, TimeSpan.FromSeconds(0.01))
          .Select(t => Mathf.Lerp(pair.Previous, pair.Current, (float)t))
          .TakeUntil(Health.Skip(1)))
      .Subscribe(value => healthBar.fillAmount = value / 100f);

4. Интеграция с корутинами

Мы использовали Observable.FromCoroutine для обертки корутин в наблюдаемые последовательности, что позволяло комбинировать их с другими потоками данных:

public IObservable<Texture2D> LoadTextureAsync(string url) {
    return Observable.FromCoroutine<Texture2D>((observer, cancellationToken) => 
        LoadTextureCoroutine(url, observer, cancellationToken));
}

5. Оптимизация производительности

Операторы Throttle, Sample, DistinctUntilChanged помогали снижать частоту обработки событий, например, при частых обновлениях данных:

// Обновляем UI не чаще чем раз в 0.1 секунду, даже если данные меняются чаще
playerPosition
    .Sample(TimeSpan.FromSeconds(0.1))
    .Subscribe(pos => UpdateMinimap(pos));

Почему именно UniRx, а не альтернативы?

  • Единая парадигма для всех асинхронных операций: UI, сетевые запросы, таймеры, ресурсы
  • Отличная интеграция с Unity (встроенная поддержка MonoBehaviour, UnityEvent)
  • Богатая библиотека операторов для трансформации, фильтрации и комбинирования потоков
  • Автоматическое управление жизненным циклом подписок через AddTo(), что уменьшало ошибки с утечками памяти

Проблемы, которые решил UniRx

  • Сложность управления асинхронным кодом — цепочки коллбэков превращались в линейные цепочки операторов
  • Рассыпанная бизнес-логика — реактивные цепочки собирали логику в одном месте
  • Утечки памяти из-за неправильной отписки от событий — CompositeDisposable давал централизованное управление

В результате внедрения UniRx код стал более предсказуемым, уменьшилось количество багов, связанных с состоянием и асинхронностью, а разработка сложных взаимодействий между системами ускорилась примерно на 20-30%. Однако важно отметить, что реактивный подход требовал обучения команды и иногда приводил к излишней сложности в простых сценариях, поэтому мы использовали его там, где это действительно давало преимущества.