追記(2024/09)
有り難いことに、この記事を未だに読んでくれる方がいらっしゃいます
ですが、Zenjectはもうメンテナンスされていません
新規で導入するのはやめましょう
他の代替のDIコンテナを検討してください
VContainer
PResolver
Doinject
注意
2017年のZenjectのREADMEの訳です。参考程度にとどめGitHub上のREADMEを読むことをおすすめします。
ちょっとした説明
ZenjectのREADMEの日本語訳です。勉強用に全部ちゃんと読もうと思い翻訳してみました。
8割ぐらいの英文をGoogle翻訳で翻訳しました。
(殆どの訳にあまり違和感がないので、Google翻訳の精度すごい...)
場合によっては原文を呼んだほうがわかりやすいものもありますので、ご注意を。
時間をかけて他のmdファイルも翻訳していこうと思います。
原文:https://github.com/modesttree/Zenject
Dependency Injection Framework for Unity3D
Introduction
Zenjectは、Unityを対象とするために特別に構築された軽量依存性注入フレームワークです(ただし、Unityの外部でも使用できます)。これは、アプリケーションを、細分化された責任を持って疎結合された部品の集合に変えるのに使用できます。 Zenjectは、さまざまな構成で部品を接着することができ、スケーラブルで非常に柔軟な方法でコードを簡単に作成、再利用、リファクタリング、テストすることができます。
Tested in Unity 3D on the following platforms:
- PC/Mac/Linux
- iOS
- Android
- Webplayer
- WebGL
- Windows Store (including 8.1, Phone 8.1, Universal 8.1 and Universal 10 - both .NET and IL2CPP backend)
IL2CPPがサポートされていますが、いくつかの問題があります - 詳しくはこちらをご覧ください
Features
- Injection
- 通常のC#クラスとMonoBehavioursの両方をサポート
- コンストラクタインジェクション(複数のコンストラクタにタグを付けることができます)
- フィールドインジェクション
- プロパティインジェクション
- メソッド注入
- 条件付きバインディング(タイプ、名前などによる)
- オプションの依存関係
- ファクトリを使用した初期化後のオブジェクトの作成のサポート
- ネストされたコンテナ、別名サブコンテナ
- 1つのシーンから次のシーンに情報を渡すために異なるUnityシーンにまたがって注入
- 1つのシーンが別のシーンからバインディングを継承できるようにするシーンの子育て
- グローバルなプロジェクト全体のバインディングをサポートし、すべてのシーンの依存関係を追加する
- クラス名、名前空間、またはその他の基準に基づく規約に基づくバインディング
- 編集時にオブジェクトグラフを検証する機能(工場で作成された動的オブジェクトグラフを含む)
- コマンドとシグナル
- ZenjectBindingコンポーネントを使用してシーン内のコンポーネントに対する自動バインディング
Moqライブラリを使った自動モッキング
Installation
次のいずれかの方法でZenjectをインストールできます
-
リリースページから。ここでは、次の中から選択できます。
- Zenject-WithAsteroidsDemo.vX.X.unitypackage - これはAsset Storeで見つけたものと同等で、パッケージの一部としてサンプルゲーム "Asteroids"と "SpaceFighter"の両方が含まれています。 Zenjectのソースコードはすべてここに含まれています。
- Zenject.vX.X.unitypackage - サンプルプロジェクトを除いて上記と同じです。
- Zenject-NonUnity.vX.X.zip - Unityの外でZenjectを使用する場合はこれを使用します(通常のC#プロジェクトと同じように)
-
- Normally this should be the same as what you find in the Releases section, but may also be slightly out of date since Asset Store can take a week or so to review submissions sometimes.
-
From Source
- You can also just clone this repo and copy the
UnityProject/Assets/Zenject
directory to your own Unity3D project. In this case, make note of the folders underneath "OptionalExtras" and choose only the ones you want.
- You can also just clone this repo and copy the
History
Unityは素晴らしいゲームエンジンですが、新しい開発者が奨励するアプローチは、大きく、柔軟性があり、スケーラブルなコードベースを作成するのには適していません。特に、Unityが異なるゲームコンポーネント間の依存関係を管理するデフォルトの方法は、しばしば扱いにくく、エラーが発生する可能性があります。
このプロジェクトは、問題の概要を説明するSebastiano Mandalaの一連の偉大な記事を読んだ後に開始されました。 Sebastianoは概念証明を書いてオープンソーシングしていて、このライブラリの基礎となった。 Zenjectはまた、Ninjectから多くのインスピレーションを受けています(名前の意味)。
最後に、私はあなたがDIフレームワークの経験がなく、オブジェクト指向のコードを書いているなら、私を信頼してください、あなたは後で私に感謝するでしょう! DIを使って適切に疎結合コードを書く方法を学ぶと、単純に戻ることはできません。
Documentation
Zenjectのドキュメントは、次のセクションに分かれています。できるだけ早く立ち上げることができるように、2つの部分に分割されています(はじめと高度)。私は少なくともIntroductionセクションを読むことをお勧めしますが、必要に応じて詳細セクションで自由にジャンプしてください
また、提供されているサンプルプロジェクト() Zenject / OptionalExtras / SampleGame1
や Zenject / OptionalExtras / SampleGame2
を開くことで見つけることができます)で遊ぶこともできます。
このページの一番下にある典型的な使用シナリオの理解に役立つチートシートもあります。
これらのテストは、それぞれの特定の機能() Zenject / OptionalExtras / UnitTests
と Zenject / OptionalExtras / IntegrationTests
で見つけることができます)の使い方を示すのにも役立ちます
Table Of Contents
- Introduction
- What is Dependency Injection?
- Zenject API
- Advanced
- Binding
- Scriptable Object Installer
- Runtime Parameters For Installers
- Creating Objects Dynamically Using Factories
- Update / Initialization Order
- Zenject Order Of Operations
- Injecting data across scenes
- Scene Parenting Using Contract Names
- Scene Decorators
- Sub-Containers And Facades
- Writing Automated Unit Tests / Integration Tests
- Commands And Signals
- Auto-Mocking using Moq
- Creating Unity EditorWindow's with Zenject
-
Frequently Asked Questions
- Isn't this overkill? I mean, is using statically accessible singletons really that bad?
- Does this work on AOT platforms such as iOS and WebGL?
- How is Performance?
- Can I use .NET framework 4.0 and above?
- How do I use Unity style Coroutines in normal C# classes?
- How do I use Zenject with pools to minimize memory allocations?
- What games/tools/libraries are using Zenject
- Cheat Sheet
- Further Help
- Release Notes
- License
Theory
以下は、私の観点からの依存性注入の概要です。しかし、それは軽く保たれているので、その背景にある理論を書いた多くの人がいる(しばしばよりよい筆記能力を持つ)人が多いので、他の人材を探すことを強く勧めます。
また、理論の紹介として役立つビデオについては、こちらをご覧ください。
いくつかの機能を達成するために個々のクラスを書くとき、その目標を達成するためにシステム内の他のクラスとやりとりする必要があるでしょう。これを行う1つの方法は、具体的なコンストラクタを呼び出して、クラス自体に依存関係を作成させることです。
public class Foo
{
ISomeService _service;
public Foo()
{
_service = new SomeService();
}
public void DoSomething()
{
_service.PerformTask();
...
}
}
これは小さなプロジェクトではうまくいきますが、プロジェクトが成長するにつれて扱いにくくなります。クラスFooは、クラス「SomeService」に密接に結合されています。後で、別の具体的な実装を使用することを決めたら、Fooクラスに戻ってそれを変更する必要があります。
これを考えた後、最終的にFooがサービスの特定の実装を選択する詳細を気にしてはいけないという認識にたどり着きます。 Fooが気にするべきことは、それ自身の具体的な責任を果たすことです。 Fooが要求する抽象的なインターフェイスをサービスが満たしている限り、Fooは満足しています。私たちのクラスは次のようになります。
public class Foo
{
ISomeService _service;
public Foo(ISomeService service)
{
_service = service;
}
public void DoSomething()
{
_service.PerformTask();
...
}
}
これは優れていますが、今ではFoo(Fooという名前をつけましょう)を作成しているどのクラスでもFooの余分な依存関係を埋める問題があります:
public class Bar
{
public void DoSomething()
{
var foo = new Foo(new SomeService());
foo.DoSomething();
...
}
}
また、Class Barはおそらく、SomeService Fooの具体的な実装が何を使用しているかについて実際には気にしません。したがって、依存関係を再び押し上げる:
public class Bar
{
ISomeService _service;
public Bar(ISomeService service)
{
_service = service;
}
public void DoSomething()
{
var foo = new Foo(_service);
foo.DoSomething();
...
}
}
そのため、アプリケーションの「オブジェクトグラフ」で、どのクラスのどの特定の実装をさらに使用するかを決定する責任があることがわかります。これを極端に捉えて、アプリケーションのエントリーポイントに到達します。その時点で、すべての依存関係が満たされてから開始されます。アプリケーションのこの部分の依存性注入用語は、「合成ルート」と呼ばれます。通常、次のようになります。
var service = new SomeService();
var foo = new Foo(service);
var bar = new Bar(service);
var qux = new Qux(bar);
.. etc.
ZenjectのようなDIフレームワークは、上記のコードのように明示的に行う必要がないように、これらのすべての具体的な依存関係を作成して渡すこのプロセスを自動化するのに役立ちます。
Misconceptions
DIについては、多くの誤解があります。なぜなら最初はあなたの頭を完全に包み込むことが難しいからです。完全に「クリック」する前に時間と経験が必要です。
上記の例に示すように、DIを使用すると、特定のインタフェースの異なる実装(この例ではISomeService)を簡単に交換できます。しかし、これはDIが提供する多くの利点の1つにすぎません。
それより重要なのは、Zenjectのような依存性注入フレームワークを使うと、「Single Responsibility Principle」に従うことがより簡単にできるという事実です。 Zenjectがクラスを配線することを心配することによって、クラス自体は特定の責任を果たすことに集中することができます。
DIの初心者は、すべてのクラスからインタフェースを抽出し、そのクラスを直接使用するのではなく、すべてのインタフェースを使用するという共通の間違いがあります。目標は、コードをより疎結合にすることです。したがって、インターフェイスにバインドされることは、具体的なクラスにバインドされるよりも優れていると考えることは妥当です。しかし、ほとんどの場合、アプリケーションのさまざまな責任には、それらを実装する特定のクラスが1つしかありません。そのため、インターフェイスを使用すると不必要なメンテナンスオーバーヘッドが追加されます。また、具象クラスにはすでにパブリックメンバによって定義されたインタフェースがあります。経験則としては、クラスに複数の実装がある場合にのみインタフェースを作成することです(これはReused Abstraction Principleです). ?postid = 934))
その他の利点は次のとおりです。
- リファクタリング性 - DIを適切に使用する場合のように、コードが疎結合している場合、コードベース全体が変更に対してより弾力性があります。これらの変更を他の部分に混乱させることなく、コードベースの部分を完全に変更することができます。
- モジュラコードを奨励する - DIフレームワークを使用するときは、クラス間のインターフェイスについて考える必要があるため、デザインのプラクティスを順守する必要があります。
- テスト容易性 - 自動ユニットテストやユーザ主導テストの作成は、依存関係を別の方法で結ぶ別の '構成ルート'を書くことの単なる問題であるため、非常に簡単になります。 1つのサブシステムだけをテストしたいですか?新しいコンポジションルートを作成するだけです。 Zenjectは、コンポジションルート自体でコードの重複を避けるためにもサポートしています(インストーラを使用します - 後述)。
DIフレームワークを使用するためのさらなる正当な理由については、こちらを参照してください。
Hello World Example
using Zenject;
using UnityEngine;
using System.Collections;
public class TestInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container.Bind<string>().FromInstance("Hello World!");
Container.Bind<TestRunner>().NonLazy();
}
}
public class TestRunner
{
public TestRunner(string message)
{
Debug.Log(message);
}
}
この例を実行するには、次の手順を実行します。
- Unityで新しいシーンを作成する
- Hierarchyタブ内を右クリックし、
Zenject - > Scene Context
を選択します。
*シーン階層内のフォルダを右クリックし、作成 - > Zenject - > MonoInstaller
を選択します。 TestInstaller.csという名前を付けます。 (このテンプレートを使用しないでこのファイルを直接作成することもできます)。 - TestInstallerスクリプトをシーンに追加します(独自のGameObjectとして、またはSceneContextと同じGameObject上では問題ありません)
- "Installers"プロパティのインスペクタに新しい行を追加して(+ボタンを押して)、TestInstaller GameObjectをドラッグしてSceneContextのプロパティにTestInstallerへの参照を追加します
- TestInstallerを開き、上記のコードを貼り付けます
- Edit - > Zenject - > Validate Current Sceneを選択するかCTRL + SHIFT + Vを押してシーンを検証します。 (このステップは必ずしも必要ではありませんが、良い習慣に入ることに注意してください)
*実行
*また、ショートカットCTRL + SHIFT + Rを使用して「検証して実行」することもできます。検証は通常、ゲームの実行に顕著なオーバーヘッドを追加しないように十分に速いです。エラーを検出すると、起動時間を避けるため、反復する方がはるかに高速です。
*出力のためにユニティコンソールを観察する
SceneContext MonoBehaviourはアプリケーションのエントリーポイントで、Zenjectはシーンを蹴る前にさまざまな依存関係を設定します。 Zenjectシーンにコンテンツを追加するには、シーン内で使用されているすべての依存関係と、それらの関係を宣言する 'Installer'としてZenjectで参照されるものを記述する必要があります。 "NonLazy"としてマークされているすべての依存関係は、この時点で自動的に作成されます。また、 IInitializable
、 ITickable
などの標準Zenjectインターフェースを実装する依存関係も自動的に作成されます。 、読み続ける!
Injection
コンテナに型をバインドするには、さまざまな方法があります。これについては、次のセクションで説明しています。 これらの依存関係をクラスに注入する方法もいくつかあります。 これらは:
1 - Constructor Injection
public class Foo
{
IBar _bar;
public Foo(IBar bar)
{
_bar = bar;
}
}
2 - Field Injection
public class Foo
{
[Inject]
IBar _bar;
}
フィールド注入は、コンストラクタが呼び出された直後に発生します。 [Inject]属性でマークされたすべてのフィールドは、コンテナ内で検索され、値が与えられます。 これらのフィールドはプライベートまたはパブリックにすることができ、インジェクションは引き続き発生することに注意してください。
3 - Property Injection
public class Foo
{
[Inject]
public IBar Bar
{
get;
private set;
}
}
プロパティの注入は、C#のプロパティに適用される以外はフィールド注入と同じです。 フィールドと同じように、この場合セッターはプライベートまたはパブリックにすることができます。
4 - Method Injection
public class Foo
{
IBar _bar;
Qux _qux;
[Inject]
public Init(IBar bar, Qux qux)
{
_bar = bar;
_qux = qux;
}
}
メソッドインジェクションインジェクションはコンストラクターインジェクションと非常によく似ています。
これらのメソッドは、他のすべての注入タイプの後に呼び出されることに注意してください。このように設計されているため、これらのメソッドを使用してこれらの依存関係の1つを使用する初期化ロジックを実行できます。また、初期化ロジックだけを行う場合は、パラメータリストを空のままにしておくこともできます。
注入メソッドはいくつでも構いません。この場合、BaseクラスからDerivedクラスの順に呼び出されます。これは、コンストラクターのパラメーターを介して、派生クラスの多くの依存関係を基本クラスに転送する必要がなくなり、コンストラクターの動作と同様に、基本クラスの注入メソッドが最初に完了することを保証するのに便利です。
注入メソッドを介して受け取った依存関係自体は、すでに注入されている必要があることに注意してください。インジェクトメソッドを使用して初期化を行う必要がある場合があるため、インジェクトメソッドを使用して基本的な初期化を行う場合は、これが重要になります。
MonoBehavioursはコンストラクタを持つことができないので、[Inject]メソッドを使用して依存関係を注入するのがMonoBehavioursの推奨アプローチです。
推奨事項
- ベストプラクティスは、フィールド/プロパティインジェクションと比較してコンストラクタ/メソッドインジェクションを優先させることです。
- コンストラクタインジェクションは、依存関係をクラス作成時に一度だけ解決するよう強制します。これは通常あなたが望むものです。ほとんどの場合、最初の依存関係のためにパブリックプロパティを公開したくないのは、変更が可能であることを示唆しているからです。
- コンストラクタインジェクションはクラス間の循環依存性を保証しません。これは一般的には悪いことです。ただし、必要に応じてメソッド注入またはフィールド注入を使用してこれを行うことができます。
- コンストラクタ/メソッドインジェクションは、ZenjectなどのDIフレームワークなしでコードを再利用することを決めた場合に移植性が向上します。パブリックプロパティでも同じことができますが、エラーが発生しやすくなります(1つのフィールドを初期化してオブジェクトを無効な状態にするのを忘れた方が簡単です)
- 最後に、コンストラクタ/メソッド注入は、別のプログラマがコードを読んでいるときにクラスのすべての依存関係が何であるかを明確にします。彼らは単にメソッドのパラメータリストを見ることができます。これはまた、クラスがあまりにも多くの依存性を持ち、分割されるべきであるときにはもっと明白になるので(これはコンストラクタのパラメータリストが長すぎるため)
Binding
すべての依存性注入フレームワークは、最終的にタイプをインスタンスにバインドするためのフレームワークにすぎません。
Zenjectでは、依存関係マッピングは、コンテナと呼ばれるものにバインディングを追加することによって行われます。コンテナは、特定のオブジェクトのすべての依存関係を再帰的に解決することによって、アプリケーション内のすべてのオブジェクトインスタンスを作成する方法を「認識」する必要があります。
コンテナは、指定された型のインスタンスを作成するように要求されると、C#reflectionを使用してコンストラクタ引数のリスト、および[Inject]属性でマークされたすべてのフィールド/プロパティを検索します。次に、これらの必要な依存関係のそれぞれを解決しようとします。これらの依存関係は、コンストラクターを呼び出して新しいインスタンスを作成するために使用されます。
したがって、各Zenjectアプリケーションは、バインドコマンドを介して行われるこれらの依存関係のそれぞれの解決方法をコンテナに伝えなければなりません。たとえば、次のクラスを指定します。
public class Foo
{
IBar _bar;
public Foo(IBar bar)
{
_bar = bar;
}
}
このクラスの依存関係を以下のように結びつけることができます:
Container.Bind<Foo>().AsSingle();
Container.Bind<IBar>().To<Bar>().AsSingle();
これは、Foo型の依存関係を必要とするすべてのクラスが同じインスタンスを使用しなければならないことをZenjectに伝え、必要なときに自動的に作成します。 同様に、IBarインターフェース(Fooなど)を必要とするクラスには、タイプBarの同じインスタンスが与えられます。
bindコマンドの完全な形式は次のとおりです。 ほとんどの場合、これらのメソッドのすべてを使用するわけではなく、指定されていない場合はすべて論理的な既定値を持つことに注意してください
Container.Bind<ContractType>()
.To<ResultType>()
.WithId(Identifier)
.FromConstructionMethod()
.AsScope()
.WithArguments(Arguments)
.When(Condition)
.InheritInSubContainers()
.NonLazy();
Where:
-
ContractType = The type that you are creating a binding for.
- この値は注入されているフィールド/パラメータのタイプに対応します。
-
ResultType = The type to bind to.
- Default: ContractType
- このタイプは、ContractTypeに等しいか、ContractTypeから派生しなければなりません。 指定されていない場合は、ToSelf()とみなされます。つまり、ResultTypeはContractTypeと同じです。 この値は、この型のインスタンスを取得するためにConstructionMethodとして与えられたものによって使用されます
-
Identifier = バインディングを一意に識別するために使用する値。 これはほとんどの場合無視できますが、同じ契約タイプの複数のバインディングを区別する必要がある場合には非常に便利です。 詳細については、こちらをご覧ください。
-
ConstructionMethod = ResultTypeのインスタンスが作成/取得されるメソッド。 さまざまな構築方法の詳細については、このセクションを参照してください。
- Default: FromNew()
- Examples: eg. FromGetter, FromMethod, FromPrefab, FromResolve, FromSubContainerResolve, FromInstance, etc.
-
Scope = この値は、生成されたインスタンスが複数回の注入で再利用される頻度を決定します。
-
Default: AsTransient
-
次のいずれかになります。
- AsTransient - インスタンスをまったく再利用しません。 ContractTypeが要求されるたびに、DiContainerはResultTypeという新しいインスタンスを返します
- AsCached - ContractTypeがリクエストされるたびに、ResultTypeの同じインスタンスを再使用します。これは最初の使用時に遅延生成されます
- AsSingle - DiContainer全体でResultTypeの同じインスタンスを再使用します。最初の使用時に遅延して生成されます。 AsCachedは、複数のバインドコマンドで同じインスタンスにバインドできるため、AsCachedの強力なバージョンと考えることができます。 DiContainerにResultTypeのインスタンスが1つだけ存在することも保証されます(つまり、ResultTypeが 'Singleton'という名前になります)。
-
ほとんどの場合、おそらくAsSingleを使用したいと思うでしょうが、AsTransientとAsCachedもその用途があります。
-
異なるスコープタイプの違いを説明するには、次の例を参照してください。
public interface IBar { } public class Bar : IBar { } public class Foo() { public Foo(Bar bar) { } }
// This will cause every instance of Foo to be given a brand new instance of Bar Container.Bind<Bar>().AsTransient();
// This will cause every instance of Foo to be given the same instance of Bar Container.Bind<Bar>().AsCached();
public class Qux() { public Qux(IBar bar) { } }
// This will cause both Foo and Qux to get different instances of type Bar // However, every instance of Foo will be given the the same instance of type Bar // and similarly for Qux Container.Bind<Bar>().AsCached(); Container.Bind<IBar>().To<Bar>().AsCached();
// This will cause both Foo and Qux to get the same instance of type Bar Container.Bind<Bar>().AsSingle(); Container.Bind<IBar>().To<Bar>().AsSingle();
-
-
Arguments = A list of objects to use when constructing the new instance of type ResultType. This can be useful as an alternative to
Container.BindInstance(arg).WhenInjectedInto<ResultType>()
-
Condition = The condition that must be true for this binding to be chosen. See here for more details.
-
InheritInSubContainers = If supplied, then this binding will automatically be inherited from any subcontainers that are created from it. In other words, the result will be equivalent to copying and pasting the
Container.Bind
statement into the installer for every sub-container. -
NonLazy = Normally, the ResultType is only ever instantiated when the binding is first used (aka "lazily"). However, when NonLazy is used, ResultType will immediately by created on startup.
Construction Methods
-
FromNew - C#new演算子を使用して作成します。 構築方法が指定されていない場合のデフォルトです。
// These are both the same Container.Bind<Foo>(); Container.Bind<Foo>().FromNew();
-
FromInstance - 指定されたインスタンスを使用する
Container.Bind<Foo>().FromInstance(new Foo()); // You can also use this short hand which just takes ContractType from the parameter type Container.BindInstance(new Foo()); // This is also what you would typically use for primitive types Container.BindInstance(5.13f); Container.BindInstance("foo");
-
FromMethod - カスタムメソッドを使用して作成
Container.Bind<Foo>().FromMethod(SomeMethod); Foo SomeMethod(InjectContext context) { ... return new Foo(); }
-
FromComponent - 既存のゲームオブジェクトに新しいコンポーネントとして作成します。 ResultTypeはこの場合UnityEngine.MonoBehaviour / UnityEngine.Componentから派生する必要があります
Container.Bind<Foo>().FromComponent(someGameObject);
-
FromSiblingComponent - コンポーネントが挿入されている同じゲームオブジェクト上に新しいコンポーネントとして作成します。 ResultTypeはこの場合UnityEngine.MonoBehaviour / UnityEngine.Componentから派生する必要があります
Container.Bind<Foo>().FromSiblingComponent(someGameObject);
-
FromGameObject - 新しいゲームオブジェクトに新しいコンポーネントとして作成します。 ResultTypeはこの場合UnityEngine.MonoBehaviour / UnityEngine.Componentから派生する必要があります
Container.Bind<Foo>().FromGameObject();
-
FromPrefab - 与えられたプレハブをインスタンス化し、それからタイプResultTypeを検索することによって作成します。 ResultTypeはこの場合UnityEngine.MonoBehaviour / UnityEngine.Componentから派生する必要があります
Container.Bind<Foo>().FromPrefab(somePrefab);
-
FromPrefabResource -指定されたリソースパスでプレハブをインスタンス化し、タイプResultTypeを検索して作成します。 ResultTypeはこの場合UnityEngine.MonoBehaviour / UnityEngine.Componentから派生する必要があります
Container.Bind<Foo>().FromPrefabResource("Some/Path/Foo");
-
FromResource - ResultTypeのUnity3d関数
Resources.Load
を呼び出して作成します。 これは、テクスチャ、サウンド、プレハブ、ScriptableObjectから派生したカスタムクラスなど、Resources.Load
がロードできるあらゆるタイプをロードするために使用できます。public class Foo : ScriptableObject { } Container.Bind<Foo>().FromResource("Some/Path/Foo");
-
FromResolve - コンテナの別のルックアップを実行する(つまり、
DiContainer.Resolve <ResultType>()
を呼び出して)インスタンスを取得します。 これが機能するには、ResultTypeを別のbind文でバインドする必要があることに注意してください。 この構築方法は、以下の例に示すように、インターフェイスを別のインターフェイスにバインドする場合に特に便利ですpublic interface IFoo { } public interface IBar : IFoo { } public class Foo : IBar { } Container.Bind<IFoo>().To<IBar>().FromResolve(); Container.Bind<IBar>().To<Foo>();
-
FromFactory - カスタムファクトリクラスを使用してインスタンスを作成します。 この構築方法は
FromMethod
に似ていますが、ロジックがより複雑であるか依存関係が必要な場合にはよりクリーンにすることができます(ファクトリ自体は依存関係を注入することができます)class FooFactory : IFactory<Foo> { public Foo Create() { // ... return new Foo(); } } Container.Bind<Foo>().FromFactory<FooFactory>()
-
FromResolveGetter - コンテナに対して別のルックアップを実行することによって得られた別の依存関係のプロパティからインスタンスを取得します(つまり、
DiContainer.Resolve <ObjectType>()
を呼び出してから、ResultType型の返されたインスタンスの値にアクセスします) )。 これが機能するには、** ObjectType **は別のバインドステートメントでバインドする必要があります。public class Bar { } public class Foo { public Bar GetBar() { return new Bar(); } } Container.Bind<Foo>(); Container.Bind<Bar>().FromResolveGetter<Foo>(x => x.GetBar());
-
FromSubContainerResolve - サブコンテナのルックアップを実行してResultTypeを取得します。 これが機能するには、サブコンテナにResultTypeのバインディングが必要であることに注意してください。 このアプローチは非常に強力です。なぜなら、関連する依存関係をミニコンテナ内でグループ化してから、特定のクラス( "Facades" )を使用して、このグループの依存関係をより高いレベルで操作します。 サブコンテナの使用の詳細については、このセクションを参照してください。 サブコンテナを定義するには4つの方法があります。
-
ByMethod - メソッドを使用してサブコンテナを初期化します。
Container.Bind<Foo>().FromSubContainerResolve().ByMethod(InstallFooFacade); void InstallFooFacade(DiContainer subContainer) { subContainer.Bind<Foo>(); }
-
ByInstaller -
Installer
から派生したクラスを使用してサブコンテナを初期化します。 これは、特に、インストーラ自体にデータを注入する必要がある場合に、ByMethodを使用するよりも、よりクリーンで、エラーを起こしにくい代替手段となります。 ByMethodを使用すると、メソッド内でsubContainerではなくContainerを誤って使用するため、エラーが発生しにくくなります。Container.Bind<Foo>().FromSubContainerResolve().ByInstaller<FooFacadeInstaller>(); class FooFacadeInstaller : Installer { public override void InstallBindings() { Container.Bind<Foo>(); } }
-
ByPrefab -プレハブを使用してサブコンテナを初期化します。 プレハブには、ルートゲームオブジェクトに添付された
GameObjectContext
コンポーネントが含まれていなければならないことに注意してください。GameObjectContext
の詳細については、このセクションを参照してください。Container.Bind<Foo>().FromSubContainerResolve().ByPrefab(MyPrefab); // Assuming here that this installer is added to the GameObjectContext at the root // of the prefab. You could also use a ZenjectBinding in the case where Foo is a MonoBehaviour class FooFacadeInstaller : MonoInstaller { public override void InstallBindings() { Container.Bind<Foo>(); } }
-
ByPrefabResource -
Resources.Load
で得られたプレハブを使ってサブコンテナを初期化します。 プレハブには、ルートゲームオブジェクトに添付されたGameObjectContext
コンポーネントが含まれていなければならないことに注意してください。Container.Bind<Foo>().FromSubContainerResolve().ByPrefabResource("Path/To/MyPrefab");
-
Installers
多くの場合、各サブシステムに関連するバインディングのコレクションがいくつか存在するため、それらのバインディングを再利用可能なオブジェクトにグループ化することは理にかなっています。 Zenjectでは、この再利用可能なオブジェクトは「インストーラ」と呼ばれます。 次のように新しいインストーラを定義できます。
public class FooInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container.BindAllInterfaces<Foo>().To<Foo>().AsSingle();
Container.Bind<Bar>().AsSingle();
// etc...
}
}
InstallBindingsメソッドをオーバーライドすることでバインディングを追加します。これはインストーラが追加された Context
(通常は SceneContext
です)によって呼び出されます。 MonoInstallerはMonoBehaviourですので、FooInstallerをGameObjectに追加することで追加できます。 GameObjectであるため、パブリックメンバーを追加してUnityインスペクタからインストーラを設定することもできます。これにより、シーン内の参照の追加、アセットへの参照、または単にデータのチューニングが可能になります(詳細については、こちらを参照してください)。チューニングデータ)。
インストーラがトリガされるためには、それは SceneContext
オブジェクトのInstallersプロパティにアタッチされなければならないことに注意してください。インストーラーは SceneContext
に与えられた順序で実行されますが、この順序は通常は重要ではありません(インストールプロセス中に何もインスタンス化しないでください)
多くの場合、インストーラをMonoInstallerから派生させて、インスペクタ設定を行うことができます。 MonoBehaviourである必要がない場合に使うことができる、単に `Installer 'という別の基本クラスもあります。
別のインストーラからインストーラを呼び出すこともできます。例えば:
public class BarInstaller : Installer<BarInstaller>
{
public override void InstallBindings()
{
...
}
}
public class FooInstaller : MonoInstaller
{
public override void InstallBindings()
{
BarInstaller.Install(Container);
}
}
この場合、BarInstallerは Installer <>
(一般的な引数に注意してください)であり、MonoInstallerではなく、単にBarInstaller.Install(Container)を呼び出すことができ、BarInstallerをシーンは既に。 BarInstaller.Installを呼び出すと、すぐにBarInstallerの一時インスタンスが作成され、そのインスタンスにInstallBindingsが呼び出されます。これは、このインストーラーがインストールするインストーラーについて繰り返されます。また、 Installer <>
基本クラスを使うときは、必ず Installer <>
の総称引数として渡す必要があります。これは、 Installer <>
基本クラスが静的メソッド BarInstaller.Install
を定義できるようにするために必要です。ランタイムパラメータをサポートするために、この方法で設計されています(後述)。
インストーラを使用する主な理由の1つは、すべてのバインディングをシーンごとにまとめて宣言するだけでなく、再利用可能にすることです。これは、 Installer <>
型のインストーラでは問題ありません。あなたが使いたいすべてのシーンについて上記のように FooInstaller.Install
を呼び出すことができるからです。しかし、複数のシーンでMonoInstallerを再利用するにはどうしたらいいですか? ?
これを行うには3つの方法があります。
-
Prefab instances within the scene. MonoInstallerをシーン内のゲームオブジェクトに接続したら、そこからプレハブを作成できます。 これは、MonoInstallerのインスペクタで行った設定をシーン間で共有することができます(また、必要に応じてシーン単位のオーバーライドも可能です)。 シーンに追加したら、それをドラッグして「コンテキスト」のインストーラプロパティにドロップすることができます
-
Prefabs. インストーラプレハブをプロジェクトタブからSceneContextのInstallerPrefabsプロパティに直接ドラッグすることもできます。 この場合、プレハブをシーンにインスタンシエートするときのように、シーン単位のオーバーライドを行うことはできませんが、シーンの乱れを避けるためには良いことに注意してください。
-
Prefabs within Resources folder. Resourresフォルダの下にインストーラプレハブを置き、Resourcesパスを使用してコードから直接インストールすることもできます。 使用法の詳細については、こちらを参照してください。
MonoInstallerと Installer <>
に加えて、(特に設定のための)いくつかの利点を持つScriptableObjectInstallerを使用することもできます - 詳しくは、こちらを参照してください。
他のインストーラからインストーラを呼び出すときは、パラメータを渡すことが一般的です。 その仕組みの詳細については、こちらを参照してください。
ITickable
多くの場合、通常のC#クラスを優先してMonoBehavioursの余分な重量を避けることが望ましいです。 Zenjectでは、MonoBehaviourを使用するために通常必要とする機能を反映するインターフェイスを提供することで、これをもっと簡単に行うことができます。
たとえば、フレームごとに実行する必要のあるコードがある場合、 ITickable
インターフェースを実装することができます:
public class Ship : ITickable
{
public void Tick()
{
// Perform per frame tasks
}
}
それでは、あなたのインストーラの1つに以下を含めるだけです。
Container.Bind<ITickable>().To<Ship>().AsSingle();
または、船のインプリメンテーションのインターフェイスを覚えておく必要がない場合は、
Container.BindAllInterfaces<Ship>().To<Ship>().AsSingle();
Tick()がすべてのITickablesで呼び出される順序は、ここで概説されているように、設定可能であることに注意してください。
また、ILightTickableとIFixedTickableというインタフェースがあり、他の統一の更新メソッドと同様に動作することにも注意してください。
IInitializable
特定のオブジェクトで発生する必要がある初期化がある場合は、このコードをコンストラクタに含めることができます。ただし、これは、初期化ロジックが作成されるオブジェクトグラフの途中で発生するため、理想的ではない可能性があります。
1つの方法は IInitializable
を実装し、Initialize()
メソッドで初期化ロジックを実行することです。このInitializeメソッドは、オブジェクトグラフ全体が構築され、すべてのコンストラクタが呼び出された後に呼び出されます。
初期オブジェクトグラフのコンストラクタは、UnityのAwakeイベント中に呼び出され、 IInitializable.Initialize
メソッドはUnityのStartイベントで直ちに呼び出されることに注意してください。したがって、コンストラクタとは対照的に IInitializable
を使用することは、Unityの独自の推奨に沿ったものであり、Awakeフェーズを使用してオブジェクト参照を設定し、開始フェーズをより複雑な初期化ロジックに使用する必要があります。
初期化の順序は、ここで説明したように、 ITickable
と同様にカスタマイズ可能であるため、コンストラクタや[Inject]
メソッドを使うよりも良いでしょう。 >。
public class Ship : IInitializable
{
public void Initialize()
{
// Initialize your object here
}
}
IInitializable
はスタートアップの初期化にはうまくいきますが、工場から動的に作成されるオブジェクトはどうでしょうか? (私がここで言及しているものについては、このセクションを参照してください)。 これらの場合、 [Inject]
メソッドを使いたいと思うでしょう:
public class Foo
{
[Inject]
IBar _bar;
[Inject]
public void Initialize()
{
...
_bar.DoStuff();
...
}
}
IDisposable
アプリケーションが終了したときにクリーンアップしたい外部リソースがある場合、シーンが変更されたり、コンテキストオブジェクトが何らかの理由で破棄された場合、クラスを単に以下のように IDisposable
として宣言できます:
public class Logger : IInitializable, IDisposable
{
FileStream _outStream;
public void Initialize()
{
_outStream = File.Open("log.txt", FileMode.Open);
}
public void Log(string msg)
{
_outStream.WriteLine(msg);
}
public void Dispose()
{
_outStream.Close();
}
}
次に、インストーラに次のものを含めることができます。
Container.Bind<Logger>().AsSingle();
Container.Bind<IInitializable>().To<Logger>().AsSingle();
Container.Bind<IDisposable>().To<Logger>().AsSingle();
または、次のショートカットを使用することもできます。
Container.BindAllInterfacesAndSelf<Logger>().To<Logger>().AsSingle();
これは、Sceneが変更されたり、単一性のアプリケーションが閉じられたりすると、SceneContextクラスを含むすべてのMonoBehavioursに対してUnidイベントOnDestroy()が呼び出され、 IDisposable
にバインドされたすべてのオブジェクトに対してDispose()
この例は良いアイデアかもしれないことに注意してください(例えば、あなたのアプリケーションがクラッシュした場合、ファイルは開いたままになります)が、ポイントを示しています:)
Using the Unity Inspector To Configure Settings
MonoBehaviourではなく、通常のC#クラスとしてコードの大部分を記述することの意味は、インスペクタを使用してデータを構成する能力を失うことです。 しかし、サンプルプロジェクトに見られるように、次のパターンを使用してZenjectでこれを利用することもできます。
public class AsteroidsInstaller : MonoInstaller
{
public Settings SceneSettings;
public override void InstallBindings()
{
...
Container.BindInstance(SceneSettings.StateMoving);
...
}
[Serializable]
public class Settings
{
...
public ShipStateMoving.Settings StateMoving;
...
}
}
このメソッドに従う場合は、必ず設定ラッパーに[Serializable]属性を含める必要があります。そうしないとUnityインスペクターに表示されません。
これを実際に見るには、小惑星の場面を開始して、 船 - >状態移動 - >移動速度
設定を調整して、船の速度が変わるのを見てみてください。
これを行うもう1つの方法は、ScriptableObjectInstallerを使用して設定を保存することです。これには、実行時に設定を変更して、再生モードが停止しても自動的に変更を保持できるという利点があります。 詳細については、こちらを参照してください。
Object Graph Validation
DIフレームワークを使用してバインディングを設定するときの通常のワークフローは、次のようなものです。
- いくつかのバインディングをコードに追加する
- あなたのアプリを実行する
- 多くのDI関連の例外を観察する
- あなたのバインディングを変更して問題に対処する
- 繰り返し
これは小規模なプロジェクトではうまくいきますが、プロジェクトの複雑さが増すにつれ、しばしば面倒なプロセスになります。アプリケーションの起動時間が特に悪い場合、または例外が実行時にさまざまな時点で工場からのみ発生した場合、問題は悪化します。偉大なことは、あなたのオブジェクトグラフを分析し、アプリケーション全体を起動するコストを必要とせずに、欠落しているバインディングがどこにあるかを正確に伝えるツールです。
これは、メニュー項目 Edit -> Zenject -> Validate Then Run
を実行するか、単純にCTRL + SHIFT + Vを押して、検証したいシーンを開いた状態でZenjectで行うことができます。これにより、現在のシーンのすべてのインストーラが実行され、結果は完全にバインドされたコンテナになります。次に、オブジェクトグラフを反復して、すべてのバインディングが見つかることを確認します(実際にインスタンス化することなく)。実際にクラスをインスタンス化する代わりに、ダミーオブジェクトをコンテナに格納することで動作します
あるいは、メニュー項目 Edit - > Zenject - > Validate Then Run
を実行するか、CTRL + SHIFT + Rを押すだけです。これにより、開いているシーンが検証され、検証が成功すると再生モードが開始されます。検証は通常非常に速いので、特にゲームにコストのかかる起動時間がある場合は、常にプレイを打ち込むのに適しています。
Scene Bindings
多くの場合、MonoBehaviourがUnityエディタ内のシーンに追加されています(つまり、エディタ時にはランタイムではありません)。また、これらのMonoBehaviourをZenjectコンテナに追加して、 他のクラス。
これが行われる通常の方法は、以下のようにインストーラ内でこれらのオブジェクトへのパブリックリファレンスを追加することです。
public class Foo : MonoBehaviour
{
}
public class GameInstaller : MonoInstaller
{
public Foo foo;
public override void InstallBindings()
{
Container.BindInstance(foo);
Container.Bind<IInitializable>().To<GameRunner>().AsSingle();
}
}
public class GameRunner : IInitializable
{
readonly Foo _foo;
public GameRunner(Foo foo)
{
_foo = foo;
}
public void Initialize()
{
...
}
}
これはうまく動作しますが、場合によっては煩雑になることがあります。 たとえば、アーティストがシーンに任意の数の Enemy
オブジェクトを追加できるようにしたい場合、それらのすべての Enemy
オブジェクトをZenjectコンテナに追加することもできます。 この場合、インストーラのいずれかのインスペクタにそれぞれ手動でドラッグする必要があります。 これは簡単に忘れることができるので間違いやすいです、あるいは Enemy
ゲームオブジェクトを削除しますが、インストーラのインスペクタでnull参照を削除するのを忘れないでください。
これを行う別の方法は ZenjectBinding
コンポーネントを使うことです。 Zenjectコンテナに自動的に追加したいゲームオブジェクトに ZenjectBinding
MonoBehaviourを追加することで、これを行うことができます。
例えば、シーン内に Foo
型のMonoBehaviourがある場合は、その上に ZenjectBinding
を追加してから、ZoojectBindingコンポーネントのComponentプロパティにFooコンポーネントをドラッグします。
インストーラは次のようになります。
public class GameInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container.Bind<IInitializable>().To<GameRunner>().AsSingle();
}
}
ZenjectBinding
コンポーネントは以下のプロパティを持っています:
-
Bind Type - これにより、使用する「契約タイプ」を決定します。 次のいずれかの値に設定できます。
Self
これは、これを行った最初の例と同じです:
Container.Bind<Foo>().FromInstance(_foo);
これもこれと同じです:
Container.BindInstance(_foo);
だから、このゲームオブジェクトを複製して複数のゲームオブジェクトを
Foo
(そしてそのZenjectBinding
)上に置くと、それらはすべてこの方法でコンテナにバインドされます。 ですから、これを実行した後、上記のGameRunner
をList <Foo>
に変更する必要があります。そうしないと、Zenjectの例外が発生します(こちらを参照)。 リストのバインディング)。AllInterfaces
このバインド・タイプは、次のものと等価です。
Container.BindAllInterfaces(_foo.GetType()).FromInstance(_foo);
ただし、この場合、
GameRunner
はコンストラクタにIFoo
型を要求しなければなりません。GameRunner
がFoo
型を要求した場合、BindAllInterfaces
メソッドは具体的な型ではなくインタフェースのみをバインドするので、Zenjectは例外をスローします。 あなたは具体的なタイプをしたい場合は、あなたが使用することができます:AllInterfacesAndSelf
このバインド・タイプは、次のものと等価です。
Container.BindAllInterfacesAndSelf(_foo.GetType()).FromInstance(_foo);
これは、インタフェースを必要とするのではなく、Foo型を使用して直接Fooにアクセスできることを除いて、AllInterfacesと同じです。
4.BaseType
このバインド・タイプは、次のものと等価です。
Container.BindAllInterfacesAndSelf(_foo.GetType().BaseType()).FromInstance(_foo);
-
Identifier - この値はほとんどの場合空のままにすることができます。 バインディングの識別子として使用されるものが決定されます。 たとえば、 "Foo1"に設定されている場合は、以下を実行するのと同じです。
Container.BindInstance(_foo).WithId("Foo1");
-
Context - これは完全にオプションであり、ほとんどの場合は未設定のままにしておく必要があります。 これにより、バインディングを適用するコンテキストが決定されます。 未設定のままにすると、GameObjectがどんなコンテキストであっても使用します。ほとんどの場合、これはSceneContextになりますが、GameObjectContext内にあればそのコンテナにバインドされます。 このフィールドの重要な使用事例の1つは、コンポーネントがGameObjectContext内にある場合に、このフィールドにSceneContextをドラッグできるようにすることです。 これにより、MonoBehaviourをGameObjectContextによって与えられたサブコンテナ全体の Facade として扱うことができます。
General Guidelines / Recommendations / Gotchas / Tips and Tricks
-
GameObject.Instantiateを使用しないでください。オブジェクトに依存関係を注入させたい場合
- 実行時にプレハブをインスタンス化し、MonoBehaviourを自動的に注入する場合は、ファクトリを使用することをお勧めします。また、
InstantiatePrefab
メソッドのいずれかを呼び出してDiContainerを直接使用してプレハブをインスタンス化することもできます。 GameObject.Instantiateとは対照的に、これらの方法を使用すると、[Inject]属性でマークされたフィールドが正しく埋められ、プレハブ内のすべての[Inject]メソッドが呼び出されます。
- 実行時にプレハブをインスタンス化し、MonoBehaviourを自動的に注入する場合は、ファクトリを使用することをお勧めします。また、
-
DIを使用したベストプラクティスは、コンポジションルートの「レイヤー」内のコンテナを参照するのみです。
- ファクトリはこのレイヤの一部であり、コンテナはそこで参照できます(実行時にオブジェクトを作成するために必要です)。たとえば、サンプルプロジェクトのShipStateFactoryを参照してください。詳細については、こちらを参照してください。
-
動的に作成されたオブジェクトにIInitializable、ITickable、IDisposableを使用しないでください
- IInitializable型のオブジェクトは、UnityのStartフェーズ中に起動時に一度だけ初期化されます。ファクトリを通してオブジェクトを作成し、それが
IInitializable
から派生する場合、Initialize()メソッドは呼び出されません。この場合、[Inject]メソッドを使用する必要があります。 - ITickableとIDisposableについても同様です。これらから派生するものは、起動時に作成された元のオブジェクトグラフの一部でない限り、何も行いません。
- Update()メソッドを持つオブジェクトを動的に作成した場合は、手動でUpdate()を呼び出すのが最善の方法ですが、これを行うのに理想的な上位レベルのマネージャー的なクラスが存在することがよくあります。しかし、動的オブジェクトに
ITickable
を使いたい場合は、TickableManagerへの依存関係を宣言し、それを明示的に追加/削除することもできます。
- IInitializable型のオブジェクトは、UnityのStartフェーズ中に起動時に一度だけ初期化されます。ファクトリを通してオブジェクトを作成し、それが
-
複数のコンストラクタの使用
- Zenjectは現在、複数のコンストラクタへの注入をサポートしていません。複数のコンストラクタを持つことはできますが、そのうちの1つを[Inject]属性でマークして、Zenjectがどちらを使うかを知る必要があります。
-
動的に作成されたMonoBehavioursのメソッドを起動/起動するために[Inject]メソッドを使用する
- Zenjectを使用するときによく発生する問題の1つは、ゲームオブジェクトが動的にインスタンス化され、そのゲームオブジェクトのMonoBehavioursの1つが、Start()メソッドまたはAwake()メソッドに注入されたフィールド依存関係の1つを使用しようとすることです。このような場合、依存関係は注入されていないかのように、依然としてヌルになります。ここで問題となるのは、ZenjectはGameObject.Instantiateの呼び出しが完了するまで依存関係を埋めることができず、ほとんどの場合、GameObject.InstantiateはStart()メソッドとAwake()メソッドを呼び出します。解決方法は、Start()またはAwake()を使用せず、代わりに新しいメソッドを定義し、[Inject]属性でマークすることです。これにより、メソッドを実行する前にすべての依存関係が解決されたことが保証されます。
-
** Unityの外でのZenjectの使用**
- Zenjectは主にUnity3Dで動作するように設計されています。しかし、Unity3D以外の汎用DIフレームワークとしても使用できます。 Zenjectは、ASP.NET MVCおよびWPFプロジェクトで正常に使用されています。これを行うには、GitHubページの「リリース」セクションからDLLを取得するか、
NonUnityBuild / Zenject.sln
で自分でソリューションをビルドします
- Zenjectは主にUnity3Dで動作するように設計されています。しかし、Unity3D以外の汎用DIフレームワークとしても使用できます。 Zenjectは、ASP.NET MVCおよびWPFプロジェクトで正常に使用されています。これを行うには、GitHubページの「リリース」セクションからDLLを取得するか、
-
緩やかにインスタンス化されたオブジェクトとオブジェクトグラフ
- Zenjectは、インストーラで設定したバインディングによって定義されたすべてのオブジェクトを即座にインスタンス化しません。代わりに、Zenjectはいくつかのルートレベルのオブジェクトを構築し、その後は使用法に基づいて残りのインスタンスを遅延インスタンス化します。ルートレベルのオブジェクトは、IInitializable / ITickable / IDisposableにバインドされたクラスと、
NonLazy()
とマークされたバインディングで宣言されたクラスです。 - The order that things occur in is wrong, like injection is occurring too late, or Initialize() event is not called at the right time, etc.
- It may be because the 'script execution order' of the Zenject classes 'ProjectContext' or 'SceneContext' is incorrect. These classes should always have the earliest or near earliest execution order. This should already be set by default (since this setting is included in the
cs.meta
files for these classes). However if you are compiling Zenject yourself or have a unique configuration, you may want to make sure, which you can do by going to "Edit -> Project Settings -> Script Execution Order" and confirming that these classes are at the top, before the default time.
- Zenjectは、インストーラで設定したバインディングによって定義されたすべてのオブジェクトを即座にインスタンス化しません。代わりに、Zenjectはいくつかのルートレベルのオブジェクトを構築し、その後は使用法に基づいて残りのインスタンスを遅延インスタンス化します。ルートレベルのオブジェクトは、IInitializable / ITickable / IDisposableにバインドされたクラスと、
-
トランジェントはデフォルトの範囲です
-
別のよくある間違いは、スコープ(AsSingle、AsTransient、AsCachedなど)を定義するコールを外し、意図せずデフォルト(AsTransient)を使用することです。 例えば:
-
Container.BindAllInterfacesAndSelf <Foo>()。<Foo>();
-
上記のバインディングは、あなたがやりたいことではありません。Fooが持つすべてのインターフェースに対してFooのインスタンスを作成するためです。 代わりに、ほとんどの場合、このケースでは
AsCached
またはAsSingle
のどちらかを使いたいでしょう
-
Game Object Bind Methods
新しいゲームオブジェクトを作成するバインディング(例えば、FromPrefabまたはFromGameObject)には、2つの追加のバインドメソッド
-
WithGameObjectName = このバインディングに関連付けられた新しいゲームオブジェクトを与えるための名前。
Container.Bind<Foo>().FromPrefabResource("Some/Path/Foo").WithGameObjectName("Foo1"); Container.Bind<Foo>().FromGameObject().WithGameObjectName("Foo2");
-
UnderTransformGroup(string) = 新しいゲームオブジェクトを配置するトランスフォームグループの名前。 これは、工場では、プレハブの多くのコピーを作成するために使用することができますので、シーンの階層構造内で一緒に自動的にグループ化することができれば便利です。
Container.BindFactory<Bullet, Bullet.Factory>() .FromPrefab(BulletPrefab) .UnderTransformGroup("Bullets");
-
UnderTransform(Transform) = 新しいゲームオブジェクトを配置する実際の変換。
Container.BindFactory<Bullet, Bullet.Factory>() .FromPrefab(BulletPrefab) .UnderTransform(BulletTransform);
Optional Binding
いくつかの依存関係を次のようにオプションとして宣言できます。
public class Bar
{
public Bar(
[InjectOptional]
IFoo foo)
{
...
}
}
...
// You can comment this out and it will still work
Container.Bind<IFoo>().AsSingle();
オプションの依存関係がどのインストーラにもバインドされていない場合は、nullとしてインジェクトされます。
依存関係がプリミティブ型(int、float、structなど)の場合、デフォルト値(intの場合は0)が挿入されます。
また、次のような標準的なC#方法を使用して、明示的なデフォルトを割り当てることもできます。
public class Bar
{
public Bar(int foo = 5)
{
...
}
}
...
// Can comment this out and 5 will be used instead
Container.BindInstance(1);
この場合、 [InjectOptional]
は既にデフォルト値で暗示されているので、 [InjectOptional]
は必要ないことに注意してください。
あるいは、プリミティブパラメータをnullableとして定義し、それが供給されるかどうかに応じてロジックを実行することができます。
public class Bar
{
int _foo;
public Bar(
[InjectOptional]
int? foo)
{
if (foo == null)
{
// Use 5 if unspecified
_foo = 5;
}
else
{
_foo = foo.Value;
}
}
}
...
// Can comment this out and it will use 5 instead
Container.BindInstance(1);
Conditional Bindings
多くの場合、特定の依存関係が注入される場所を制限する必要があります。 次の構文を使用してこれを行うことができます。
Container.Bind<IFoo>().To<Foo1>().AsSingle().WhenInjectedInto<Bar1>();
Container.Bind<IFoo>().To<Foo2>().AsSingle().WhenInjectedInto<Bar2>();
WhenInjectedInto
は、より一般的なWhen()
メソッドを使用する以下の簡単な省略形です:
Container.Bind<IFoo>().To<Foo>().AsSingle().When(context => context.ObjectType == typeof(Bar));
InjectContextクラス(上記の context
パラメータとして渡されます)には、あなたの条件で使用できる以下の情報が含まれています:
-
Type ObjectType
- 依存関係を注入している、新しくインスタンス化されたオブジェクトの型。 これは、Resolve <>
またはInstantiate <>
へのルート呼び出しではnullであることに注意してください -
object ObjectInstance
- 依存関係が満たされている、新しくインスタンス化されたインスタンス。 これは、フィールドを注入する場合、または[Inject]
メソッドにのみ使用でき、コンストラクタパラメータにはnullがあることに注意してください -
string Identifier
- ほとんどの場合、これはnullになり、[Inject]
属性のパラメータとして与えられるものに設定されます。 例えば、[Inject(Id =" foo ")] _foo
は、 -
object ConcreteIdentifier
- ほとんどの場合、これはnullになり、AsSingle
メソッドの識別子として与えられたものに設定されます。 -
string MemberName
- インジェクションするフィールドまたはパラメータの名前。 これは、たとえば、文字列であるコンストラクタパラメータが複数ある場合などに使用できます。 ただし、パラメータまたはフィールド名を使用すると、他のプログラマが別の名前を使用するようにリファクタリングする可能性があるため、エラーが発生しやすくなります。 多くの場合、明示的な識別子を使用する方がよい -
Type MemberType
- インジェクションするフィールドまたはパラメータの型。 -
InjectContext ParentContext
- これには、作成中の現在のクラスに先行するオブジェクトグラフ全体に関する情報が含まれます。 たとえば、Bのインスタンスが必要な依存関係Aが作成される可能性があります。このフィールドを使用して、Aに関するいくつかの条件に基づいて異なる値をCに注入できます。これは非常に複雑な 親コンテキスト情報の任意の組み合わせを使用する条件。ParentContext.MemberType
はObjectTypeと必ずしも同じではないことに注意してください。なぜなら、ObjectTypeはParentContext.MemberType
-
bool Optional
-[InjectOptional]
パラメータが注入されているフィールドに宣言されている場合は真
List Bindings
Zenjectは、同じ型の複数のバインディングを検出すると、それをリストと解釈します。 したがって、以下のサンプルコードでは、BarはFoo1、Foo2、およびFoo3の新しいインスタンスを含むリストを取得します。
// In an installer somewhere
Container.Bind<IFoo>().To<Foo1>().AsSingle();
Container.Bind<IFoo>().To<Foo2>().AsSingle();
Container.Bind<IFoo>().To<Foo3>().AsSingle();
...
public class Bar
{
public Bar(List<IFoo> foos)
{
}
}
注目すべき点は、IFooの単一の依存関係(以下のBarなど)を宣言して複数のバインディングがある場合、ZenjectはIFooのどのインスタンスを使用するかを知らないため、例外をスローします。
public class Bar
{
public Bar(IFoo foo)
{
}
}
また、空のリストが有効な場合は、Listコンストラクタのパラメータ(または[Inject]フィールド)をオプションとしてマークする必要があります(詳細はこちらを参照してください)。
Global Bindings Using Project Context
これはすべて個々のシーンに適していますが、すべてのシーンで永続的に存続したい場合はどうしたらいいですか? Zenjectでは、ProjectContextオブジェクトにインストーラを追加することでこれを行うことができます。
これを行うには、まずProjectContext用のプレハブを作成し、それにインストーラを追加する必要があります。 Edit - > Zenject - > Create Project Context
メニュー項目を選択すると、これを最も簡単に行うことができます。 Assets / Resources
フォルダに 'ProjectContext'という名前の新しいアセットが表示されます。
これをクリックすると、 SceneContext
のインスペクタとほぼ同じように表示されます。このプレハブを設定する最も簡単な方法は、一時的にシーンに追加し、インストーラを追加してから、「適用」をクリックしてプレハブに保存してからシーンから削除することです。インストーラーに加えて、独自のカスタムMonoBehaviourクラスをProjectContextオブジェクトに直接追加することもできます。
次に、 SceneContext
を含むシーンを開始すると、 ProjectContext
オブジェクトが常に最初に初期化されます。ここに追加したインストーラーはすべて実行され、その中に追加したバインディングはプロジェクト内のすべてのシーンで使用できるようになります。 ProjectContext
ゲームオブジェクトはシーンを変更するときに破壊されないように DontDestroyOnLoad として設定されています。
これは一度だけ発生することにも注意してください。最初のシーンから別のシーンをロードすると、ProjectContextは再び呼び出されず、以前に追加したバインディングは新しいシーンに保持されます。 IInitializable.Initialize
が各プレイセッションで一度だけ呼び出されるようにシーンインストーラと同じように ITickable
/ IInitializable
/ IDisposable
オブジェクトをグローバルインストーラで宣言することができます。 IDisposable .Dispose
は、アプリケーションが完全に停止すると呼び出されます。
グローバルインストーラに追加するすべてのバインディングが個々のシーン内の任意のクラスで使用できる理由は、各シーンのコンテナがProjectContextコンテナを「親」として使用するためです。ネストされたコンテナの詳細については、こちらを参照してください。
ProjectContextは、シーン間で永続化したいオブジェクトを配置するのに非常に便利な場所です。しかし、すべてのシーンに完全にグローバルであるという事実は、意図しない振る舞いにつながる可能性があります。たとえば、Zenjectを使用するシンプルなテストシーンを記述しても、ProjectContextがロードされますが、これはあなたが望むものではない可能性があります。これらの問題に対処するには、代わりにScene Parentingを使用する方が良いでしょう。そのアプローチでは、同じ共通バインディングを継承するシーンを選択することができます。このアプローチの詳細については、こちらを参照してください。
Identifiers
同じタイプの別個のバインディングを持つ必要がある場合にはバインディングにIDを渡すことができ、 `List <> 'という結果にならないようにすることもできます。 例えば:
Container.Bind<IFoo>().WithId("foo").To<Foo1>().AsSingle();
Container.Bind<IFoo>().To<Foo2>().AsSingle();
...
public class Bar1
{
[Inject(Id = "foo")]
IFoo _foo;
}
public class Bar2
{
[Inject]
IFoo _foo;
}
この例では、 Bar1
クラスは Foo1
のインスタンスを与えられ、 Bar2
クラスは Foo2
にバインドされたデフォルトバージョンの IFoo
を使用します。
constructor / inject-method引数についても同様のことができることに注意してください。
public class Bar
{
Foo _foo;
public Bar(
[Inject(Id = "foo")]
Foo foo)
{
}
}
I多くの場合、IDは文字列として作成されますが、実際にこのようなタイプを使用することは可能です。 たとえば、代わりに列挙型を使用すると便利な場合があります。
enum Cameras
{
Main,
Player,
}
Container.Bind<Camera>().WithId(Cameras.Main).FromInstance(MyMainCamera);
Container.Bind<Camera>().WithId(Cameras.Player).FromInstance(MyPlayerCamera);
Equals
演算子を実装している限り、カスタム型を使用することもできます。
Scriptable Object Installer
独自のインストーラを実装する際の MonoInstallerまたはインストーラから派生するの別の代替方法は、ScriptableObjectInstallerクラスから派生させることです。これは、ゲーム設定を保存するために最も一般的に使用されます。このアプローチには次の利点があります。
- プレイモードを停止すると、インストーラのプロパティに加えた変更はそのまま残ります。これは、ランタイムパラメータを調整するときに非常に便利です。シーン内の他のインストーラタイプおよびMonoBehaviourについては、再生モードが停止すると、実行時のインスペクタプロパティの変更は取り消されます。ただし、注意すべき点があります。これらの設定の変更は、MonoInstallerの設定とは異なり、永続的に保存されます。したがって、このルートに行く場合は、すべての設定オブジェクトを読み取り専用として扱う必要があります。
- 同じインストーラの複数のインスタンスを簡単に交換することができます。たとえば、以下の例を使用すると、
GameSettingsEasy
と呼ばれるGameSettingsInstaller
のインスタンスとGameSettingsHard
と呼ばれるインスタンスを持つことができます。
Example:
*オープンユニティ
- [プロジェクト]タブのどこかを右クリックし、[作成 - > Zenject - > ScriptableObjectInstaller`を選択します。
- GameSettingsInstallerという名前を付けてください
- 同じ場所でもう一度右クリック
- 新しく追加されたメニュー項目
Create - > Installers - > GameSettingsInstaller
を選択します - ここで概説されている設定へのアプローチに続いて、それを次のものに置き換えることができます:
public class GameSettings : ScriptableObjectInstaller
{
public Player.Settings Player;
public SomethingElse.Settings SomethingElse;
// ... etc.
public override void InstallBindings()
{
Container.BindInstance(Player);
Container.BindInstance(SomethingElse);
// ... etc.
}
}
public class Player : ITickable
{
readonly Settings _settings;
Vector3 _position;
public Player(Settings settings)
{
_settings = settings;
}
public void Tick()
{
_position += Vector3.forward * _settings.Speed;
}
[Serializable]
public class Settings
{
public float Speed;
}
}
- これで、ゲームを実行し、実行時にGameSettingsInstallerアセットにあるSpeed値を調整し、その変更を永久に保存できるようになります
Runtime Parameters For Installers
多くの場合、他のインストーラからインストーラを呼び出すときは、パラメータを渡すことができることが望ましいです。 ランタイムパラメータの型で使用しているインストーラベースクラスの汎用引数を追加することで、これを行うことができます。 たとえば、MonoBehaviour以外のインストーラを使用する場合:
public class FooInstaller : Installer<string, FooInstaller>
{
string _value;
public FooInstaller(string value)
{
_value = value;
}
public override void InstallBindings()
{
...
Container.BindInstance(_value).WhenInjectedInto<Foo>();
}
}
public class MainInstaller : MonoInstaller
{
public override void InstallBindings()
{
FooInstaller.Install(Container, "asdf");
}
}
または、MonoInstallerプレハブを使用する場合:
public class FooInstaller : MonoInstaller<string, FooInstaller>
{
string _value;
// Note that in this case we can't use a constructor
[Inject]
public void Construct(string value)
{
_value = value;
}
public override void InstallBindings()
{
...
Container.BindInstance(_value).WhenInjectedInto<Foo>();
}
}
public class MainInstaller : MonoInstaller
{
public override void InstallBindings()
{
// For this to work, there must be a prefab with FooInstaller attached to it at
// Resources/My/Custom/ResourcePath.prefab
FooInstaller.InstallFromResource("My/Custom/ResourcePath", Container, "asdf");
// In this case the prefab will be assumed to exist at 'Resources/Installers/FooInstaller'
// FooInstaller.InstallFromResource(Container, "asdf");
}
}
または、ScriptableObjectInstallerを使用して:
public class FooInstaller : ScriptableObjectInstaller<string, FooInstaller>
{
string _value;
// Note that in this case we can't use a constructor
[Inject]
public void Construct(string value)
{
_value = value;
}
public override void InstallBindings()
{
...
Container.BindInstance(_value).WhenInjectedInto<Foo>();
}
}
public class MainInstaller : MonoInstaller
{
public override void InstallBindings()
{
// For this to work, there must be an instance of FooInstaller added at
// Resources/My/Custom/ResourcePath.asset
FooInstaller.InstallFromResource("My/Custom/ResourcePath", Container, "asdf");
// In this case the FooInstaller asset will be assumed to exist at 'Resources/Installers/FooInstaller'
// FooInstaller.InstallFromResource(Container, "asdf");
}
}
Commands And Signals
See here.
Creating Objects Dynamically Using Factories
See here.
Update / Initialization Order
多くの場合、特に小規模プロジェクトの場合、クラスが更新または初期化する順序は重要ではありません。 しかし、大規模なプロジェクトでは、更新や初期化の順序が問題になります。 Start()、Awake()、またはUpdate()メソッドがどのような順序で呼び出されるかを予測することは難しいため、これは特にUnityの問題です。残念ながらUnityはこれを簡単に制御する方法はありません ( Edit -> Project Settings -> Script Execution Order
のほかに、使用するのが面倒かもしれませんが)
Zenjectでは、デフォルトでITickablesとIInitializeableが追加された順番で呼び出されますが、更新や初期化の順序が重要な場合は、もっと良い方法があります。インストーラで優先順位を明示的に指定する。 たとえば、サンプルプロジェクトでは、シーンインストーラで次のコードを見つけることができます。
public class AsteroidsInstaller : MonoInstaller
{
...
void InitExecutionOrder()
{
// In many cases you don't need to worry about execution order,
// however sometimes it can be important
// If for example we wanted to ensure that AsteroidManager.Initialize
// always gets called before GameController.Initialize (and similarly for Tick)
// Then we could do the following:
Container.BindExecutionOrder<AsteroidManager>(-10);
Container.BindExecutionOrder<GameController>(-20);
// Note that they will be disposed of in the reverse order given here
}
...
public override void InstallBindings()
{
...
InitExecutionOrder();
...
}
}
このようにして、予期せぬオーダー依存関係のためにプロジェクト終了時に壁に衝突することはありません。
ここで、BindExecutionOrderに与えられた値は、ITickable / IInitializableとIDisposable(IDisposableの順序を逆にして)に適用されます。
次のように、特定のインターフェイスごとに優先順位を割り当てることもできます。
Container.BindInitializableExecutionOrder<Foo>(-10);
Container.BindInitializableExecutionOrder<Bar>(-20);
Container.BindTickableExecutionOrder<Foo>(10);
Container.BindTickableExecutionOrder<Bar>(-80);
優先度を割り当てられていないITickables、IInitializeable、または IDisposable
には自動的に0の優先度が与えられます。 これにより、明示されていないクラスの前後に明示的な優先度を持つクラスを実行させることができます。 例えば、上記のコードでは、 'Foo.Initialize'が 'Bar.Initialize'の前に呼び出されます。
Zenject Order Of Operations
Zenject駆動アプリケーションは、次の手順で実行されます。
- Unity Awake() phase begins
- SceneContext.Awake()メソッドが呼び出されます。これはあなたのシーンで最初に実行されるべきものです。デフォルトでこの方法で動作するはずです(特に気付いている場合は、こちらを参照してください)。
- これがこのプレイセッション中にロードされる最初のシーンである場合、SceneContextはProjectContextプレハブを作成します。 ProjectContextが前のシーンで既に作成されている場合は、このステップをスキップして、SceneContextを直接初期化します
- ProjectContextは、Unity Inspectorを介してプレハブに追加されたすべてのインストーラを反復し、DiContainerを指すようにそれらを更新し、それぞれにInstallBindings()を呼び出します。各インストーラは、DiContainer上でいくつかのBind <>メソッドを呼び出します。
- 次に、ProjectContextは、そのゲームオブジェクトに添付されているすべてのMonoBehavioursとその子オブジェクトを注入します
- ProjectContextは、ITickable / IInitializableまたはIDisposableから派生したクラスと、
NonLazy()
バインディングで追加されたクラスを含む、すべての非遅延オブジェクトを構築します。 - SceneContextは、Unity Inspectorを介して追加されたすべてのインストーラを繰り返し、DiContainerを指すようにそれらを更新し、それぞれのInstallBindings()を呼び出します。各インストーラは、DiContainer上でいくつかのBind <>メソッドを呼び出します。
- SceneContextは、シーン内のすべてのオブジェクトを注入します(ProjectContextを親とするオブジェクトを除く)
- SceneContextは、ITickable / IInitializableやIDisposableから派生したクラスと、
NonLazy()
バインディングで追加されたクラスを含む、すべての非遅延オブジェクトを構築します。 - 必要な依存関係を解決できない場合は、ZenjectResolveExceptionがスローされます。
- シーン内の他のMonoBehaviourには、Awake()メソッドがあります。
- Unity Start() phase begins
- ProjectContext.Start()メソッドが呼び出されます。 これにより、すべての
IInitializable
オブジェクトのInitialize()メソッドが、ProjectContextインストーラで指定された順序で起動されます。 - SceneContext.Start()メソッドが呼び出されます。 これは、すべての
IInitializable
オブジェクトのInitialize()メソッドを、SceneContextインストーラで指定された順序で起動します。
*シーン内の他のMonoBehaviourにはStart()メソッドがあります
- ProjectContext.Start()メソッドが呼び出されます。 これにより、すべての
- Unity Update() phase begins
- ProjectContext.Update()が呼び出され、TickickがすべてのITickableオブジェクトに対して呼び出されます(ProjectContextインストーラーで指定された順序で)
- SceneContext.Update()が呼び出され、TickContentがすべてのITickableオブジェクトに対して呼び出されます(SceneContextインストーラーで指定された順序で)
- シーン内の他のMonoBehaviourにはUpdate()メソッドがあります
- これらの同じ手順をLateUpdateとILateTickableに対して繰り返します
- 同時に、物理的タイムステップに従ってFixedUpdateに対して同じ手順を繰り返します
- ユニティシーンはアンロードされます
- Dispose()は、SceneContextインストーラ内の
IDisposable
にマップされたすべてのオブジェクトに対して呼び出されます(詳細はここを参照してください)
- Dispose()は、SceneContextインストーラ内の
- アプリは終了しました
- Dispose()は、ProjectContextインストーラ内の
IDisposable
にマップされたすべてのオブジェクトに対して呼び出されます(詳細は、ここを参照)
- Dispose()は、ProjectContextインストーラ内の
Injecting data across scenes
場合によっては、あるシーンから別のシーンに引数を渡すと便利です。 Unityがこれをデフォルトで行うことを可能にする方法はかなり厄介です。 あなたのオプションは、永続的なGameObjectを作成し、DontDestroyOnLoad()を呼び出してシーンを変更したときにその状態を維持したり、グローバル静的クラスを使用してデータを一時的に保存したりすることです。
次のシーンに「レベル」の文字列を指定するふりをしましょう。 入力が必要な次のクラスがあります。
public class LevelHandler : IInitializable
{
readonly string _startLevel;
public LevelHandler(
[InjectOptional]
string startLevel)
{
if (startLevel == null)
{
_startLevel = "default_level";
}
else
{
_startLevel = startLevel;
}
}
public void Initialize()
{
...
[Load level]
...
}
}
LevelHandler
を含むシーンを読み込み、以下の構文を使って特定のレベルを指定することができます:
public class Foo
{
readonly ZenjectSceneLoader _sceneLoader;
public Foo(ZenjectSceneLoader sceneLoader)
{
_sceneLoader = sceneLoader;
}
public void AdvanceScene()
{
_sceneLoader.LoadScene("NameOfSceneToLoad", LoadSceneMode.Single, (container) =>
{
container.BindInstance("custom_level").WhenInjectedInto<LevelHandler>();
});
}
}
ラムダ内でここに追加するバインディングは、新しいシーンのインストーラ内にあるかのようにコンテナに追加されます。
シーンを直接実行することもできます。その場合は、デフォルトで「default_level」を使用します。 これは、 InjectOptional
フラグを使用しているために可能です。
代わりに、これを行うためのもっとクリーンな方法は、 LevelHandler
クラスではなく、インストーラ自体をカスタマイズすることです。 この場合、 [InjectOptional]
フラグを付けずに LevelHandler
クラスを書くことができます。
public class LevelHandler : IInitializable
{
readonly string _startLevel;
public LevelHandler(string startLevel)
{
_startLevel = startLevel;
}
public void Initialize()
{
...
[Load level]
...
}
}
次に、私たちのシーンのインストーラでは、以下を含めることができます:
public class GameInstaller : Installer
{
[InjectOptional]
public string LevelName = "default_level";
...
public override void InstallBindings()
{
...
Container.BindInstance(LevelName).WhenInjectedInto<LevelHandler>();
...
}
}
次に、直接LevelHandlerに注入するのではなく、インストーラに注入することができます。
public class Foo
{
readonly ZenjectSceneLoader _sceneLoader;
public Foo(ZenjectSceneLoader sceneLoader)
{
_sceneLoader = sceneLoader;
}
public void AdvanceScene()
{
_sceneLoader.LoadScene("NameOfSceneToLoad", (container) =>
{
container.BindInstance("custom_level").WhenInjectedInto<GameInstaller>();
});
}
}
ZenjectSceneLoader
クラスは、シーンを現在のシーンの「子」としてロードするなど、より複雑なシナリオも可能にします。これにより、新しいシーンは現在のシーンのすべての依存関係を継承します。 しかし、代わりに、これに代わり「Scene Contract Names」を使用するほうがよい場合があります。 詳細については、こちらをご覧ください。
Scene Parenting Using Contract Names
ProjectContextの中にバインディングを置くことは、シーン全体で共有される共通の長期的な依存関係を素早く簡単に追加する方法です。しかし、多くの場合、特定のシーン間でのみ共有したいバインディングがあるため、ProjectContextを使用すると機能しません。その場合、そこに追加するバインディングはプロジェクト全体のすべての単一シーンに共通しているからです。
一例として、私たちが宇宙船に取り組んでいるというふりをして、環境(惑星、小惑星、星などを含む)として役立つ1つのシーンを作成し、それを表現する別のシーンを作成したい船のシーンのすべてのクラスが環境シーンで宣言されたバインディングを参照できるようにします。また、船のシーンと環境のシーンの両方の複数の異なるバージョンを定義できるようにしたいと考えています。このすべてを実現するために、「Scene Contract Names」というZenject機能を使用します。
まず、multi-scene edittingのUnityのサポートを使用し、環境シーンと船のシーンの両方をドラッグしてScene Heirarchy]タブをクリックします。次に、環境シーンでSceneContextを選択し、「Contract Name」を追加します。それを「環境」と呼んでみましょう。そして、今私たちがしなければならないことは、船のシーンの中のSceneContextを選択し、その「Parent Contract Name」を同じ値( 'Environment')に設定することです。ここでplayを押すと、船のシーンのすべてのクラスが環境シーンの宣言されたバインディングにアクセスできます。
シーン名を明示的に使用するのではなく、ここで名前フィールドを使用する理由は、さまざまな実装のためにさまざまな環境シーンを交換することをサポートすることです。この例では、同じ契約名「環境」を使用して複数の異なる環境を定義することができます。そのため、シーンの階層構造にドラッグして再生するだけで、さまざまな船のシーンと簡単にミックスできます。
すべての環境シーンが船のシーンによって特定の「Contract」に従うことが予想されるため、「Contrac Name」と呼ばれます。例えば、船の場面には、どの環境シーンがロードされたかにかかわらず、船が避けなければならない小惑星のリストを含む「AsteroidManager」のためのバインディングが必要であるかもしれません。
これが動作するには、環境シーンと船のシーンを同時にロードする必要はありません。たとえば、環境内に埋め込まれたメニューを使用して、開始前に船を選択できるようにすることができます。だから、メニューシーンを作成し、それを環境シーンの後にロードすることができます。ユーザーが船を選ぶと、「SceneManager.LoadScene」という単体メソッド(LoadSceneMode.Additiveを使用していることを確認してください)を呼び出して、関連する船のシーンをロードすることができます。
また、Validateコマンドを使用して、さまざまなマルチシーン設定を素早く確認することもできます。
また、Unityには現在、マルチシーンの設定を保存および復元するための組み込みの方法がないということを言及する必要があります。興味のある方は、こちらをご覧ください。
Scene Decorators
シーンデコレータは、上述したシーンの子育てに加えて、複数のシーンをzenjectと共に使用する別のアプローチを提供します。違いは、シーンデコレータでは、問題の複数のシーンがすべて同じコンテナを共有するため、すべてのシーンが他のすべてのシーンのバインディングにアクセスできることです(シーンバインディングとは異なり、親バインディングのみにアクセスできます)。
シーンデコレータについて考えるもう一つの方法は、シーン間でデータを注入するために説明したプロセスをより高度な方法で実行することです。つまり、シーン内のインストーラを実際に変更せずに別のシーンに動作を追加することができます。
通常、条件に応じて特定のシーンに対して異なる動作をカスタマイズする場合は、MonoInstallersのbooleanまたはenumプロパティを使用し、設定された値に応じて異なるバインディングを追加します。しかし、シーンデコレータのアプローチは、メインシーンを変更する必要がないため、時にはよりクリーンにすることができます。
たとえば、テストの目的で、メインのプロダクションシーンに特別なキーボードショートカットを追加したいとします。デコレータを使用してこれを行うには、次のようにします。
- 主要な制作シーンを開く
- シーン階層内のシーン名の横にある右端のメニューを右クリックし、新しいシーンを追加を選択します
- シーンをメインシーンの上にドラッグする
- 新しいシーンの中を右クリックし、
Zenject - > Decorator Context
を選択します。 - デコレータコンテキストを選択し、「デコレート契約名」フィールドを「メイン」に設定します。
- メインシーンでSceneContextを選択し、同じ値( 'Main')を持つコントラクト名を追加します
- 次の内容の新しいC#スクリプトを作成し、このMonoBehaviourをgameObjectとしてデコレータシーンに追加し、それを
SceneDecoratorContext
のInstallers
プロパティにドラッグします
public class ExampleDecoratorInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container.Bind<ITickable>().To<TestHotKeysAdder>().AsSingle();
}
}
public class TestHotKeysAdder : ITickable
{
public void Tick()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("Hotkey triggered!");
}
}
}
シーンを実行すると、デコレータインストーラに追加された機能を除いて、メインシーンとまったく同じように動作するはずです。 また、ここには示されていませんが、両方のシーンがすべて同じシーンにあるかのように、お互いのバインディングにアクセスできます。
また、Validateコマンド(CTRL + SHIFT + V)を使用して、異なるマルチシーン設定を素早く確認することもできます。
また、デコレータシーンは、装飾しているシーンの前にロードする必要があります。
また、Unityには現在、マルチシーンの設定を保存および復元するための組み込みの方法がないということを言及する必要があります。 興味のある方は、こちらをご覧ください。
Sub-Containers And Facades
See here.
Writing Automated Unit Tests / Integration Tests
See here.
Non Generic bindings
場合によっては、コンパイル時にバインドする正確な型がわからないことがあります。 これらの場合、汎用パラメータの代わりに System.Type
値をとる Bind
メソッドのオーバーロードを使うことができます。
// These two lines will result in the same behaviour
Container.Bind(typeof(Foo));
Container.Bind<Foo>();
非ジェネリックバインディングを使用する場合は、複数の引数を渡すこともできます。
Container.Bind(typeof(Foo), typeof(Bar), typeof(Qux)).AsSingle();
// The above line is equivalent to these three:
Container.Bind<Foo>().AsSingle();
Container.Bind<Bar>().AsSingle();
Container.Bind<Qux>().AsSingle();
同じことがToメソッドにも当てはまります:
Container.Bind<IFoo>().To(typeof(Foo), typeof(Bar)).AsSingle();
// The above line is equivalent to these two:
Container.Bind<IFoo>().To<Foo>().AsSingle();
Container.Bind<IFoo>().To<Bar>().AsSingle();
両方を行うこともできます:
Container.Bind(typeof(IFoo), typeof(IBar)).To(typeof(Foo1), typeof(Foo2)).AsSingle();
// The above line is equivalent to these:
Container.Bind<IFoo>().To<Foo>().AsSingle();
Container.Bind<IFoo>().To<Bar>().AsSingle();
Container.Bind<IBar>().To<Foo>().AsSingle();
Container.Bind<IBar>().To<Bar>().AsSingle();
これは、複数のインタフェースを実装するクラスを持つ場合に特に便利です。
Container.Bind(typeof(ITickable), typeof(IInitializable), typeof(IDisposable)).To<Foo>().AsSingle();
この特定の例では、既にこのための組み込みショートカットメソッドがありますが
Container.BindAllInterfaces<Foo>().To<Foo>().AsSingle();
Convention Based Binding
条約に基づく拘束力は、以下のシナリオのいずれかに役立ちます。
- クラスがコンテナにどのようにバインドされているかを決定する命名規則を定義したい(例えば、接頭辞、接尾辞、または正規表現を使用する)
- カスタム属性を使用して、クラスがコンテナにバインドされる方法を決定する
- 指定された名前空間またはアセンブリ内の特定のインターフェイスを実装するすべてのクラスを自動的にバインドする
"convention over configuration"を使うと、インストーラ内のすべてのバインディングを明示的に追加するのではなく、他のプログラマがすばやく簡単に作業を進めるために使用できるフレームワークを定義できます。 これは、Ruby on Rails、ASP.NET MVCなどのフレームワークの後に続く哲学です。もちろん、このアプローチには長所と短所の両方があります。
それらは Bind()
と To()
に型のリストを与える代わりに、非汎用バインディングと同様の方法で指定されます。 メソッドについては、Fluent APIを使用して規約を説明します。 例えば、 IFoo
をコードベース全体で実装するすべてのクラスにバインドするには:
Container.Bind<IFoo>().To(x => x.AllTypes().DerivingFrom<IFoo>());
Bind()
メソッドでも同じFluent APIを使うことができます。また、 Bind()
と To()
の両方で同時に使うこともできます。
より多くの例については、下記の例のセクションをご覧ください。 完全な形式は次のとおりです。
x.InitialList().Conditional().AssemblySources()
Where:
-
InitialList = バインディングに使用する型の初期リスト。 このリストは、指定された条件付きでフィルタリングされます。 それは、以下の(かなり自明の)方法の1つです。
- AllTypes
- AllNonAbstractClasses
- AllAbstractClasses
- AllInterfaces
- AllClasses
-
Conditional = InitialListによって与えられた型のリストに適用するフィルタ。 これらのうちの多くを必要に応じて連鎖させることができ、すべてが順番に初期リストに適用されることに注意してください。 次のいずれかになります。
-
DerivingFrom -
T
から派生するマッチタイプのみ -
DerivingFromOrEqual -
T
から派生するかまたは等しいマッチタイプのみ -
WithPrefix(value) -
value
で始まる名前の一致する型のみ -
WithSuffix(value) -
value
で終わる名前の一致する型だけです -
WithAttribute - クラス宣言の上に
[T]
属性を持つ型だけをマッチさせます -
WithoutAttribute - クラス宣言の上に
[T]
属性を持たない型にのみマッチする -
WithAttributeWhere (述部) - クラス宣言の上に
[T]
属性を持ち、与えられた述部が属性を渡すときにtrueを返す型にのみマッチします。これは、属性に与えられたデータを使用してバインディングを作成できるので便利です - InNamespace(value) - 指定された名前空間にある一致タイプのみ
- InNamespaces(value1、value2など) - 指定された名前空間のいずれかに一致する型のみ
- MatchingRegex(pattern) - 指定された正規表現に一致するタイプのみマッチする
-
Where(述語) - 最後に、
Type
パラメータを取る述語を渡すことによって、任意の種類の条件付きロジックを追加することもできます
-
DerivingFrom -
-
AssemblySources = InitialListを取り込むときに型を検索するアセンブリのリスト。 次のいずれかになります。
- FromAllAssemblies - ロードされたすべてのアセンブリのタイプを検索します。 指定されていない場合のデフォルトです
-
FromAssemblyContaining - 型
T
が入っているアセンブリの型を調べます - FromAssembliesContaining(type1、type2、..) - 指定された型のいずれかを含むすべてのアセンブリの型をルックアップします
- FromThisAssembly - このメソッドを呼び出すアセンブリでのみ型を検索します
- FromAssembl(assembly) - 指定されたアセンブリ内でのみタイプを検索する
- FromAssemblies(assembly1、assembly2、...) - 指定されたアセンブリ内でのみ型を検索する 1. FromAssembliesWhere(predicate) - 指定された述部と一致するすべてのアセンブリの型を検索します
同じバインディングで、以下の条件を組み合わせることができます。 ここではアセンブリを指定していないので、Zenjectはロードされたすべてのアセンブリ内を検索します。
-
IFooをコードベース全体で実装するすべてのクラスにバインドします。
Container.Bind<IFoo>().To(x => x.AllTypes().DerivingFrom<IFoo>());
これも同じ結果になることに注意してください:
Container.Bind<IFoo>().To(x => x.AllNonAbstractTypes());
Zenjectは、具体的な型が実際に基底型から派生していないバインディングをスキップするためです。 この場合、IFooを自分自身にバインドしないために、AllTypesの代わりにAllNonAbstractTypesを使用するようにする必要があります
-
指定された名前空間内でそれを実装するすべてのクラスにインターフェイスをバインドする
Container.Bind<IFoo>().To(x => x.AllTypes().DerivingFrom<IFoo>().InNamespace("MyGame.Foos"));
-
接尾辞 "Controller"を持つすべてのクラス(
ASP.NET MVCで行われているように)
IController`を自動バインドします:Container.Bind<IController>().To(x => x.AllNonAbstractTypes().WithSuffix("Controller"));
MatchingRegex
を使ってこれを行うこともできます:Container.Bind<IController>().To(x => x.AllNonAbstractTypes().MatchingRegex("Controller$"));
-
接頭辞「ウィジェット」ですべてのタイプをバインドし、Fooに注入する
Container.Bind<object>().To(x => x.AllNonAbstractTypes().WithPrefix("Widget")).WhenInjectedInto<Foo>();
-
指定された名前空間内のすべての型によって使用されるインターフェイスを自動バインドする
Container.Bind(x => x.AllInterfaces()) .To(x => x.AllNonAbstractClasses().InNamespace("MyGame.Things"));
これは、名前空間 "MyGame.Things"のすべての型に対して、
Container.BindAllInterfaces <T>()。To <T>()
を呼び出すのと同じです。 上で触れたように、Zenjectは、具体的な型が実際に基底型から派生していないバインディングをスキップするため、これが機能します。 したがって、ロードされたすべてのアセンブリのすべての単一インタフェースに一致するAllInterfaces
を使用していても、このインタフェースを実装しない型にインタフェースをバインドしようとしないため、これは問題ありません。
Unbind / Rebind
別のバインドステートメントで追加されたバインディングを削除または置換することもできます。
-
Unbind - コンテナーからバインディングを削除します。
Container.Bind<IFoo>().To<Foo>(); // This will nullify the above statement Container.Unbind<IFoo>();
-
Rebind -既存のバインディングを新しいもので上書きする。 これは、指定された型でunbindを呼び出し、その後すぐにbindを呼び出すのと同じです。
Container.Bind<IFoo>().To<Foo>(); // Container.Rebind<IFoo>().To<Bar>();
Singleton Identifiers
通常の識別子に加えて、特定のシングルトンに識別子を割り当てることもできます。
これにより、Zenjectは To <>
メソッドのジェネリック引数として与えられた型に基づいてシングルトンが一意に識別されるため、複数のシングルトンを1つではなく複数作成することができます。 したがって、たとえば:
Container.Bind<IFoo>().To<Foo>().AsSingle();
Container.Bind<IBar>().To<Foo>().AsSingle();
Container.Bind<IQux>().To<Qux>().AsSingle();
上記のコードでは、IFooとIBarの両方が同じインスタンスにバインドされます。 Fooのインスタンスは1つだけ作成されます。
Container.Bind<IFoo>().To<Foo>().AsSingle("foo1");
Container.Bind<IBar>().To<Foo>().AsSingle("foo2");
ただし、この場合、2つのインスタンスが作成されます。
これのもう1つの使用例は、同じプレハブから複数のシングルトンを作成できるようにすることです。 たとえば、次のように指定します。
Container.Bind<Foo>().FromPrefab(MyPrefab).AsSingle();
Container.Bind<Bar>().FromPrefab(MyPrefab).AsSingle();
Singletonは FromPrefab
を使用するときにプレハブによってのみ識別されるので、プレハブMyPrefabを一度インスタンス化します。 与えられた具体的なタイプは、 "このコンポーネントのインスタンス化されたプレハブを検索する"と解釈できます。 しかし、代わりにZenjectが FromPrefab
バインディングごとにプレハブの新しいインスタンスをインスタンス化するようにしたい場合は、次のように AsSingle
関数に識別子を渡すことで同様に行うことができます:
Container.Bind<Foo>().FromPrefab(MyPrefab).AsSingle("foo");
Container.Bind<Bar>().FromPrefab(MyPrefab).AsSingle("bar");
これで、プレハブの2つのインスタンスが作成されます。
Auto-Mocking using Moq
See here.
Creating Unity EditorWindow's with Zenject
独自のUnityプラグインを追加する必要があり、独自のEditorWindow派生クラスを作成したい場合は、Zenjectを使用してこのコードを管理することを検討することもできます。 どのようにこれを行うかの例を見てみましょう:
- プロジェクトビューのEditorフォルダの下を右クリックし、「Create」→「Zenject」→「Editor Window」を選択します。 それをTimerWindowと呼ぶことにしましょう。
- メニュー項目
Window - > TimerWindow
を選択して、新しいエディタウィンドウを開きます。 - 今は空ですので、いくつかのコンテンツを追加しましょう。 それを開いて内容を次のように置き換えます。
public class TimerWindow : ZenjectEditorWindow
{
TimerController.State _timerState = new TimerController.State();
[MenuItem("Window/TimerWindow")]
public static TimerWindow GetOrCreateWindow()
{
var window = EditorWindow.GetWindow<TimerWindow>();
window.titleContent = new GUIContent("TimerWindow");
return window;
}
public override void InstallBindings()
{
Container.BindInstance(_timerState);
Container.BindAllInterfaces<TimerController>().To<TimerController>().AsSingle();
}
}
class TimerController : IGuiRenderable, ITickable, IInitializable
{
readonly State _state;
public TimerController(State state)
{
_state = state;
}
public void Initialize()
{
Debug.Log("TimerController initialized");
}
public void GuiRender()
{
GUI.Label(new Rect(25, 25, 200, 200), "Tick Count: " + _state.TickCount);
if (GUI.Button(new Rect(25, 50, 200, 50), "Restart"))
{
_state.TickCount = 0;
}
}
public void Tick()
{
_state.TickCount++;
}
[Serializable]
public class State
{
public int TickCount;
}
}
ZenjectEditorWindowのInstallBindingsメソッドでは、シーン内と同じように、IInitializable、ITickable、およびIDisposableバインディングを追加できます。 Unityの即時モードguiを使用してウィンドウにコンテンツを描画するために使用できる IGuiRenderable
という新しいインタフェースもあります。
Unity内でコードが再度コンパイルされるたびに、エディタウィンドウがリロードされることに注意してください。 InstallBindingsが再び呼び出され、すべてのクラスが最初から作成されます。つまり、メンバ変数に格納されている状態情報はすべてリセットされます。しかし、EditorWindowの派生クラス自体のメンバフィールドは直列化されているので、これを利用して再コンパイル全体で状態を保持することができます。上記の例では、現在のティックカウントをSerializableクラスにラップし、これをEditorWindow内のメンバとして含めることで、現在のティックカウントを持続させることができます。
注目すべきことは、ITickable.Tickメソッドが呼び出される割合が、フォーカスしているものによって変化する可能性があることです。私たちのタイマーウィンドウを実行し、Unity以外の別のウィンドウを選択すると、私の意味を見ることができます。 (ティックカウントははるかにゆっくり増加する)
Frequently Asked Questions
-
これは過度なことではありませんか?つまり、静的にアクセス可能なシングルトンを実際に使っているのですか?
十分な規模のプロジェクトでは、グローバルシングルトンを使用するほうが簡単で複雑になるかもしれないと私は同意します。しかし、プロジェクトの規模が大きくなるにつれて、グローバルシングルトンを使用するとコードが扱いにくくなります。良いコードは、基本的に疎結合コードと同義であり、疎結合コードを書くには、クラスとコードの間の依存関係を実際に認識しておく必要があります(ただし、文字通りどこでもインターフェイスを使用するわけではありませんが、説明されている[ここ](#overusinginterfaces)
グローバルシングルトンを使用して(A)の点では、何が何に依存しているのか、そして時間の経過とともに、すべてがすべてに依存する傾向にあるので、あなたのコードは本当に複雑になるでしょう。コールスタックのどこかで、コードベースのどこかの他のクラスへのいくつかの礼儀要求をいつでもどこかで行うことができます。 (B)に関しては、あなたは常に具体的なクラスを指しているので、グローバルシングルトンとのインターフェイスを実際にコーディングすることはできません
DIフレームワークでは、(A)の点では、コンストラクタに必要な依存関係を宣言するのにもう少し作業が必要ですが、これはクラス間の依存関係を認識させるためにも良いことです。
そして、(B)の面では、インターフェイスにコード化するよう強制します。すべての依存関係をコンストラクタパラメータとして宣言することによって、基本的には「私がXを実行するためには、これらの契約が満たされている必要があります。これらのコンストラクタパラメータは実際にはインタフェースまたは抽象クラスではないかもしれませんが、抽象的な意味では依然として契約であり、クラス内で作成する場合やグローバルシングルトンを使用する場合とは異なります。
結果はより疎結合されたコードとなり、リファクタリング、メンテナンス、テスト、理解、再利用などを100倍容易にします。
-
これはiOSやWebGLなどのAOTプラットフォームで動作しますか?
はい。 しかし、あなたが知っておくべきことがいくつかあります。 UnityのIL2CPPコンパイラが行うことの1つは、使用されていないコードを取り除くことです。 使用法を見つけるために静的にコードを分析することによって、どのコードが使用されるかを計算します。 これは、明示的に使用されていないメソッド/タイプを見逃すことを除いて、素晴らしいことです。 特に、Zenjectを介してのみ作成されるクラスは、IL2CPPコンパイラによってコンストラクタが無視されます。 これに対処するために、時にはコンストラクタに適用される[Inject]属性は、コンストラクタをIL2CPPに自動的にマークして削除しないようにします。 言い換えれば、この問題を解決するには、WebGL / iOS用にコンパイルするときにZenjectで作成したすべてのコンストラクタを[Inject]属性でマークするだけです。
-
DIは、初期オブジェクトグラフを構築するときの起動時間に影響を与える可能性があります。 ただし、実行時に新しいオブジェクトをインスタンス化するたびに、パフォーマンスに影響を与えることもあります。
Zenjectは通常は遅いC#のリフレクションを使用しますが、Zenjectではこの作業がキャッシュされるため、パフォーマンスのヒットはクラスごとに1回しか発生しません。 言い換えれば、Zenjectは、パフォーマンスを向上させるためにパフォーマンスとメモリの間のトレードオフを行うことで、コストのかかるリフレクション動作を回避します。
Zenjectと他のDIフレームワークのベンチマークについては、hereを参照してください。
Zenjectはフレームごとのヒープ割り当てもゼロにする必要があります。
-
デフォルトでUnityは.NET Framework 3.5を使用しているので、Zenjectはこれがあなたが望むものと仮定します。 Zenjectをこれよりも大きなバージョンでコンパイルする場合はこれで問題ありませんが、Func.csの内容を削除またはコメントアウトする必要があります。
-
どのようにしてUnityスタイルのコルーチンを通常のC#クラスで使用できますか?
Zenjectでは、すべてのクラスを
MonoBehaviour
にする必要性は少なくなります。 しかし、非同期メソッドを追加するためにStartCoroutine
を呼び出すことができることがしばしば望まれます。ここでの解決策の1つは、専用のクラスを使用し、その代わりに
StartCoroutine
を呼び出すことです。 例えば:public class AsyncProcessor : MonoBehaviour { // Purposely left empty } public class Foo : IInitializable { AsyncProcessor _asyncProcessor; public Foo(AsyncProcessor asyncProcessor) { _asyncProcessor = asyncProcessor; } public void Initialize() { _asyncProcessor.StartCoroutine(RunAsync()); } public IEnumerator RunAsync() { Debug.Log("Foo started"); yield return new WaitForSeconds(2.0f); Debug.Log("Foo finished"); } } public class TestInstaller : MonoInstaller { public override void InstallBindings() { Container.Bind<IInitializable>().To<Foo>().AsSingle(); Container.Bind<AsyncProcessor>().FromGameObject().AsSingle(); } }
私が強くお勧めするこの問題のもう一つの解決策はUniRxです。
さらに別のオプションは、Unityが提供するものと同様の機能を実装するコルーチンライブラリを使用することです。 過去にModest Treeで使用した例は、hereを参照してください
-
メモリ割り当てを最小限に抑えるためにプールでZenjectを使用するにはどうすればいいですか?
現在、Zenjectはメモリプーリングをサポートしていません。 一時的に何かをバインドするかファクトリを使用する場合、Zenjectは常に新しいインスタンスを作成します。 多くのオブジェクト(特にモバイル上)を作成する場合、これが非効率的であるため、将来のバージョンで対処したいものです。
-
どのゲーム/ツール/ライブラリがZenjectを使用していますか?
Zenjectを使用している他のプロジェクトが分かっている場合は、このリストに追加できるようにここのコメントを追加してください。
Games
- Pokemon Go (both iOS and Android)
- Spinball Carnival (Android)
- Slugterra: Guardian Force (Android)
- Submarine (iOS and Android)
- NOVA Black Holes (iOS)
- Farm Away! (iOS and Android)
- Build Away! (iOS and Android)
Libraries
- EcsRx - A framework for Unity using the ECS pattern
- Karma - An MVC framework for Unity
- View Controller - A view controller system
Tools
- Modest 3D (WebGL, WebPlayer, PC) - An IDE to allow users to quickly and easily create procedural training content
- Modest 3D Explorer (WebGL, WebPlayer, iOS, Android, PC, Windows Store) - A simple editor to quickly create a 3D presentation with some number of slides
Cheat Sheet
See here.
Further Help
For general troubleshooting / support, please use the zenject subreddit or the zenject google group. If you have found a bug, you are also welcome to create an issue on the github page, or a pull request if you have a fix / extension. You can also follow @Zenject on twitter for updates. Finally, you can also email me directly at sfvermeulen@gmail.com
Release Notes
See here.
License
The MIT License (MIT)
Copyright (c) 2010-2015 Modest Tree Media http://www.modesttree.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.