まだオブジェクト参照で消耗してるの？ Zenjectではじめる疎結合なUnityアプリ開発

# こんなことありませんか？
「ボタンを押したら立方体が出る」という簡単なシーンを作ってみましょう。

## 「ボタンを押したら立方体が出る」最初の実装
ナイーブに作ると、こんな感じになりますよね。
![Basic Scene with arrow.png](https://qiita-image-store.s3.amazonaws.com/0/97681/2b276ca9-270c-b7ee-ffc8-6bc0c01a636b.png)

ボタンのOnClickにCubeのMesh Rendererへの**オブジェクト参照**を作って、enabledをtrueにするように設定してます。
これで、ボタンを押したら立方体が出るようになりましたね！

では、このCubeを別のGameObjectに入れ替えたらどうなるでしょうか？
また、Cubeが増えたらどうなるでしょうか？

## Cubeを別のGameObjectに入れ替えたら？
オブジェクト参照がMissingになって動かなくなります。
修正するには、手作業でオブジェクトを登録しなおさないといけません。

![Missing Object Reference.png](https://qiita-image-store.s3.amazonaws.com/0/97681/57f8801b-afb5-191e-efb3-74d318e992be.png)

## Cubeが増えたら…
ひとつずつ登録しないといけないですよね。

![Multiple Unity Events.png](https://qiita-image-store.s3.amazonaws.com/0/97681/0090a4d0-b92d-925f-54bc-516b307c1e50.png)

こうなる。Cubeが増えるたびに登録しないといけない。

## すごくめんどくさい！！
めんどうだし工数かかるし、修正しようとしたらえらいことになって…わけがわからなくなりますよね。

# まずはObserverパターンにする
この問題を解決するには、まず「ボタンからCubeへの参照」を「Cubeからボタンへの参照」に変えましょう。
![Observer Pattern.png](https://qiita-image-store.s3.amazonaws.com/0/97681/c8c767d1-2978-e163-171a-c25510879cab.png)

```ButtonObserver.cs
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Renderer))]
public class ButtonObserver : MonoBehaviour {
    public Button button;
    Renderer targetRenderer;

    void Start() {
        button.onClick.AddListener(OnClick);
        targetRenderer = GetComponent<Renderer>();
    }

    void OnClick() {
        targetRenderer.enabled = true;
    }
}
```

これで、Cubeが複数に増えてもいちいち登録しなくてよくなりました。
めでたしめでたし…とはいかないですよね。

このままだと、次の課題が残ります。
* CubeをPrefabにしてシーンに複数配置するときに、Buttonへの参照をいちいち設定しないといけない
* CubeをSphereに変えたときは、SphereからButtonへの参照をいちいち設定しないといけない
* Cubeが複数あるときに、Buttonを別のコンポーネントに入れ替えると、各Cubeのオブジェクト参照を入れ替えなおし
* Buttonが複数になったりすると、また設定しなおし

これを全部Zenjectで解決しちゃいましょう！

# ここでZenjectですよ！！
## どんな感じになるの？
なんと、ButtonからCubeへの参照もなければ、CubeからButtonへの参照もなくなります。
あるのは、ButtonのGameObject内での参照のみです。

![Zenject Scene with arrow.png](https://qiita-image-store.s3.amazonaws.com/0/97681/c1f1a4e5-9921-7265-082a-c1257c158985.png)

```ButtonObserverDI.cs
using UnityEngine;
using UnityEngine.UI;
using Zenject;

[RequireComponent(typeof(Renderer))]
public class ButtonObserverDI : MonoBehaviour {
    [Inject]
    Button button;

    Renderer targetRenderer;

    void Start() {
        button.onClick.AddListener(OnClick);
        targetRenderer = GetComponent<Renderer>();
    }

    void OnClick() {
        targetRenderer.enabled = true;
    }
}
```

ButtonObserver.csをButtonObserverDI.csに書き換えましたが、変えたのは`using`と`[Inject]`なるAttributeのみです。
もちろん、上で書いた課題も解決できてます。
* Cubeが複数あっても動く
* Cubeを別のGameObject（Sphereとか）に変えても動く
* CubeをPrefabにしてシーンに複製しても動く
* Buttonを別のコンポーネントに入れ替えても、Button内での参照を入れ替えるだけで動く
* Buttonが複数になっても、コード上での少ない変更で済む（後述）

さっそく使い方を見てみましょう。

## Zenjectのインストール
1. Asset Storeでダウンロード（無料！）  
![AssetStore Zenject.png](https://qiita-image-store.s3.amazonaws.com/0/97681/7bc74e7e-bf6d-c444-c701-fb64f75ce574.png)
2. インポート  
![Zenject Import.png](https://qiita-image-store.s3.amazonaws.com/0/97681/bf4af43d-ff0f-02f0-adcd-5ce556fbba5c.png)

## 使ってみる
1. [Hierarchy右クリック > Zenject > Scene Context]でScene Contextを作成（シーン内に必ずひとつだけ必要）  
![Zenject Scene Context.png](https://qiita-image-store.s3.amazonaws.com/0/97681/8ea5e55f-a229-ddd8-2ba0-46c2b524cc4e.png)
2. ButtonにZenject Bindingコンポーネントを追加して、ButtonコンポーネントをComponents配列に追加  
![Zenject Button.png](https://qiita-image-store.s3.amazonaws.com/0/97681/94367c99-0e27-e566-23c9-c0aa76ccaf6a.png)
3. ButtonObserver.csのbuttonフィールドのAttributeに`[Inject]`を追加してprivateにする（`using Zenject`も追加しておく）

こんだけです。これで、Zenject Bindingに登録されたButtonコンポーネントが、Button型を必要としている`[Inject]`Attributeがついた場所全部に登録されます。Start時には見れるようになっています。

# 補足
## Buttonが複数あるとき
複数Buttonがあって、どのButtonを押してもCubeを見えるようにしたいときは、ButtonObserverDI.csを以下に変えるだけです。

```MultiButtonObserverDI.cs
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Zenject;

[RequireComponent(typeof(Renderer))]
public class MultiButtonObserverDI : MonoBehaviour {
    [Inject]
    List<Button> buttons;

    Renderer targetRenderer;

    void Start() {
        foreach (var button in buttons) {
            button.onClick.AddListener(OnClick);
        }
        targetRenderer = GetComponent<Renderer>();
    }

    void OnClick() {
        targetRenderer.enabled = true;
    }
}
```

## 複数のButtonをCube、Sphere、Capsuleに割り当てたいとき
以下のような感じで、GameObjectの名前をButtonとCubeとかで一致させておいてマッチングすればできます。
もっといい方法あるかもしれないので、ぜひ教えてください。

```SelectButtonObserver.cs
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Zenject;

public class SelectButtonObserverDI : MonoBehaviour {
    Renderer targetRenderer;

    [Inject]
    void Init(List<Button> buttons) {
        foreach (var button in buttons) {
            if (button.gameObject.name == gameObject.name) {
                button.onClick.AddListener(OnClick);
            }
        }
    }

    void Start() {
        targetRenderer = GetComponent<Renderer>();
    }

    void OnClick() {
        targetRenderer.enabled = true;
    }
}
```

# まとめ
ZenjectはDependency Injectionを楽に実装するためのライブラリ、という説明はよく聞くのですが、じゃあどんな効能があるのか、についてはあまり言及されてなかったので書いてみました。
先週あたりから触り始めたばかりなので、みなさん教えていただけると幸いです。
UniRxとかと組み合わせると最強（？）かもしれません

ではでは！

# 次の記事
[また同じコード書いてるの？ Zenject Signalsでみる疎結合の威力](https://qiita.com/TyounanMOTI/items/ce9f37b11d1eb71c1684)