R3のSubjectを使ったイベント処理の実装方法

Subjectとは

ReactivePropertyは最新の値をずっと持ち続けるのに対して、Subjectは出来事が起きた瞬間だけを通知するためのものです。
例えばボタンを押したとかダメージを受けたときのように、状態ではなくイベントそのものを伝えたいときに使えます。

他のイベント処理との違い

UnityEventやC#のイベントでも同じことができますが、購読の解除の問題でメモリリークの原因になります。SubjectはCompositeDisposableによる自動的な購読解除が可能なので、安全なイベント処理もできます。

Subjectの使い方

Subject<型> でイベント通知を宣言します。型の部分を Unit とすることで、値を渡さない通知のみを行う処理にすることができます。
イベントを実行するには .OnNext(値) でイベントを発行できます。

前回載せていませんでしたが、オブジェクトが破棄された場合にも購読を続けてしまう可能性があるので、OnDestroy() で購読を解除するように Dispose を呼ぶことでメモリリークを避けることができます。

Subjectを使用した例

以下はMVPの設計(Model, View, Presenter)を意識したカウント通知の処理をSubjectを用いて実装した例です。
カウントの加算と減算のボタンを押すとカウントの表示が変化します。

Model

イベント発行とカウント用の値を管理します。

using UnityEngine;
using R3;

public class SimpleCounterModel : MonoBehaviour
{
    //カウンターの値
    public readonly ReactiveProperty Count = new(0);
    //カウンターが増加したときのイベント通知
    public readonly Subject OnIncreased = new();
    //カウンターが減少したときのイベント通知
    public readonly Subject OnDecreased = new();
    //カウンターがリセットされたときのイベント通知
    public readonly Subject OnReset = new();

    //CompositeDisposableを使用して破棄を管理
    private readonly CompositeDisposable disposables = new();

    private void Awake()
    {
        // 管理対象に登録
        disposables.Add(Count);
        disposables.Add(OnIncreased);
        disposables.Add(OnDecreased);
        disposables.Add(OnReset);
    }

    private void OnDestroy()
    {
        // まとめて破棄
        disposables.Dispose();
    }

    //カウンターを増やす
    public void Increase()
    {
        Count.Value++;
        //イベント通知
        OnIncreased.OnNext(Unit.Default);
    }

    //カウンターを減らす
    public void Decrease()
    {
        Count.Value--;
        //イベント通知
        OnDecreased.OnNext(Unit.Default);
    }

    //カウンターをリセット
    public void Reset()
    {
        Count.Value = 0;
        //イベント通知
        OnReset.OnNext(Unit.Default);
    }
}

Presenter

ModelとViewを繋げる処理になります。

using UnityEngine;
using R3;
using UnityEngine.UI;

public class SimpleCounterPresenter : MonoBehaviour
{
    [Header("Model and View")]
    [SerializeField] private SimpleCounterModel model;
    [SerializeField] private SimpleCounterView view;

    [Header("Buttons")]
    [SerializeField] private Button increaseButton;
    [SerializeField] private Button decreaseButton;
    [SerializeField] private Button resetButton;

    private void Start()
    {
        SetupButtons();
        SetupModelToViewBinding();
    }

    //ボタンのイベントを設定
    private void SetupButtons()
    {
        increaseButton.onClick.AddListener(() => model.Increase());
        decreaseButton.onClick.AddListener(() => model.Decrease());
        resetButton.onClick.AddListener(() => model.Reset());
    }

    //モデルとビューのバインディングを設定
    private void SetupModelToViewBinding()
    {
        //カウントを購読してビューに反映
        model.Count
        .Subscribe(view.UpdateCountDisplay)
        .AddTo(this);

        //カウントが増加したときのイベントを購読
        model.OnIncreased
        .Subscribe(_ => view.ShowIncreaseEffect())
        .AddTo(this);

        //カウントが減少したときのイベントを購読
        model.OnDecreased
        .Subscribe(_ => view.ShowDecreaseEffect())
        .AddTo(this);

        //カウントがリセットされたときのイベントを購読
        model.OnReset
        .Subscribe(_ => view.ShowResetEffect())
        .AddTo(this);
    }
}

View

UIの表示やエフェクトを担当します。

using UnityEngine;
using UnityEngine.UI;
using TMPro;
using DG.Tweening;

public class SimpleCounterView : MonoBehaviour
{
    [SerializeField] private TextMeshProUGUI countText;

    //カウントを更新
    public void UpdateCountDisplay(int count)
    {
        countText.text = count.ToString();
    }

    //カウントが増加したときのエフェクトを表示
    public void ShowIncreaseEffect()
    {
        //緑色のエフェクトを表示
        countText.color = Color.green;
        countText.DOColor(Color.white, 0.5f);
    }

    //カウントが減少したときのエフェクトを表示
    public void ShowDecreaseEffect()
    {
        //赤色のエフェクトを表示
        countText.color = Color.red;
        countText.DOColor(Color.white, 0.5f);
    }

    //カウントがリセットされたときのエフェクトを表示
    public void ShowResetEffect()
    {
        countText.color = Color.white;
    }
}

まとめ

イベント処理はゲーム制作をする上で多用します。Unity標準のイベント処理よりも、Rxベースの処理の方が読みやすさや処理の流れの把握がしやすいので、少しずつ移行してみてもいいですね。

まだまだR3には便利な機能がありそうですが、個人開発の範囲であれば今回の内容で十分にカバーできると思います。一旦R3の回はここまでとし、また便利な機能を見つけたらブログに残していこうと思います。

最新情報をチェックしよう!
>
CTR IMG