5
5

More than 1 year has passed since last update.

[Unity] Zenject入門

Posted at

UniRx,UniTask,DOTweenなどUnityには便利ライブラリが多く存在します.その中の1つであるZenjectについての初歩のまとめ.

Zenjectとは.

GitHubの公式ページによると

 Zenject is a lightweight highly performant dependency injection framework

Zenjectは軽量で高性能な Dependency Injection のフレームワークとのことです.

Dependency Injection

説明で出てきた Dependency Injection (DI) は「依存性の注入」と訳されます.具体的に言うと,未定義な箇所(依存する箇所)に適する物を外部から定義する(注入)します.
言葉では分かりにくいですね.簡単なコードに書き起こしてみます.

interface IInputHandler{
    public Vector3 GetInputDirection();
}
// Player.cs
public class Player{
    IInputHandler inputHander;
    void Update(){
        Vector3 dir = inputHandler.GetInputDirection();
        Move(dir);
    }
    void Move(Vector3 dir){
        // 省略
    }
}

Playerというクラスでは,何か入力を受け取るIInputHandlerというクラス(interface)をフィールドとして持っています.このフィールドから入力を受け取りPlayerはMove()することができます.ここで重要なのが,IInputHandlerが依存される場所(未定義)になっていると言うことです.どこからも代入がされていません.

フィールドへの代入

inputHandlerフィールドでの代入は,Awake()Start()関数で代入する.または[SerirlaizeField]にしてインスペクター上で設定するなどがあります.

// 代入例
    void Start(){
        inputHandler = new HogeInputHandler();
    }

しかし,これはPlayer内部でどのクラスを使うかを定義してしまい,テストのために別の入力方法を取りたい時に変更が大変になります.

注入(Injection)

そこで Dependency Injection の考え方は,Playerクラス内部でinputHandlerに代入するクラスを決めるのではなく, 外部から代入(注入)しようと言うものになります.

この外部から代入の操作をUnity上でいい感じにしてくれるのが, 「Zenject」になります.

Zenjectのセットアップ

1.ZenjectをAssetStoreからMyAssetに追加する.
今はExtenject Dependency Injection IOCとしてAssetStoreに公開されているので,ダウンロードしてきます.

2.新規プロジェクトを作成し, Window => PackageManager のMy Assetから Extenject をインポートする.
スクリーンショット 2022-04-06 11.36.47.png

これでZenject(Etenject)が使えるようになったため,実際に使用してみましょう.


今回作成するもの

PlayerのIInputHandlerに対して,

  • WASDで動かす : InputFromWASD
  • 矢印キーで動かす : InputFromArrow
  • 自動で動かす : InputTest

3つのクラスを外部から注入してみます.設計は図にすると分かりやすい.
UML.jpg

Playerに対する入力方法を変えていこうと思います.


Zenjectを試しに使ってみる

さて本題として実際に使ってみましょう.初めに上のクラス図で定義したファイルを全て作ります(Sampleプロジェクトなのでフォルダ構成は考えていません).
スクリーンショット 2022-04-06 12.22.04.png

IInputHandlerのInterfaceを定義して3つのクラスで実装します.
InputTestでは120回関数がよばれたら逆方向を返の入力に変わるようにしました. その時々のテストを書くといいでしょう.

// IInputHandler.cs
using UnityEngine;
namespace ZenjectSample
{
    public interface IInputHandler
    {
        Vector3 GetInputDirection();
    }
}
InputFromArrow.cs
using UnityEngine;

namespace ZenjectSample
{
    public class InputFromArrow : IInputHandler
    {
        public Vector3 GetInputDirection()
        {
            if (Input.GetKey(KeyCode.UpArrow))
                return Vector3.up;
            if (Input.GetKey(KeyCode.LeftArrow))
                return Vector3.left;
            if (Input.GetKey(KeyCode.DownArrow))
                return Vector3.down;
            if (Input.GetKey(KeyCode.RightArrow))
                return Vector3.right;
            return Vector3.zero;
        }
    }
}
InputFromWASD.cs
using UnityEngine;

