Для чего использовал UniRx на прошлой работе?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Использование 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%. Однако важно отметить, что реактивный подход требовал обучения команды и иногда приводил к излишней сложности в простых сценариях, поэтому мы использовали его там, где это действительно давало преимущества.