Unityに関連する記事です

 UniRx の提供する ReactiveProperty は大変強力で便利な機能ですが、注意すべき点もいくつかあります。

 特に非同期処理になれないうちは、エラーの原因として ReactiveProperty が作用している部分が見えにくく、エラー解決ができにくいという部分もあります。
複数の人が携わるプロジェクトとなると、なおさら煩雑になりがちです。
またオブザーバーパターンの理解が深まらないと、どのクラスで購読されていて、どのタイミングで動いているのかなど、通常の処理に比べてわかりくい挙動も多いです。

 処理の内容をしっかりと理解した上で、適切な用法で利用していくことが求められます。



1.null チェックの方法


 ReactiveProperty は、値型または参照型のデータをラップすることができます。
具体的には、ジェネリックな性質を持っており、どんな型でもラップすることができます。
そのため、int や float などの値型だけでなく、string や object、 GameObject、自作クラスなどの参照型もラップすることができます。

 例えば、ReactiveProperty<int>は値型である int 型の値をラップしますが、
ReactiveProperty<string>は参照型である string 型の値をラップします。
そして、それぞれの ReactiveProperty インスタンスは、その型に応じた値を持ちます。



 ReactivePropertyが値型をラップする場合、その値はnullになることはありません。
一方、参照型をラップする場合、その値はnullになる可能性があります。

 C# の値型(int、float、boolなど)を宣言した ReactiveProperty の場合、通常は null チェックは必要ありません。
値型は null になることができないためです。(null 許容型である場合を除きます)

 ですが、参照型のプリミティブ型(string、object など)であったり、GameObject やコンポーネントの型、自作クラスなどを宣言した ReactiveProperty の場合には、null チェックが必要になります。
参照型の変数は null になる可能性があるためです。

 例えば、ReactiveProperty<Rigidbody>のようにコンポーネントの型のクラスを宣言した ReactiveProperty の場合、
Rigidbody が null である可能性があります。ReactivePropertyは参照型のデータをラップしていますが、自体が null であることもあるためです。



 通常の変数と ReactivePropertyの違いは、ReactivePropertyはクラスであり、そのインスタンスが null である可能性がある点です。
その場合、ReactivePropertyのnullチェックは次のように行う必要があります。


public class Test_A {
    public int count;
}

public class Test_B {

    public ReactiveProperty<Test_A> TestProperty = new();

    void Start() {

        if (TestProperty == null || TestProperty .Value == null) {
            return;
        }
    }
}

 このように、TestProperty が null であるかどうかをチェックし、
その後に TestProperty.Value が null であるかどうかをチェックすることで、正しく null チェックを行うことができます。


2.CompositeDisposable を利用した場合の購読停止のタイミング


 UniRx の処理は AddTo メソッドを利用して購読停止を行うケースがあります。
ただし、オブジェクトプールなどを併用している場合には、AddTo で紐づいているオブジェクトが破壊されないため、購読停止が行われません。
そのため、別途、Dispose する必要があります。

 その際、CompositeDisposable を利用して複数の UniRx や ReactiveProperty を監視している場合、
CompositeDisposable は一度 Dispose すると同じストリームでは購読が再開されません

 購読処理を再開させる予定がある場合には、Clear メソッドを利用して購読停止するか、単体で停止できるように IDisposable 型を利用しましょう。



 サンプルコードです。

using UniRx;
using UnityEngine;


/// <summary>
/// ライフ管理用基底クラス
/// </summary>
public class LifeBase {

    public ReactiveProperty<int> Hp = new();
    protected int maxHp;

    protected readonly CompositeDisposable disposables = new();
    public CompositeDisposable Disposables { get => disposables; set => disposables.Add(value); }


    protected void Dispose() {

        // Dispose ではなく Clear で購読を停止する
        disposables.Clear();
    }

    protected void OnDestroy() {
        Dispose();
    }
}



 Hp を購読する場合には、下記のように指定します。


