24
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

MV(R)Pの設計とZenjectは相性がいい!MV(R)PでZenject完全理解!

Last updated at Posted at 2019-04-22

#こんな経験ありませんか?
[SerializeField]~View;
[SerializeField]~Model;
そして起こる無限のNullReferenceException...:innocent:
今回はこれを解決するように組んでいきます
#例えば
おそらくZenject未使用でMVP(UniRx)ありで組もうとするとこんな感じになると思います。

HPModel.cs
using System;
using UniRx;
using UnityEngine;

public class HPModel : MonoBehaviour
{
    [SerializeField]float hp = 100f;
    Subject<float> hPChanged = new Subject<float>();

    public IObservable<float> HPChanged => hPChanged;
    private void Start()
    {
        hPChanged.OnNext(hp);
    }
    public void Damage(float damage)
    {
        hp -= damage;
        hPChanged.OnNext(hp);
    }
}
HPPresenter.cs
using UnityEngine;
using UniRx;
public class HPPresenter : MonoBehaviour
{
    [SerializeField] HPModel model;
    [SerializeField] HPView view;
    void Start()
    {
        model.HPChanged.Subscribe(view.DisplayHP);
    }
}

HPView.cs
using UnityEngine;
using UnityEngine.UI;

public class HPView : MonoBehaviour
{
    [SerializeField] Text hp;
    public void DisplayHP(float HP)
    {
        hp.text = HP.ToString();
    }
}

これらのコードを
image.png

のようにマウスでカチカチして設定して

DamageButton.cs
using UnityEngine;
using UnityEngine.UI;
using UniRx;
public class DamageButton : MonoBehaviour
{
    [SerializeField] HPModel model;
    [SerializeField] float damageAmount;
    void Start()
    {
        GetComponent<Button>().onClick.AsObservable().Subscribe(_ => model.Damage(damageAmount));
    }
}

をButtonにつけて
image.png
またマウスでカチカチして...完成!

とこんな感じに
ezgif-2-0c7436b789be.gif
できます!
#問題点
##設定忘れ==死問題:innocent:
はい...
image.png
こうなるだけで死にます、この例程度ならわかりますが規模がでかくなると死にます、つらい
さらに別の問題もあって
ModelにMonobehaviourが依存する形で実装することで[SerializeField]でModelをいじることが出来るようになっていますが、ほんとは依存させたくない(いろいろ理由はありますが今回は省略)し負荷的にもあまりよろしくないです。あと実行順序で死ぬ場合もあるし
少し前まではここでシングルトンの説明が出てきてたのですが今回は別のソリューションでLet'sGo!
#解決策
ここでZenject
Zenject.png
##1.Zenjectのインストール
以下URLからダウンロードしてUnityに入れます。
https://github.com/modesttree/Zenject/releases
##2.SceneContextの用意
まずSceneContextを用意します
image.png
こんな感じで1つのシーンに1つSceneContextを用意してください
##3.スクリプトの書き換え
Zenjectっぽく書いていきますよー!

HPModel.cs
using System;
using UniRx;
using UnityEngine;

public class HPModel//MonoBehaviourが消えた!(どこからも参照されなくて大丈夫です)
{
    float hp = 100f;
    BehaviorSubject<float> hPChanged;
    public IObservable<float> HPChanged => hPChanged;
    public HPModel()
    {
        hPChanged = new BehaviorSubject<float>(hp);//初期化がコンストラクタでできる!(Monobehaviourがあるとできない)
    }
    public void Damage(float damage)
    {
        hp -= damage;
        hPChanged.OnNext(hp);
    }
    public void Heal(float heal)
    {
        hp += heal;
        hPChanged.OnNext(hp);
    }
}
HPPresenter.cs
using UniRx;
using System;

public class HPPresenter //MonoBehaviourが消えた!(どこからも参照されなくて大丈夫です)
{
     HPModel model; 
     HPView view;//SerializeFieldが消えた!

