土鍋で雑多煮

UnityでXR・ゲーム開発をしています。学んだことや備忘録、趣味の記録などを書いていきます。

Extenject(旧Zenject)を触ってみた ~Inputの切り替えを例に~

はじめに

どうも、土鍋です。
この記事はIwaken Lab.の開発合宿ブログリレー11日目の記事です。
9月に行われた開発合宿で初めて触ったExtenjectについて書きました!

Extenjectとは?

DI(依存性の注入)フレームワークで、もともとZenjectという名前でしたが、開発者の方が会社とごたごたがあって名前が変わったそうです。 役割としては、「疎結合やテストのことを考えて設計したときに発生してしまう問題を解決してくれる」といった感じです。

Extenjectを使うには、疎結合な設計になっていないとその効力を発揮しない。

疎結合な設計

そこでまず、疎結合な設計について見ていきます。 例えば、複数デバイスの入力に対応し、切り替えを行う場合、どうしてもコードがごちゃっとしたり、デバッグが大変だったりします。

→ そこでInterface等を使って疎結合にしていく!!

例としてVRとキーボードで入力切り替えることを考えて設計していきましょう。

まずこれが密結合の状態です。

これでは操作方法が増えるたびにPlayerMoveを書き換える必要が出てきてしまいます。これでは手間ですし、チーム開発においては様々な問題に繋がりかねません。

これをinterfaceを使うことで、疎結合にしていきます。

これによりPlayerMoveは書き換えることなく、様々な操作方法に対応できます。

具体的なスクリプト

この例ではスカイダイビングゲームを想定して書きました。

PlayerMove

Playerの動き用スクリプト

public class PlayerMove : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 2;
    [SerializeField] private float fallSpeedMagnification = 3;
    
    private IInputProvider _inputProvider;

    private Rigidbody _rigidbody;
    private void Start()
    {
        _rigidbody = GetComponent<Rigidbody>();
    }

    private void Update()
    {
        _rigidbody.AddRelativeForce(_inputProvider.GetMove() * moveSpeed);
        _rigidbody.drag = _inputProvider.FallSpeed() * fallSpeedMagnification;
    }
}

うまーいこと疎結合にしているとInputが増えてもここを書き換える必要はない。

IInputProvider

今回作ったInput用のインターフェイス。使用する動きを定義。

public interface IInputProvider
{
    Vector2 GetMove();
    float FallSpeed();
}

VRInputProvider

IInputProviderを実装しています。

public class VRInputProvider : IInputProvider
{    
    public Vector2 GetMove()
    {
        処理
    }

    public float FallSpeed()
    {
        処理
    }
}

KeyboardInputProviderVRInputProvider同様に実装する。

Extenjectの利用

ただ、疎結合にするとクラスの利用者的には不便な場面が多いです。

例えば 「利用するインスタンスの決定をどうやって行うのか?」 「どのタイミングで呼び出すのか?」 といった問題が発生します。

そこでExtenjectで依存性を注入する!!

導入方法

assetstore.unity.com

従来のアセット通り、AssetStoreからインストールできます。

今回の実装

主な構成はこんな感じ。

1. Installerを書いていく

DI Container1に対して「IInputProviderが要求されたら、VRInputProviderを注入する」ということを設定するVRInputInstallerを書く。

VRInputInstaller

public class VRInputInstaller : Installer<VRInputInstaller>
{
    public override void InstallBindings()
    {
        Container
            .Bind<IInputProvider>()
            .To<VRInputProvider>()
            .AsCached();
    }
}

KeyboardInputInstaller

同様にしてKeyboardInputInstallerを記述。

public class KeyboardInputInstaller : Installer<KeyboardInputInstaller>
{
    public override void InstallBindings()
    {
        Container
            .Bind<IInputProvider>()
            .To<KeyboardInputProvider>()
            .AsCached();
    }
}

InputManager

さらにVRかキーボードかを判定するInputManagerを書く。

public class InputManager : MonoInstaller
{

~~中略~~
    
    public override void InstallBindings()
    {
        if (XRGeneralSettings.Instance && XRGeneralSettings.Instance.Manager.activeLoader != null)
        {
            Debug.Log("VR Mode");
            VRInputInstaller.Install(Container);
        }
        else
        {
            Debug.Log("Keyboard Mode");
            KeyboardInputInstaller.Install(Container);
        }
    }
}

2. Contextを設定する

配置されたシーンにのみInstallerの影響範囲を設定するScene Contextをシーンに配置する。

Scene ContextにInputManagerを登録します。

3. DI Containerがオブジェクトを注入する先を設定

変数に[Inject]属性をつけることでContainerがオブジェクトを注入してくれます。

いやー簡単ですね...

今回であれば、PlayerMoveIInputProviderの宣言の部分に[Inject]属性を追記。

[Inject]
private IInputProvider _inputProvider;

これで実行時にIInputProviderVR or キーボードのInputProviderが注入されます。

まとめ

今までまともに設計したことがなかったのですが(設計しても結局うまくいかなかった)、Extenjectを学ぶにあたりインターフェイス周りの勉強にもなりました。

UniRx/UniTask等と組み合わせることで、さらにチーム開発においてかなり効力を発揮しそうですねー。今度はそこら辺取り組んで記事にしようかなと思います。

参考

以下の記事を参考にさせていただきました!ありがとうございました!

Zenject入門その1 疎結合とDI Container - Qiita

Unity開発で使える設計の話+Zenjectの紹介

【Unity(C#)】Extenject(Zenject)使って入力機能を切り離したものを管理してみた - Qiita


  1. DI Containerは簡単に言えば「環境にあらかじめ依存関係を設定しておくと、それに応じて自動的にインスタンスを設定してまわってくれる便利な存在」です。Zenject入門その1 疎結合とDI Container - Qiitaより引用