namespace ZenjectSample
{
    public class InputFromWASD : IInputHandler
    {
        public Vector3 GetInputDirection()
        {
            if (Input.GetKey(KeyCode.W))
                return Vector3.up;
            if (Input.GetKey(KeyCode.A))
                return Vector3.left;
            if (Input.GetKey(KeyCode.S))
                return Vector3.down;
            if (Input.GetKey(KeyCode.D))
                return Vector3.right;
            return Vector3.zero;
        }
    }
}
InputTest.cs
using UnityEngine;

namespace ZenjectSample
{
    public class InputTest : IInputHandler
    {
        Vector3 dir = Vector3.right;
        int calledCount = 0;
        readonly int changeDirCount = 120;

        public Vector3 GetInputDirection()
        {
            calledCount++;
            if (calledCount > changeDirCount)
            {
                dir *= -1;
                calledCount = 0;
            }
            return dir;
        }
    }
}

最後にPlayerのクラスです.

// Player.cs
using UnityEngine;

namespace ZenjectSample
{
    public class Player : MonoBehaviour
    {
        [Zenject.Inject] IInputHandler inputHandler;

        void Update()
        {
            if (inputHandler == null) return;
            var direction = inputHandler.GetInputDirection();
            transform.position += direction * Time.deltaTime;
        }
    }
}

inputHandlerフィールドに対してアノテーションがついています.

    [Zenject.Inject] IInputHandler inputHandler;

このように書くこともできます.

// usingを最初に書いておく.
using Zenject;
    [Inject] IInputHandler inputHandler;

[Zenect.Inject] アノテーションがこのフィールドは依存性箇所で,別の場所から注入(代入)します.と表しています.Playerクラス内では,inputHandler = ** のように代入する式が1つも出てきていません.

どのように注入(代入)するか

どのInputHandlerを使うか定義するファイルを生成します.
ProjectView => + => Zenject => MonoInstallerを選択します.

スクリーンショット 2022-04-06 12.57.03.png

InputInstallerという名前で保存しました.
スクリーンショット 2022-04-06 13.01.42.png
保存するとMonoInstallerを継承したInputInstallerのクラスが出来上がります.出来上がった際にすでにInstallBindings()関数があるので中身を書いていきます.始めはWASDで入力出来るようにしてみます.

using Zenject;

namespace ZenjectSample
{
    public class InputInstaller : MonoInstaller
    {
        public override void InstallBindings()
        {
            Container
                .Bind<IInputHandler>()
                .To<InputFromWASD>()
                .AsSingle();
        }
    }
}

ContainerにBind<>()でInterfaceを指定し,To<>()でどのクラスを注入するか指定します.
AsSingleの部分にはインスタンスをどのように扱うかを記載します.他にもパターンがあり,こちらで紹介されていました.

Contextの作成

Installerで,どこに何をバインドするかを決めたら最後はContextで,適用範囲を決めます.
プログラムで言うスコープのようなもので,開発プロジェクト全体に及ぼすのか,シーン内だけに及ぼすのか設定できます.
Hierarchy -> +(or 右クリック) => Zenject => Scene Contextを押すとSceneContextのスクリプトを持つGameObjectが出来上がります.
スクリーンショット 2022-04-06 14.21.25.png
次にInstallerをContextに設定します.今作ったSceneContextのGameObjectに,InputInstaller.csを取り付けます.インスペクタ上で,SceneContextコンポーネントのMonoInstallerに,InputInstallerをアタッチします.

スクリーンショット 2022-04-06 14.28.42.png

このようにすることで,このSceneContextを持ったゲームオブジェクトが存在するシーン内では, InputInstallerがBindしたものが適用されます.クラス部分だけ再掲します. シーン内のIinputHandlerインターフェース部分にInputFromWASDクラスが適用され, 入力がWASDでの入力となります.

    public class InputInstaller : MonoInstaller
    {
        public override void InstallBindings()
        {
            Container
                .Bind<IInputHandler>()
                .To<InputFromWASD>()
                .AsSingle();
        }
    }

今回はInnputInstallerに一つのインターフェースしか記載しませんでしたが,複数Containerに定義して,デバッグ時には一括して使用するクラスを変更するなど幅広い使い方が出来るようになります.今UnityでDIを使おうとしたらZenject一択になりそうなので是非つかってみましょう.


参考

5
5
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
5
5