private LifeBase myLife;

// Hp の購読
myLife.Hp
    .Subscribe(hp => Debug.Log($"hp : {hp}")
  .AddTo(myLife.Disposables);

 AddTo の引数に先ほど作成したクラス内の CompositeDisposable のプロパティを指定することで購読停止を実行することができます。


3.Mathf クラスとの併用


 Mathf クラスと ReactiveProperty を一緒に利用する場合、その引数内に ReactiveProperty を利用すると購読処理が複数回動きます。

 例えば Mathf.Clamp メソッドを利用して、ReactiveProperty を制限値内に収めたい場合、Clamp メソッド内で代入処理を挟んでしまうと
ReactiveProperty の計算処理の時点で1回購読され、さらにその値が Clamp の制限値を超えた場合には数値が再度更新されるため、
もう1回購読処理が動きます。

 そのため、予期していない値での購読処理が行われる可能性が高いため、
^貪戞▲蹇璽ル変数を作成し、その値を Mathf クラスで制限してから ReactiveProperty に代入するようにする
△△襪い蓮Clamp 内では ReactiveProperty に代入処理しない(+=で処理しない)ことで安全に運用できます。


using UniRx;
using UnityEngine;

/// <summary>
/// ライフ管理用基底クラス
/// </summary>
public class LifeBase : MonoBehaviour {

    public ReactiveProperty<int> Hp = new();
    protected int maxHp;


    /// <summary>
    /// 初期設定
    /// </summary>
    /// <param name="initHp"></param>
    protected virtual void SetUpLife(int initHp) {
        Hp.Value = initHp;
        maxHp = initHp;
    }

    /// <summary>
    /// Hp 計算処理
    /// 生存判定付
    /// </summary>
    /// <param name="amount"></param>
    /// <returns></returns>
    public virtual bool CalcLife(int amount, int effectIndex, string seType) {

    //  檻院.蹇璽ル変数に ReactiveProperty を代入し、直接計算しないようにする
        int hp = Hp.Value;

        //  檻押hp の計算を Clamp で制限することで、Value への代入を1回だけにする
        Hp.Value = Mathf.Clamp(hp += amount, 0, maxHp);

        // △△襪い蓮◆ のみで加算する(代入処理を挟まない)
        Hp.Value = Mathf.Clamp(Hp.Value + amount, 0, maxHp);

        return Hp.Value > 0;
    }
}



 下記のように処理を書くと、Clamp メソッド内で ReactiveProperty の購読処理が2回が動く可能性があります。
 
    public virtual bool CalcLife(int amount, int effectIndex, string seType) {

    // ReactiveProperty の計算を直接 Clamp 内で行うと、第1引数の計算時に1回、Clamp の制限時に1回、購読処理が動く可能性がある
        Hp.Value = Mathf.Clamp(Hp.Value += amount, 0, maxHp);

        return Hp.Value > 0;
    }

 これを避けるようにコーディングしましょう。


コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Menu



技術/知識(実装例)

2Dおはじきゲーム(発展編)

2D強制横スクロールアクション(発展編)

3Dダイビングアクション(発展編)

2Dタップシューティング(拡張編)

レースゲーム(抜粋)

2D放置ゲーム(発展編)

3Dレールガンシューティング(応用編)

3D脱出ゲーム(抜粋)

2Dリアルタイムストラテジー

2Dトップビューアドベンチャー(宴アセット使用)

3Dタップアクション(NavMeshAgent 使用)

2Dトップビューアクション(カエルの為に〜、ボコスカウォーズ風)

VideoPlayer イベント連動の実装例

VideoPlayer リスト内からムービー再生の実装例(発展)

AR 画像付きオブジェクト生成の実装例

AR リスト内から生成の実装例(発展)

private



このサイト内の作品はユニティちゃんライセンス条項の元に提供されています。

管理人/副管理人のみ編集できます