こんなことありませんか?
「ボタンを押したら立方体が出る」という簡単なシーンを作ってみましょう。
「ボタンを押したら立方体が出る」最初の実装
ボタンのOnClickにCubeのMesh Rendererへのオブジェクト参照を作って、enabledをtrueにするように設定してます。
これで、ボタンを押したら立方体が出るようになりましたね!
では、このCubeを別のGameObjectに入れ替えたらどうなるでしょうか?
また、Cubeが増えたらどうなるでしょうか?
Cubeを別のGameObjectに入れ替えたら?
オブジェクト参照がMissingになって動かなくなります。
修正するには、手作業でオブジェクトを登録しなおさないといけません。
Cubeが増えたら…
ひとつずつ登録しないといけないですよね。
こうなる。Cubeが増えるたびに登録しないといけない。
すごくめんどくさい!!
めんどうだし工数かかるし、修正しようとしたらえらいことになって…わけがわからなくなりますよね。
まずはObserverパターンにする
この問題を解決するには、まず「ボタンからCubeへの参照」を「Cubeからボタンへの参照」に変えましょう。
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内での参照のみです。
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のインストール
使ってみる
- [Hierarchy右クリック > Zenject > Scene Context]でScene Contextを作成(シーン内に必ずひとつだけ必要)
- ButtonにZenject Bindingコンポーネントを追加して、ButtonコンポーネントをComponents配列に追加
- ButtonObserver.csのbuttonフィールドのAttributeに
[Inject]
を追加してprivateにする(using Zenject
も追加しておく)
こんだけです。これで、Zenject Bindingに登録されたButtonコンポーネントが、Button型を必要としている[Inject]
Attributeがついた場所全部に登録されます。Start時には見れるようになっています。
補足
Buttonが複数あるとき
複数Buttonがあって、どのButtonを押してもCubeを見えるようにしたいときは、ButtonObserverDI.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とかで一致させておいてマッチングすればできます。
もっといい方法あるかもしれないので、ぜひ教えてください。
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とかと組み合わせると最強(?)かもしれません
ではでは!