ゲーム制作勉強中!あこがれだったプログラマーに今からなろう!

昔、あこがれていたプログラマー。今からでも勉強してみようと思い立ち、チャレンジ開始! 勉強メモや、悪戦苦闘な日々の記録です。

Unity R3環境下での配列変数の購読について



UniRXを現代に合わせてより洗練した形に再定義・再実装したR3。
ただ、素人向けの情報がまだまだ少なく、ちょっとした事をするのも苦労する有様です。

配列変数の変化を購読したかったのですが、ちょっと、いやだいぶん苦戦したので、
備忘録として残しておきます。

UniRXでは、配列変数もReactiveCollectionでObservableでき、Observer側(購読する側)は
ObserveReplace().Subscribeで通知を受け取る事ができました。

ところが、R3環境ではReactiveCollectionがなくなっているので、他の方法を使う必要があ流みたいです。

色々と実験したのですが、配列変数のままでObservableさせる方法は、どうしても良くわからなかったので
ObservableList型や、ObservableDictionary型で考えるのが良いのかもしれません。

一応、配列変数のままでの購読は、UniRXやR3とは関係のない、C#として標準的な .NET の機能で対応する
こともできました。

.NET の機能で行う場合の、Observable側(購読される側)

using UnityEngine;
using System.Collections.ObjectModel;

public class Test : MonoBehaviour
{
    public ObservableCollection<bool> testCollections; 

    float time = 0;

    private void Start() 
    {
        testCollections = new ObservableCollection<bool>(){true,false,false,false};

    }

    void Update()
    {   
        time += Time.deltaTime;
        if(time > 2)
        {
            time = 0;
            testCollections[3] = !testCollections[3];
            testCollections.Add(true);
            }
    }



.NET の機能で行う場合の、Observer側(購読する側)

using UnityEngine;
using System.Collections.Specialized;

public class Reader : MonoBehaviour
{
    [SerializeField]Test test;

    void Start()
    {
        test.testCollections.CollectionChanged += OnCollectionChanged;//購読する配列に変化があった際の関数を定義
    }

    void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        Debug.Log("CollectionChanged");

        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add://配列変数に追加があった際
                Debug.Log("Add : " + 
                        e.NewStartingIndex + " : " //配列変数の新しいindex
                        + e.NewItems[0]); //追加された値

                break;
            case NotifyCollectionChangedAction.Remove:
                Debug.Log("Remove " + e.OldStartingIndex + " : " + e.OldItems[0]);
                break;

            case NotifyCollectionChangedAction.Replace:
                Debug.Log(e.NewStartingIndex);//配列変数で変化のあったindex
                Debug.Log("Old Value: " + e.OldItems[0]//変化前の値
                        +" → New Value: "  + e.NewItems[0]);//変化後の値
                break;
            case NotifyCollectionChangedAction.Move:
                Debug.Log("Move" + e.OldStartingIndex + " to " + e.NewStartingIndex);
                break;
            case NotifyCollectionChangedAction.Reset:
                Debug.Log("Reset");
                break;
        }
    }
}


これで一応、配列変数の購読は可能でしたが、ネットの記事を色々読んでいると、どうやらこちらは
パフォーマンスが低い場合があり、大量のデータ操作や頻繁な変更が発生すると、パフォーマンスに
悪影響が出ることがあるらしい。

個人でちょっとした使い方ならこちらでも良いのかもしれないけれど、パフォーマンスに影響がある
のはできるだけ避けたいところなので、ObservableCollectionsを使った方法で試してみる事にします。

似ているけれどObservableCollectionではなく、ObservableCollection「s」らしい。
開発者様の意気込みを感じる・・・😁

配列変数のままについては良くわからなかったので、まずは、使い方がまんま参考文献に記載されている辞書型変数で考えてみる。
要は配列変数の変数名[x]=Yの形を、辞書型の変数名(x、Y)・・・xは配列変数のindex、辞書型のKeyとして考える
ということらしい。

それを踏まえた上で、Observable側(購読される側)

using UnityEngine;
using ObservableCollections;

public class Test : MonoBehaviour
{
    public ObservableDictionary<int, bool> testCollections = new ObservableDictionary<int, bool>();

    float time = 0;

    private void Start() 
    {
        testCollections.Add(0,false);
        testCollections.Add(1,false);
        testCollections.Add(2,false);
        testCollections.Add(3,false);

        Debug.Log(testCollections.Count);
    }

    void Update()
    {   
        time += Time.deltaTime;
        if(time > 2)
        {
            time = 0;
            testCollections[3] = !testCollections[3];
            testCollections.Add(testCollections.Count,true);
            Debug.Log(testCollections.Count);
        }
    }


Observer側(購読する側)は、

using UnityEngine;
using R3;
using ObservableCollections;
using System.Collections.Specialized;

public class Reader : MonoBehaviour
{
    [SerializeField]Test test;

    float time = 0;

    void Start()
    {
        test.testCollections.ObserveAdd()//追加があった際の設定
            .Subscribe(x =>
            {
                var (key,value)=x.Value;
                Debug.Log($"Add [{key}]={value}");              
            });

        test.testCollections.ObserveReplace()//変更があった際の設定
            .Subscribe(x =>
            {
                var key = x.NewValue.Key;
                var newValue = x.NewValue.Value;
                var oldValue = x.OldValue.Value;
                Debug.Log($"Replace [{key}]={oldValue} -> {newValue}");
            });
    }
}


次にObservableList型の時、
基本的には似た感じになるからなのか、これに特化した記事がまだ見つけられていないので、
試行錯誤しながら確認してみた。

Observable側(購読される側)

using UnityEngine;
using ObservableCollections;

public class Test : MonoBehaviour
{
    public ObservableDictionary<int, bool> testCollections = new ObservableDictionary<int, bool>();
    public ObservableList<bool> testCollections = new ObservableList<bool>(new bool[] { true, false, false, false });


    float time = 0;

    void Update()
    {   
        time += Time.deltaTime;
        if(time > 2)
        {
            time = 0;
            testCollections[3] = !testCollections[3];
            testCollections.Add(true);
            Debug.Log(testCollections.Count);
        }
    }
}


Observer側(購読する側)は、

using UnityEngine;
using R3;
using ObservableCollections;
using System.Collections.Specialized;

public class Reader : MonoBehaviour
{
    [SerializeField]Test test;

    float time = 0;

    void Start()
    {
        test.testCollections.ObserveAdd()//追加があった際の設定
            .Subscribe(x =>
            {
                Debug.Log(x.Index )//追加されたindex
                Debug.Log(x.Value )//追加された値
            });

        test.testCollections.ObserveReplace()//変更があった際の設定
            .Subscribe(x =>
            {
                Debug.Log(x.Index);//変更されたindex
                Debug.Log(x.OldValue);変更前の値
                Debug.Log(x.NewValue);変更後の値
            });
    }
}

これで一応、やりたい事(配列変数の変化を購読したい。)に近いことはできたと思う。

ただこれが二次元配列とかになると、そう簡単ではなさそうなので、色々と工夫は必要かもしれない。(二次元配列を通知しないといけない事態がどれくらいあるのかは置いといて・・・😅)

とりあえず、今作っているゲームはObservableListでやってみるかなぁ・・・🧐