LoginSignup
50
32

More than 3 years have passed since last update.

【Unity】依存性注入とは?と、なっているのでZenject(Extenject)を入門してみた

Posted at

最初に

どうも、ろっさむです。

今回は「依存性注入」とは何なのかを理解するために、Zenjectを入門してみる手順をまとめたものとなります。

開発環境

  • Unity:2020.2
  • Extenject:9.2.0

依存性注入とは何か

英語だと「Dependency Injection」、通称「DI」と呼ばれているものです。依存性注入と言われていますが、「Dependency」を「必要なもの」とか「ないと困るもの」とかのイメージで考えると良さそうです。

参考:"Dependency"を使って、パンチの効いた制約関係の表現をする

DI自体は、「IT用語辞典バイナリ」にて以下のような説明がされています。

DIとは、プログラミングにおけるデザインパターン(設計思想)の一種で、オブジェクトを成立させるために必要となるコードを実行時に注入(Inject)してゆくという概念のことである。

参考:IT用語辞典バイナリ > DI

つまりクラスA内で必要となるクラスBの機能を、必要となったタイミングで引っ張ってくる感じです。そうすることで、クラス間での密な結合を防ぐことができます。よくわからんなーって人は後ほど記載する、使用例とサンプルコードを確認してみてください。

この引っ張ってくる方法を提供してくれるのが、今回紹介する「Extenject」となります。

Extenjectとは

DIを実現するためのフレームワークです。

こちらはUnityのアセットストアからインポートを行うことができます。

Extenject Dependency Injection IOC
image.png

こちらは元々「Zenject」というフレームワークをメインで管理していた方が、会社の退社後に個人でメンテを始めたフレームワークのようです。現在は「Extenject」の方が最新のUnityへの対応などが行われているため、これから使用する場合は「Extenject」をインポートすることをお勧めします。

参考:Zenjectをメンテナンスするために生まれたExtenjectというリポジトリ

import 方法

先ほどのアセットストアのリンクからマイアセットに追加する > Unityで開く > Package ManagerにてExtenject Dependecy Injection IOC を検索するか、窓を開く > DL > Importまでを行ってください。

そうすると Assets > Plugins > Zenjectというフォルダが作成されます。早速試していきましょう。

使用例とテストコード

例えば、MyCharacterクラスがあるとします。

今回は適当にHierarcy > 3D Object > Sphereを作成して、MyCharacterという名前をつけておきます。

image.png
image.png

今回はこのキャラクターの移動制御を行うための処理をExtenjectを使用して構築していきます。

ここでなぜExtenjectを使用するのかというと、input処理をUnityEngine.Input以外からも受け付けられるようにするためです。
例えば、現在主流なのはUnityEngine.Inputを用いた移動処理の実装だと思いますが、この他にもUnity2020から使用可能となったInput SystemRewiredという入力管理系アセットに差し替えたくなる時が来るかもしれません。その場合、Input処理を変更するのに、UnityEngine.Inputだけに依存した処理にしていると、多くのリファクタが発生してしまいます。
今回のMyCharacterクラスで考えてみましょう。

移動処理用のコンポーネントとしてInputForMoveを用意しておきます。
image.png

このUpdate()内部では、UnityEngine.Inputを直接参照しています。この状態から、例えば先ほどあげたInput SystemでのInput処理を使いたい場合は、このUpdate()内部を書き換える必要が出てきます。
image.png

interfaceを使用しても良いのですが、結局どこかでオブジェクトをnew()する必要があり、プロジェクトが大規模になるほど、その場所はコードの奥深くに位置する可能性が高くなります。

ここで、new()を一つの場所に集約しつつも、疎結合となるような作り方を行うためにExtenjectを使用します。

まずはinterfaceと各実装を用意

Inputクラスが共通で持つ処理をInterface側に記述をし、各Inputクラスで実装を行うようにしてみましょう。

Interface作成

雑にIInputtableという名前にしておきます。

using UnityEngine;

public interface IInputtable
{
    Vector3 InputForMove();
}

このVector3値は移動するために使用する、Input情報を用いて作成したものとなります。

Inputクラス作成

まずはKeyboard入力用を作成してみます。

using UnityEngine;

public class InputFromKeyboard : IInputtable
{
    public Vector3 InputForMove()
    {
        return new Vector3(Input.GetAxis("Horizontal"), 
            0, Input.GetAxis("Vertical"));
    }
}

