Unityに関連する記事です

一定の時間が経過するとイベント準備が整うケース


 前回の続きになります。

 UniTaskとUniRxを使用して Final FantasyのATBゲージのような、一定時間が経過するとイベントの準備が整うシステムを実装する例です。
ここではボタンが押せるようにしています。


<実装動画>
動画ファイルへのリンク


設計と概要


 Model クラスのリファクタリングと、ストリームについての詳細な説明を行います。
またそれに伴った Presenter クラスのリファクタリングを行っています。

 最後に View クラスで利用している
public IObservable<Unit> OnAttackButtonClick => attackButton.OnClickAsObservable() プロパティの説明を行っています。
 

Model クラスのリファクタリング


 ATBModel_Mono は MonoBehaviour への紐付けがあるため、ゲームオブジェクトへの依存関係が発生します。

 UniRx では MonoBehaviour を利用しなくても Update メソッドと同じ機能を持つ処理を実装できるため、
下記のような形で処理をリファクタリングすることでも機能します。

 そこで ATBModel_Mono クラスの代わりに、 MonoBehaviour の継承のない Model クラスとして ATBModel クラスを新しく作成します。


ATBModel


 以前の ATBModel_Mono クラスを修正は行わず、クラス名を変えて新しく作成してください。
作成後、両方のクラスの処理を比較し、どういった部分が異なっているのかを確認して、処理の理解を深めることに役立ててください。


ATBModel.cs

<= クリックすると開きます。



解説


 MonoBehaviour の継承がなくなりました。そのためこのクラスは new してインスタンスを作成して利用する形式になります。
また SetTriggerATB メソッドではなく、コンストラクタメソッドと、その引数を利用してメンバ変数の初期設定を行っています。

 MonoBehaviour と紐付いていた UpdateAsObservable メソッドの代わりに、静的メソッドである Observable.EveryUpdate メソッドを利用することで代用しています。
こちらのメソッドは MonoBehaviour には紐付いていないメソッドであるため、AddTo メソッドにより、ライフサイクルを管理しています。

 なお、using UnityEngine; の宣言は、 Time クラスを利用するために必要です。



 Observable.EveryUpdate() によるストリームは、AddTo メソッドを利用することにより、作成と同時に CompositeDisposableに自動的に追加されます。
このクラスのインスタンスが破棄されるときにDisposeメソッドが呼び出され、それによってCompositeDisposableに含まれる全てのストリームが自動的に解放されます

 このとき、AddTo メソッドの引数には this は書けません
理由は MonoBehaviour を継承していないためです。

 MonoBehaviourはComponentを継承しており、ComponentはObjectを継承しています。
ObjectにはIDisposableが実装されているため、MonoBehaviourやその派生クラスであるスクリプトコンポーネントでもAddTo(this)が利用できます

 一方、一般的なC#クラスはIDisposableを実装していないため、this とは書けません。
AddTo()の戻り値を保持するCompositeDisposableオブジェクトを別途作成して、そこにストリームを追加しています。
そして、そのCompositeDisposableオブジェクトは必要に応じて明示的にDisposeする必要があります。


    private readonly CompositeDisposable disposables = new ();


        // ATBを回復するストリーム
        Observable.EveryUpdate()
            .Where(_ => CurrentATB.Value < MaxATB)
            .Subscribe(_ => CurrentATB.Value += RecoveryRate * Time.deltaTime)
            .AddTo(disposables);  // ストリームが生成された時に自動的に CompositeDisposable に追加


    /// <summary>
    /// オブジェクト(インスタンス)が破棄されるときにCompositeDisposableのDisposeも呼び出す
    /// </summary>
    public void Dispose()
    {
        // Dispose メソッドが呼ばれたときに、CompositeDisposable に含まれる全てのストリームが自動的に解放
        disposables.Dispose();
    }

 クラスが MonoBehaviour を継承していない場合は、OnDestroy メソッドが利用できないため、
代わりに IDisposable を実装し、Dispose メソッドでストリームを手動で解放する必要があります。

 今回宣言で利用している CompositeDisposable は IDisposable インターフェースを実装しています。
Dispose() メソッドを呼び出すことで、保持している全ての IDisposable オブジェクト (ストリーム) を破棄することができます。
そのため、CompositeDisposable を利用することで、複数のストリームをまとめて管理することができ、簡単に一括で Dispose() を呼び出すことができるようになります。

 処理の流れとしては、Observable に AddTo(disposables) を追加することで、ストリームが生成された時に自動的に CompositeDisposable に追加されます。
そして、Dispose メソッドが呼ばれたときに、CompositeDisposable に含まれる全てのストリームが自動的に解放されます。

 そのため、ATBModelクラスの場合は、Dispose メソッドを呼び出すことで、AddTo(disposables) で登録されたストリームを手動で解放することができます。



 このような修正を行うと、Presenter 側の Awake メソッド内の処理も変更が必要です。
SetTriggerATB メソッドを実行していた部分で、代わりに Model をインスタンスしてコンストラクタメソッドに引数を渡すようにしています。


