マイクアクトンの3つの大きな嘘を読むことから始めることをお勧めします。私は真剣です、これはあなたがコードをデザインする方法を変えるでしょう:http : //cellperformance.beyond3d.com/articles/2008/03/three-big-lies.html
どちらに違反しますか?
嘘#3-コードはデータよりも重要
あなたは依存性注入について話しました。これは一部(および一部)のインスタンスで役立つ場合がありますが、特にゲーム開発で使用する場合は、常に大きな大きな警報音が鳴るはずです。どうして?それは、しばしば不必要な抽象化だからです。そして、間違った場所での抽象化は恐ろしいことです。だからあなたはゲームを持っています。ゲームには、さまざまなコンポーネントのマネージャーがあります。コンポーネントはすべて定義されています。したがって、マネージャーを「持っている」メインゲームループコードのどこかにクラスを作成します。お気に入り:
private CollissionManager _collissionManager;
private BulletManager _bulletManager;
各マネージャークラス(getBulletManager())を取得するいくつかのゲッター関数を与えます。おそらく、このクラス自体がシングルトンであるか、1つのクラスから到達可能です(とにかく中央のゲームシングルトンがどこかにあるはずです)。明確に定義されたハードコードされたデータと動作には何も問題はありません。
マネージャーを使用したい他のクラスがそのキーを使用して取得できるキーを使用してマネージャーを登録できるManagerManagerを作成しないでください。それは素晴らしいシステムであり、非常に柔軟ですが、ここでゲームについて話しているところです。どのシステムがゲームに含まれているかが正確にわかります。なぜあなたがしないようなふりをするのですか?これは、データよりもコードの方が重要だと考える人のためのシステムだからです。彼らは「コードは柔軟性があり、データはそれを満たすだけだ」と言うでしょう。しかし、コードは単なるデータです。私が説明したシステムは、はるかに簡単で、信頼性が高く、保守が簡単で、はるかに柔軟です(たとえば、1つのマネージャーの動作が他のマネージャーと異なる場合、システム全体をやり直すのではなく、数行変更するだけで済みます)。
嘘#2-コードは世界のモデルを中心に設計する必要があります
これで、ゲームの世界にエンティティができました。エンティティには、その動作を定義するいくつかのコンポーネントがあります。したがって、Componentオブジェクトのリストと、各ComponentのUpdate()関数を呼び出すUpdate()関数を使用してEntityクラスを作成します。正しい?
いいえ:)それは世界のモデルを中心に設計しています。あなたはゲームに弾丸を持っているので、弾丸クラスを追加します。次に、各弾丸を更新して次の弾丸に進みます。これはあなたのパフォーマンスを完全に殺します、そして、それはあなたにあらゆる場所で重複したコードと恐ろしい複雑なコードベースを与え、同様のコードの論理的な構造化をしません。(伝統的なOO設計がなぜ悪いのか、またはデータ指向設計を調べる理由の詳細については、ここで私の回答を確認してください)
OOバイアスのない状況を見てみましょう。私たちは以下を望んでいます。エンティティまたはオブジェクトのクラスを作成する必要がないことに注意してください。
- エンティティがたくさんあります
- エンティティは、エンティティの動作を定義するいくつかのコンポーネントで構成されています
- ゲームの各コンポーネントをフレームごとに、できれば制御された方法で更新したい
- コンポーネントを一緒に属しているものとして識別することを除いて、エンティティ自体が行う必要があることは何もありません。いくつかのコンポーネントのリンク/ IDです。
そして状況を見てみましょう。お使いのコンポーネントシステムはの行動に更新されますすべてのゲーム内のオブジェクトのすべてのフレームを。これは間違いなくあなたのエンジンの重要なシステムです。ここではパフォーマンスが重要です!
コンピュータアーキテクチャまたはデータ指向設計のどちらかをよく知っている場合は、メモリを密にパックし、コード実行をグループ化することで、最高のパフォーマンスを実現する方法を知っています。ABCABCABCのようにコードA、B、Cのスニペットを実行すると、AAABBBCCCのように実行した場合と同じパフォーマンスは得られません。これは、命令とデータキャッシュがより効率的に使用されるためだけでなく、すべての "A"を次々に実行する場合、最適化の余地がたくさんあるためです。重複するコードの削除、使用されるデータの事前計算すべての「A」など
したがって、すべてのコンポーネントを更新する場合は、更新関数を使用してそれらをクラス/オブジェクトにしないでください。各エンティティの各コンポーネントに対してその更新関数を呼び出さないでください。それが「ABCABCABC」ソリューションです。同一のコンポーネントの更新をすべてグループ化しましょう。次に、すべてのAコンポーネントを更新し、次にBなどを更新します。これを行うには何が必要ですか?
まず、コンポーネントマネージャーが必要です。ゲーム内のすべてのタイプのコンポーネントについて、マネージャークラスが必要です。そのタイプのすべてのコンポーネントを更新する更新関数があります。そのタイプの新しいコンポーネントを追加する作成関数と、指定されたコンポーネントを破棄する削除関数があります。そのコンポーネントに固有のデータを取得および設定する他のヘルパー関数がある場合があります(例:モデルコンポーネントの3Dモデルの設定)。マネージャーはある意味で外の世界へのブラックボックスであることに注意してください。各コンポーネントのデータがどのように保存されているかはわかりません。各コンポーネントがどのように更新されるかはわかりません。コンポーネントが適切に動作する限り、問題はありません。
次に、エンティティが必要です。これをクラスにすることもできますが、それはほとんど必要ありません。エンティティは、一意の整数IDまたはハッシュされた文字列(整数でもかまいません)にすぎません。エンティティのコンポーネントを作成するときに、IDを引数としてManagerに渡します。コンポーネントを削除する場合は、IDを再度渡します。エンティティを単にIDにする代わりに、エンティティにもう少しデータを追加することにはいくつかの利点があるかもしれませんが、要件に記載したように、すべてのエンティティの動作はコンポーネント自体によって定義されるため、これらはヘルパー関数にすぎません。それはあなたのエンジンですが、あなたにとって意味のあることをしてください。
必要なのはエンティティマネージャーです。このクラスは、IDのみのソリューションを使用する場合に一意のIDを生成するか、Entityオブジェクトの作成/管理に使用できます。また、必要に応じて、ゲーム内のすべてのエンティティのリストを保持することもできます。エンティティマネージャーは、コンポーネントシステムの中心的なクラスであり、ゲーム内のすべてのComponentManagerへの参照を格納し、それらの更新関数を正しい順序で呼び出します。ゲームループが実行する必要があるすべての方法でEntityManager.update()を呼び出すだけで、システム全体が他のエンジンからうまく分離されます。
これが全体像です。コンポーネントマネージャがどのように機能するかを見てみましょう。必要なものは次のとおりです。
- create(entityID)が呼び出されたときにコンポーネントデータを作成する
- remove(entityID)が呼び出されたときにコンポーネントデータを削除する
- update()が呼び出されたときにすべての(適用可能な)コンポーネントデータを更新します(つまり、すべてのコンポーネントが各フレームを更新する必要があるわけではありません)
最後の1つは、コンポーネントの動作/ロジックを定義する場所であり、作成するコンポーネントのタイプに完全に依存します。AnimationComponentは、現在のフレームに基づいてアニメーションデータを更新します。DragableComponentは、マウスでドラッグされているコンポーネントのみを更新します。PhysicsComponentは、物理システムのデータを更新します。それでも、同じタイプのすべてのコンポーネントを一度に更新するので、各コンポーネントがいつでも呼び出すことができる更新関数を持つ個別のオブジェクトである場合は不可能な最適化を実行できます。
コンポーネントデータを保持するXxxComponentクラスの作成をまだ要求していないことに注意してください。それはあなた次第です。データ指向のデザインがお気に入りですか。次に、変数ごとに個別の配列でデータを構造化します。オブジェクト指向デザインがお気に入りですか。(私はそれをお勧めしません、それでも多くの場所であなたのパフォーマンスを殺します)次に、各コンポーネントのデータを保持するXxxComponentオブジェクトを作成します。
マネージャーの素晴らしいところはカプセル化です。現在、カプセル化は、プログラミングの世界で最も恐ろしく誤用されている哲学の1つです。これは、使用方法です。マネージャーだけが、どのコンポーネントデータがどこに保存されているか、コンポーネントのロジックがどのように機能するかを知っています。データを取得/設定するいくつかの関数がありますが、それだけです。マネージャー全体とその基礎となるクラスを書き換えることができ、パブリックインターフェースを変更しないと、誰も気付かないでしょう。物理エンジンを変更しましたか?単にPhysicsComponentManagerを書き換えれば完了です。
そして最後にもう1つ、コンポーネント間の通信とデータ共有があります。現在、これはトリッキーであり、万能のソリューションはありません。たとえば衝突コンポーネントが位置コンポーネント(つまり、PositionManager.getPosition(entityID))から位置を取得できるように、マネージャーでget / set関数を作成できます。イベントシステムを使用できます。いくつかの共有データをエンティティに格納できます(私の考えでは最も醜いソリューション)。メッセージングシステムを使用できます(これはよく使用されます)。または、複数のシステムの組み合わせを使用してください!私はこれらの各システムに進む時間や経験はありませんが、googleとstackoverflow検索はあなたの友達です。