Pull to refresh

Совсем чуть-чуть про UniRx для Unity

Level of difficultyEasy
Reading time3 min
Views6.7K

Примерно год назад я начал работать с этой библиотекой, а теперь настолько к ней привык, что не представляю как без нее можно обходиться. Так как многие знакомые разработчики на Unity совершенно не верят моим восторженным отзывам, здесь я попробую на пальцах показать, как немного улучшить жизнь при работе с ежедневными задачами.

Введение

На Хабре есть два исчерпывающих туториала на тему реактивов в юнити: первая и вторая. Я же попробую ответить на вопрос «зачем, если можно все сделать событиями?»

События

Предположим, у игрока меняется здоровье и мана. А мы их все время показываем в UI.
Очевидный способ — подписка на события изменения этих самых здоровья и маны

public void Init(Player player)
{
  _player = player;
  _player.HealthChanged += SetHealth;
  _player.ManaChanged += SetMana;
}

public void Dispose()
{
  _player.HealthChanged -= SetHealth;
  _player.ManaChanged -= SetMana;
}

А сам Player будет выглядеть как-то так

public class Player : IPlayer
{
  private int _health;
  private int _mana;

  public int Health => _health;
  public int Mana => _mana;

  public event Action<int> HealthChanged;
  public event Action<int> ManaChanged;
}

А если мы захотим добавить для игрока интерфейс, то придем к такому варианту

public interface IPlayer
{
  public int Health { get; }
  public int Mana { get; }

  public event Action<int> HealthChanged;
  public event Action<int> ManaChanged;
}

В чем проблема? В том, что при росте данных для показа надо будет каждый раз спускаться до метода Dispose и не забывать отписываться, а также создавать на каждое значение свой метод. Ведь я даже не могу создать n одинаковых вьюшек для показа значений

ReactiveProperty

Чем же поможет в таком случае реактивное поле? Оно позволяет совершать подписку и отписку в одном и том же месте, хотя диспоузом все равно придется воспользоваться.

private List<IDisposable> _disposables = new List<IDisposable>();

public void Init(Player player)
{
  player.Health
    //создаем подписку
    .Subscribe(v => { _healthView.text = v.ToString(); })
    //добавляем подпику в список очищаемых обьектов
    .AddTo(_disposables);
  player.Mana.Subscribe(v => { _manaView.text = v.ToString(); }).AddTo(_disposables);
}
        
public void Dispose()
{
  foreach (var disposable in _disposables)
  {
    disposable.Dispose();
  }
  _disposables.Clear();
}

В чем прелесть? В том, что метод Subscribe, которым мы подписываемся на поток, возвращает IDisposable, а значит, мы можем сразу добавлять это в список, который потом будет очищаться. При этом можно не опасаться подписок c помощью лямбд.

Теперь можно глянуть на Player

public class Player : IPlayer
{
  private ReactiveProperty<int> _health = new();
  private ReactiveProperty<int> _mana = new();
        
  public IReadOnlyReactiveProperty<int> Health => _health;
  public IReadOnlyReactiveProperty<int> Mana => _mana;
}

Так как ReactiveProperty<T> реализует интерфейс IReadOnlyReactiveProperty<T>, мы можем оставлять его торчащим наружу, не опасаясь непрошенных изменений.

ReactiveCollection/ReactiveDictionary

То же самое что и реактивные поля, но здесь надо отдельно подписываться на каждое событие, связанное с коллекцией.

public void Init(IReadOnlyReactiveCollection<Player> players)
{
  players
    //говорим, что хотим отслеживать
    .ObserveAdd()
    //подписываемся
    .Subscribe(v => 
    {
      //работаем с добавленным элементом
      Debug.Log(v.Value);
    }).AddTo(_disposables);
  players.ObserveRemove().Subscribe(v => { Debug.Log(v.Value); }).AddTo(_disposables);
  players.ObserveReplace().Subscribe(v =>
  {
    Debug.Log(v.OldValue);
    Debug.Log(v.NewValue);
  }).AddTo(_disposables);
}

А как же «все есть поток»?

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

Tags:
Hubs:
Total votes 6: ↑3 and ↓30
Comments16

Articles