Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

こんなことありませんか?

「ボタンを押したら立方体が出る」という簡単なシーンを作ってみましょう。

「ボタンを押したら立方体が出る」最初の実装

ナイーブに作ると、こんな感じになりますよね。
Basic Scene with arrow.png

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

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

Cubeを別のGameObjectに入れ替えたら?

オブジェクト参照がMissingになって動かなくなります。
修正するには、手作業でオブジェクトを登録しなおさないといけません。

Missing Object Reference.png

Cubeが増えたら…

ひとつずつ登録しないといけないですよね。

Multiple Unity Events.png

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

すごくめんどくさい!!

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

まずはObserverパターンにする

この問題を解決するには、まず「ボタンからCubeへの参照」を「Cubeからボタンへの参照」に変えましょう。
Observer Pattern.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

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
  2. インポート
    Zenject Import.png

使ってみる

  1. [Hierarchy右クリック > Zenject > Scene Context]でScene Contextを作成(シーン内に必ずひとつだけ必要)
    Zenject Scene Context.png
  2. ButtonにZenject Bindingコンポーネントを追加して、ButtonコンポーネントをComponents配列に追加
    Zenject Button.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でみる疎結合の威力

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした