MRが楽しい

MRやVRについて学習したことを書き残す

UniRxでObserveEveryValueChangedを使って特定の値の変化をフレームごとにチェックする

本日は UniRx の技術調査枠です。
UniRxでObserveEveryValueChangedを使って特定の値の変化をフレームごとにチェックする方法を記事にします。
f:id:bluebirdofoz:20210414123355j:plain

ObserveEveryValueChanged

UniRxで定義されている全クラスの拡張メソッドです。
この関数を利用することで、様々なクラスのフレーム間での値変化の監視が行えるようになります。

サンプルコード

以下のようなサンプルコードを作成しました。
2つの異なる参照変数を Subscribe することで、以下の2種類の値変化の通知を受け取ることができます

  • ReactiveProperty によるベクトル情報全体の変化通知
  • ObserveEveryValueChanged によるベクトルのX情報の変化通知

・EveryValueVectorReactiveProperty.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

using System;

using UniRx;

namespace HMProject.Test
{
    /// <summary>
    /// テスト用ベクトル情報
    /// </summary>
    [Serializable]
    public class EveryValueVector
    {
        public int X;
        public int Y;
        public int Z;

        public EveryValueVector(int x, int y, int z)
        {
            X = x;
            Y = y;
            Z = z;
        }
    }

    /// <summary>
    /// ベクトル情報を表すReactiveProperty
    /// </summary>
    [Serializable]
    public class EveryValueVectorReactiveProperty : ReactiveProperty<EveryValueVector>
    {
        public EveryValueVectorReactiveProperty()
        {
        }
        public EveryValueVectorReactiveProperty(EveryValueVector a_EveryValueVector) : base(a_EveryValueVector)
        {
        }
    }

    public class TestEveryValueChanged : MonoBehaviour
    {
        /// <summary>
        /// ベクトル情報
        /// </summary>
        [SerializeField, Tooltip("ベクトル情報")]
        private EveryValueVectorReactiveProperty p_EveryValueVector = new EveryValueVectorReactiveProperty();

        /// <summary>
        /// ベクトル情報のReadOnlyReactivePropertyの保持変数
        /// </summary>
        private IReadOnlyReactiveProperty<EveryValueVector> p_IReadOnlyReactivePropertyEveryValueVector;

        /// <summary>
        /// ベクトル情報のReadOnlyReactivePropertyの参照変数
        /// </summary>
        public IReadOnlyReactiveProperty<EveryValueVector> IReadOnlyReactivePropertyEveryValueVector
            => p_IReadOnlyReactivePropertyEveryValueVector
            ?? (p_IReadOnlyReactivePropertyEveryValueVector = p_EveryValueVector.ToSequentialReadOnlyReactiveProperty());


        // EveryValueChanged を使ってベクトル情報の特定の値に変化があった際に通知する
        // EveryValueChanged を使うとフレーム間での変化のみを検出する
        /// <summary>
        /// ベクトル情報の EveryValueChanged オブザーバ保持変数
        /// </summary>
        private IObservable<int> p_IObservableEveryValueVectorEveryValueChanged;

        /// <summary>
        /// ベクトル情報の EveryValueChanged オブザーバ参照変数
        /// </summary>
        public IObservable<int> IObservableEveryValueVectorEveryValueChanged
            => p_IObservableEveryValueVectorEveryValueChanged
            ?? (p_IObservableEveryValueVectorEveryValueChanged =
            p_EveryValueVector.ObserveEveryValueChanged(property => property.Value.X)); // X の値の変化を検知する


        /// <summary>
        /// ベクトル情報の設定
        /// </summary>
        private void ReceptionVector(int x, int y, int z)
        {
            p_EveryValueVector.SetValueAndForceNotify(new EveryValueVector(x, y, z));
        }

        /// <summary>
        /// 起動処理
        /// </summary>
        void Start()
        {
            // 初期値を設定する
            VectorReset();
        }

        /// <summary>
        /// 定期処理
        /// </summary>
        void Update()
        {
        }