オブジェクトにアタッチ用のスクリプト作成

実際に動かすオブジェクトに対してアタッチするスクリプトを作成し、内部でInput入力を受け取って位置を更新する処理を記述します。

using UnityEngine;
using Zenject;

public class InputForMove : MonoBehaviour
{
    [Inject]
    private IInputtable _inputObject;

    void Update()
    {
        if(_inputObject != null)
        {
            Move(_inputObject.InputForMove());
        }
    }

    void Move(Vector3 vec)
    {
        var position = transform.localPosition;
        transform.localPosition = position + vec;
    }
}

ここでの_inputObject[Inject]を付けることによって、中身が「注入」されます。

さて、問題はこの_inputObjectの中身に、どうやってInputFromKeyboardを入れるかですが、ここを解決するにはInstaller用のクラスを作成する必要があります。

Installerの作成

Installerクラスは、Zenjectの注入を行うにあたって、必要となる依存関係の定義を行うクラスになります。

ProjectタブなどからZenject > Mono Installerを選択し、作成してみましょう。
image.png

今回はIInputtableのインターフェース型の変数に注入する場合、InputFromKeyboardクラスを使用したいので、以下のように記述してみます。

using Zenject;

public class SampleInstaller : MonoInstaller
{
    public override void InstallBindings()
    {
        Container
            .Bind<IInputtable>()
            .To<InputFromKeyboard>()
            .AsSingle();
    }
}

一つずつ要素を分解して確認してみましょう。

Container

注入するに当たって、

  • 要求される可能性のあるinterface
  • そのinterfaceに対応したクラス(このクラスのインスタンスを注入)
  • インスタンスの生成方法や、扱い方

を設定します。その設定先がこのContainerとなります。

Bind

「要求される可能性のあるinterface」をここで記述しています。
今回の場合だと、IInputtable型を必要とする変数、プロパティなどを記述しているクラス全てが対象となることを明示しています。ただし、このInstaller自体の影響範囲内に限ります(後述)。

To

「interfaceに対応したクラス(このクラスのインスタンスを注入)」をここで記述しています。
IInputtable型を必要とするクラスの変数などに、Toで示したInputFromKeyboard型のインスタンスを注入します。

AsSingle

「インスタンスの生成方法や、扱い方」をここで記述しています。
AsSingleだけではなく、他のパターンもあるので見ていきましょう。

項目 概要
AsSingle このContainer全体で、Toの際に定めたクラスのインスタンスを1つだけ持ち、そのインスタンスを再利用し続ける。シングルトン状態。基本的にこれがよく使われる。が、動的Bindには向いていない。
AsCached 既にインスタンス登録が行われている場合は、そのインスタンスを使用する。UnBindするとインスタンスの参照は破棄されるため、動的にBindを行いたい場合はこちらを使用する。
AsTransient インスタンスを再利用せず、Toの際に定めたクラス型のインスタンスが要求される度に、新しいインスタンスを使用する。

なので、今回は「IInputtable型のインスタンスが必要になった際には、InputFromKeyboard型のインスタンスを一つだけ作成し、Containerに登録を行って、一つのインスタンスを使い回す」といった状態になります。

次に、このInstallerで記述した内容が、どこまで影響範囲を持つのかを設定する必要があります。

Contextを作成する

Zenject > Scene Contextから作成が可能です。
image.png

今回のContextではScene Contextを指定しているため、このContextが配置されているシーンが影響範囲として設定されます。

Contextの種類に関して更に詳しく知りたい方は以下の記事がおすすめです。

参考:【Unity】【Zenject】DIの影響範囲を指定するContextの使い方まとめ

作成されたScene Contextに対して、先ほど作成したSample Installerをアタッチしておき、それをScene ContextMono Installersに追加します。

image.png

これでSample Installerが走るようになりました。

実際に動作テストを行っても正常に動くかと思われます。

もしも開発途中で別のInputクラスが使用したくなっても、Installerクラス内部でのInputFromKeyboardを置き換えれば良いだけとなります。非常に便利ですね。

Extenjectにはまだまだ様々な機能があるようなので、今後もじっくり学習していきます。

また、以下の記事も今後学習を進めていくのに、有用かと思われますのでリストとして載せておきます。

参考

50
32
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
50
32