    public HPPresenter(HPModel model, HPView view)//コンストラクターに移動した!
    {
        this.model = model ?? throw new ArgumentNullException(nameof(model));
        this.view = view ?? throw new ArgumentNullException(nameof(view));
        model.HPChanged.Subscribe(view.DisplayHP);
    }
}
HPView.cs
using UnityEngine;
using UnityEngine.UI;
using Zenject;

public class HPView : MonoBehaviour
{
    [Inject] Text hp; //SerializeFieldがInjectに変わった!
    public void DisplayHP(float HP)
    {
        hp.text = HP.ToString();
    }
}
DamageButton.cs
using UnityEngine.UI;
using UniRx;
using Zenject;

public class DamageButton : MonoBehaviour
{
    [Inject] HPModel model;//どこからでも参照できる!
    [SerializeField] float damageAmount;
    void Start()
    {
        GetComponent<Button>().onClick.AsObservable().Subscribe(_ => model.Damage(damageAmount));
    }
}

##4.新しいスクリプトの追加
これだけだとどこからもスクリプトが参照されないので動かないのでZenjectをしb...依存関係を書いていきます
image.png

ここにあるMonoInstallerを押して任意の名前を付けます(今回はHPInstallerで行きます)

HPInstaller.cs
using UnityEngine;
using Zenject;

public class HPInstaller : MonoInstaller
{
    public override void InstallBindings()
    {
        Container.Bind<HPModel>().FromNew().AsCached();//HPModelをNewをして新しく作ってくださいの命令です
        Container.Bind<HPPresenter>().AsCached().NonLazy();//FromNew()は省略可能、NonLazy()は即時に実行してくださいという意味(おまじないと思っていただければ)
    }
}

##4.SceneContextに追加
このように書いて、さらに
image.png
それを適当な場所にアタッチしておきます
そして先ほどのSceneContextにマウスかちかちで設定
image.png
##5.ZenjectBindingを追加
以下の画像のように設定することでHPViewをZenjectに認識させてやります
image.png
またTextもZenjectに乗せてあげます
image.png
#完成!
ezgif-2-0c7436b789be.gif
同じものが動きます
#つまり何やってるの?
イメージとしてはこんな感じです
image.png
コンテナにInstallerとかがクラス(データ)を収納して
image.png
(依存性を解決して)データを配ってくれる
#で何がいいの?
[SerializeField]が消えてNullReferenceExceptionになりづらく!
依存関係が自動的に解消されるのでデータがないときにnullチェックをする必要性が少なくなるのもポイントですね
#気づいた方はいるだろうか...
実はこのままだとHPの設定が出来ません(ModelがUnityから見えなくなったため)
こういう時の対処法も書いておきます
このようなときはたいてい初期化用のデータの場合が多いのでデータを外から注入してやればいいことになりますのでこういう場合に便利なのがScriptableObjectInstallerになります
MonoInstallerと同様に下の部分から
image.png
で作成します今回はHPDataとしました

HPData.cs
using UnityEngine;
using Zenject;

[CreateAssetMenu(fileName = "HPData", menuName = "Installers/HPData")]
public class HPData : ScriptableObjectInstaller<HPData>
{
    [SerializeField] float hp;

    public float HP { get => hp; }

    public override void InstallBindings()
    {
        Container.Bind<HPData>().FromInstance(this).AsCached();
    }
}

HPModelを書き換え

HPModel.cs
using System;
using UniRx;
using UnityEngine;

public class HPModel
{
    [SerializeField] float hp = 100f;
    BehaviorSubject<float> hPChanged;
    public IObservable<float> HPChanged => hPChanged;
    public HPModel(HPData data)//ここを書き換え!
    {
        hp = data.HP;
        hPChanged = new BehaviorSubject<float>(hp);
    }
    public void Damage(float damage)
    {
        hp -= damage;
        hPChanged.OnNext(hp);
    }
    public void Heal(float heal)
    {
        hp += heal;
        hPChanged.OnNext(hp);
    }
}

次に実体を作ります
image.png
そして設定して
image.png
SceneContextに登録します
image.png
完了!
後はさっきの設定のところを適当な値にすると実行時に反映されます
#最後に...
Zenjectはいいぞ!!

24
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?