        /// <summary>
        /// 初期値を設定する
        /// </summary>
        public void VectorReset()
        {
            ReceptionVector(0, 0, 0);
        }

        /// <summary>
        /// Xの値変更テスト
        /// 同一フレーム内で値を2回変更する
        /// </summary>
        public void XChangeTest()
        {
            // 値を変更する
            ReceptionVector(1, 0, 0);

            // 値を変更する
            ReceptionVector(2, 0, 0);
        }

        /// <summary>
        /// Yの値変更テスト
        /// 同一フレーム内で値を2回変更する
        /// </summary>
        public void YChangeTest()
        {
            // 値を変更する
            ReceptionVector(0, 1, 0);

            // 値を変更する
            ReceptionVector(0, 2, 0);
        }
    }

#if UNITY_EDITOR
    // エディター定義
    // 拡張するクラスを指定する
    [CustomEditor(typeof(TestEveryValueChanged))]
    // 継承クラスは Editor を設定する
    public class TestEveryValueChangedEditor : Editor
    {
        // GUIの表示関数をオーバーライドする
        public override void OnInspectorGUI()
        {
            // 元のインスペクター部分を表示
            base.OnInspectorGUI();

            // targetを変換して対象スクリプトの参照を取得する
            TestEveryValueChanged editorTarget = target as TestEveryValueChanged;

            // public関数を実行するボタンの作成
            if (GUILayout.Button("VectorResetの実行"))
            {
                editorTarget.VectorReset();
            }

            // public関数を実行するボタンの作成
            if (GUILayout.Button("XChangeTestの実行"))
            {
                editorTarget.XChangeTest();
            }

            // public関数を実行するボタンの作成
            if (GUILayout.Button("YChangeTestの実行"))
            {
                editorTarget.YChangeTest();
            }
        }
    }
# endif
}

f:id:bluebirdofoz:20210414123530j:plain

合わせて参照変数に Subscribe を行い、結果を表示する以下のスクリプトを作成しました。
・TestEveryValueChangedSubscribe.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using UniRx;

namespace HMProject.Test
{
    public class TestEveryValueChangedSubscribe : MonoBehaviour
    {
        [SerializeField, Tooltip("スクリプト参照")]
        private TestEveryValueChanged p_TestEveryValueChanged;

        /// <summary>
        /// 起動処理
        /// </summary>
        void Start()
        {
            p_TestEveryValueChanged.IReadOnlyReactivePropertyEveryValueVector
                .ObserveOnMainThread()
                .Subscribe(vector =>
                {
                    Debug.Log("ForceNotify - X : " + vector.X.ToString() +
                        ", Y : " + vector.Y.ToString() +
                        ", Z : " + vector.Z.ToString());
                })
                .AddTo(this);

            p_TestEveryValueChanged.IObservableEveryValueVectorEveryValueChanged
                .ObserveOnMainThread()
                .Subscribe(value =>
                {
                    Debug.Log("EveryValueChanged : " + value.ToString());
                })
                .AddTo(this);
        }

        /// <summary>
        /// 定期処理
        /// </summary>
        void Update()
        {

        }
    }
}

f:id:bluebirdofoz:20210414123547j:plain

作成したスクリプトを以下の通り、設定して準備は完了です。
f:id:bluebirdofoz:20210414123559j:plain

動作確認

シーンを再生して動作を確認します。
f:id:bluebirdofoz:20210414123618j:plain

Y の値を変化させる関数を実行すると、ReactiveProperty の通知のみが発生します。
f:id:bluebirdofoz:20210414123630j:plain

X の値を変化させる関数を実行すると、ReactiveProperty の通知と ObserveEveryValueChanged による通知が発生します。
また、ReactiveProperty の通知は2回の変更ごとに通知が発生していますが、ObserveEveryValueChanged の通知はフレーム間の1回の変化のみを通知していることがわかります。
f:id:bluebirdofoz:20210414123712j:plain

参考ページ

qiita.com