ATBPresenter


 ATBModel をインスタンスし、コンストラクタを利用して ATBModel 内の各変数の初期化を行う処理に作り変えます。


ATBPresenter.cs

<= クリックすると開きます。



動作の確認をする

 
 リファクタリングが終了しましたので、動作を確認します。

 最初に ATBController ゲームオブジェクトにアタッチされている ATBModel_Mono クラスを Remove します。


インスペクター画像





 ATBModel クラスは MonoBehaviour の継承のないクラスであるため、ゲーム実行時に Presenter によってインスタンスが生成されて利用できる状態になります。

 その後ゲームを実行してください。
今までと同じようにゲージが溜まり、ボタンが押せ、再度、ゲージが溜まるというサイクルが動いているかを確認してください。


View クラスの Button を外部クラスで直接購読させない手法


 View クラスには OnAttackButtonClick プロパティを用意しています。
このプロパティを Subscribe することで、外部クラスでもボタンのクリックイベントを検知することができます。

 このような設計にすることで、ボタンのクリックによって実行される内部的な処理を秘匿したまま、
外部クラスにおいては、イベントを検知したことを利用した処理を書くことができます。


ATBView


 文章のみでは難しいため、具体的な処理を提示します。

 まず、この OnAttackButtonClick プロパティを使わない方法を見ていきます。


ATBView.cs

<= クリックすると開きます。


 Button の情報を取得するために GetAttackButton メソッドがゲッターメソッドとして用意されています。


ATBPresenter


 この例では、Start メソッド内で Button に OnClickAsObservable を作成し、Subscribe してクリックイベントを作成しています。
そのため、このクラス以外の外部クラスでは、クリックイベントを感知する手段がありません

 そうなったときに利用する方法が、Button 自体を public にするか、プロパティや GetAttackButton のようなゲッターメソッドを作成しておいて、
外部クラスにおいて、直接 Button にアクセスを行い、クリックイベントを外部クラスで作成するパターンです。

 下記のようなものになります。


ATBPresenter.cs

<= クリックすると開きます。


 どちらのパターンを利用した場合でも、Button は参照されるだけではなく、購読の処理も行うようになっています。
この実装方法ですとクラスの疎結合化ができておらず、購読処理の修正を行うと、View クラスにも影響が出るようになってしまいます。

 こういったケースはなるべく避けた方がよいです。


ATBView 改善案


 この方法を回避する手段として、View クラス内にある OnAttackButtonClick プロパティを利用します
これが今回説明している、クラス内部の処理は秘匿したまま、外部クラスにクリックイベントを通知させるための機能です。

 下記のような実装を行うことで、ATBView 内の Button 自体は秘匿(private のままに)し、OnClickAsObservable の Subscribe 処理はこのクラスで行うことができます。
また、OnClickAsObservable を Subscribe する処理がなくても、クリックイベントを外部クラスに提供できる上、Subscribe した際の内容は外部クラスには影響しません。

 クリックした、というイベントのみを外部から参照して Subscribe できる、という訳です。


ATBView.cs

<= クリックすると開きます。


 OnAttackButtonClick は attackButtonのクリックイベントを感知し、外部クラスでSubscribeされた時にその内容を実行します。

 Startメソッドで行っているSubscribeの内容は、OnAttackButtonClickに対する内部的な実装であり、外部クラスに影響を及ぼすことはありません
Subscribeされるまで内部で保持され、外部からは秘匿されます。


ATBPresenter 改善案


 外部クラスである View クラスの Button のクリックイベントを直接命令せずに、
OnAttackButtonClick プロパティを使うことで、クリックイベントのみの購読を行うことが出来ます。

 言い換えると、Presenter 側は、Button であるかどうかは関与せず(知らない状態)、何らかのイベントに対して購読を行うということになります。
Presenter は OnAttackButtonClick プロパティの参照先までは見ていないので、Button であるかどうかは不問の状態です。
このように処理を置き換えることで、より処理を抽象化して実装することが出来ています。


ATBPresenter.cs

<= クリックすると開きます。




 view.OnAttackButtonClick により、ATBView クラス内で実装された攻撃ボタンのクリックイベントを外部クラスから直接 Subscribe する必要がなくなり
ATBView クラスのプライベートな実装の詳細を隠蔽することができます。また、View クラスにおいて OnClickAsObservable の利用有無にかかわらず、クリックイベントを感知できます。

 これにより ATBView クラスの実装を変更することができ、それによって外部クラスの処理に影響を与えることがなく、コードの保守性が向上します。
また、この方法により、外部クラスが ATBView クラスに依存することが少なくなり、疎結合化が促進されます。

 適切なプロパティを作成して利用することにより、カプセル化と疎結合化に役立ちます。



 以上になります。

 MVP パターンの実装については、Presenter で行う購読処理をどのようにして行うかを考えて設計すると、よりよいコーディングが行えるようになります。

コメントをかく


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

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

Menu



技術/知識(実装例)

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

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

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

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

レースゲーム(抜粋)

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

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

3D脱出ゲーム(抜粋)

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

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

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

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

VideoPlayer イベント連動の実装例

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

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

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

private



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

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