Unityプロジェクトに一般的なゲームプログラミングデザインパターンを実装することで、クリーンで整理された読みやすいコードベースを効率的に構築・維持することができます。デザインパターンは、リファクタリングとテストの時間を短縮し、開発プロセスをスピードアップし、ゲーム、チーム、ビジネスを成長させるための強固な基盤に貢献します。
デザイン・パターンは、コードにコピー&ペーストできる完成されたソリューションとしてではなく、より大規模でスケーラブルなアプリケーションを構築するのに役立つ追加ツールとして考えてほしい。
このページでは、ファクトリーのデザインパターンについて説明します。
ここでの内容は無料電子書籍に基づいています、 ゲーム・プログラミング・パターンでコードをレベルアップしよう.
Unityゲームプログラミングデザインパターンシリーズの他の記事は、Unityベストプラクティスハブまたは以下のリンクからチェックしてください:
他のオブジェクトを作り出す特別なオブジェクトがあると便利なことがある。多くのゲームでは、ゲームプレイ中にさまざまなものが生まれてくるが、実行時に何が必要かは、実際に必要になるまでわからないことが多い。
ファクトリー・パターンは、この目的のためにファクトリーと呼ばれる特別なオブジェクトを指定する。一面では、"製品 "を生み出すために必要な細部の多くが凝縮されている。直接的なメリットは、コードがすっきりすることだ。
しかし、各製品が共通のインターフェイスや基本クラスに従っている場合、これをさらに一歩進めて、ファクトリー自体から隠して、独自の構築ロジックをより多く含ませることができる。こうして、新しいオブジェクトの作成がより拡張的になる。
ファクトリーをサブクラス化して、特定の商品専用の複数のファクトリーを作ることもできます。こうすることで、実行時に敵や障害物などを生成しやすくなる。
ファクトリーパターンを含む、ゲーム開発の文脈におけるさまざまなプログラミングデザインパターンを示すサンプルプロジェクトがGitHubで公開されている。
ファクトリーパターンのサンプルは、プレイヤーが迷路を移動するためのコードで構成されています。迷路の中では、クリックすることでプロダクトと呼ばれる2種類のGameObjectを生み出すことができる。どちらも同じインターフェイスを使い、同じような形をしているが、一方はパーティクルを生み、もう一方は音を奏でる。
ファクトリーパターンのシーンは、"6 Factory "と書かれたフォルダの中にある。
ゲームレベルのアイテムをインスタンス化するファクトリーパターンを作成したいとします。GameObjectを作るのにPrefabsを使うこともできますが、それぞれのインスタンスを作るときにカスタムビヘイビアを実行したいかもしれません。
このロジックを維持するためにif文やswitchを使うのではなく、コード例にあるようにIProductというインターフェースとFactoryという抽象クラスを作成する。
製品は、そのメソッドについて特定のテンプレートに従う必要があるが、それ以外の機能を共有することはない。したがって、IProductインターフェイスを定義することになる。
ファクトリーはいくつかの共有機能を必要とするかもしれないので、このサンプルでは抽象クラスを使っている。ただ、サブクラスを使用する際には、SOLIDの原則からリスコフ置換に留意すること。これは、スーパークラスのオブジェクトは、プログラムの正しさに影響を与えることなく、サブクラスのオブジェクトと置き換え可能でなければならない、というものである。言い換えれば、スーパークラスの参照を使用するプログラムは、それを知らなくてもそのサブクラスのいずれかを使用することができるはずである。
IProductインターフェイスは、製品間の共通点を定義します。この場合、単にProductNameプロパティがあり、Initialize で製品が実行するロジックがあります。
IProductインターフェイスに従う限り、必要な数の商品(ProductA、ProductBなど)を定義することができます。
ベース・クラスのFactory には、IProduct を返すGetProductメソッドがあります。抽象的なので、ファクトリーのインスタンスを直接作ることはできない。あなたはいくつかの具象サブクラス(ConcreteFactoryAと ConcreteFactoryB)を派生させ、実際に異なる製品を取得する。
この例のGetProductはVector3の位置を取るので、Prefab GameObjectをより簡単に特定の位置にインスタンス化できる。各コンクリート工場のフィールドには、対応するテンプレートのプレハブも格納される。
その結果、上の画像のような構造になる。
コードスニペットでは、サンプルProductAと ConcreteFactoryAを見ることができます。
ここでは、IProductを実装した商品クラスMonoBehavioursが、ファクトリー内のPrefabsを利用するようにしました。
各製品がそれぞれ独自のバージョンのInitializeを持つことができることに注意してください。ProductA Prefabの例にはParticleSystemが含まれており、ConcreteFactoryAがコピーをインスタンス化するときに再生されます。ファクトリー自体には、パーティクルをトリガーするための特定のロジックは含まれておらず、すべての製品に共通するInitializeメソッドを呼び出すだけです。
ClickToCreateコンポーネントがどのようにファクトリーを切り替えて、異なる動作をするProductAとProductBを作成するのか、サンプルプロジェクトをご覧ください。商品Bはスポーン時に音が鳴り、商品Aはパーティクル効果で商品バリエーションのコアコンセプトを表現しています。
多くの製品をセットアップする際、ファクトリーパターンが最も有効です。アプリケーションに新しい製品タイプを定義しても、既存の製品タイプを変更したり、以前のコードを修正したりする必要はありません。
各製品の内部ロジックを独自のクラスに分離することで、ファクトリーのコードを比較的短く保つことができる。各ファクトリーは、各製品に対してInitializeを呼び出すことしか知らない。
欠点は、パターンを実装するために多くのクラスやサブクラスを作成することだ。他のパターンと同様、これは若干のオーバーヘッドをもたらす。一方、クラスのセットアップに費やす最初の時間は、長い目で見れば、コードをデカップリングしてメンテナンスしやすくするという点で良いことかもしれない。
ファクトリーの実装は、ここに示したものとは大きく異なる可能性がある。独自のファクトリーパターンを構築する際には、以下の調整を考慮してください:
辞書を使って商品を検索する: 商品をキーと値のペアとして辞書に格納したい場合もあるでしょう。一意な文字列識別子(例えば、Nameや何らかのID)をキーとして、型を値として使用する。これにより、製品や対応する工場の検索がより便利になる。
工場(または工場長)を静的にする: これは使いやすくはなるが、追加のセットアップが必要になる。静的なクラスはインスペクタに表示されないので、商品のコレクションも静的にする必要があります。
GameObjectとMonoBehaviours以外に適用する: プレハブやユニティ専用のコンポーネントに限定しないでください。ファクトリーパターンはどんなC#オブジェクトでも動作する。
オブジェクト・プール・パターンと組み合わせる: ファクトリーは必ずしも新しいオブジェクトをインスタンス化したり、作成したりする必要はない。また、階層にある既存のものを検索することもできる。一度に多くのオブジェクトをインスタンス化する場合(武器の発射体など)、オブジェクト・プール・パターンを使用すると、メモリ管理がより最適化される。
工場は、ゲームプレイに必要な要素を必要に応じて生み出すことができる。しかし、多くの場合、製品を作ることだけが目的ではない。別の大きなタスクの一部としてファクトリーパターンを使用する場合があります(例えば、ゲームレベルの一部のダイアログボックスでUIエレメントを設定するなど)。
Unityアプリケーションでデザインパターンを使用する方法やSOLIDの原則については、無料の電子書籍「ゲームプログラミングパターンでコードをレベルアップ」で詳しく説明しています。
ベストプラクティスハブでは、Unityの高度なテクニカル電子書籍や記事をすべてご覧いただけます。電子書籍は、ドキュメンテーションの上級ベストプラクティスのページでもご覧いただけます。