私はこのコンポーネントアーキテクチャで正しい軌道に乗っていますか?


9

私は最近、深いクラス階層を取り除き、構成可能なコンポーネントで置き換えるために、ゲームアーキテクチャを刷新することを決定しました。私が置き換える最初の階層はアイテム階層です。私が正しい方向に進んでいるかどうかを知るためにいくつかのアドバイスをお願いします。

以前は、次のような階層がありました。

Item -> Equipment -> Weapon
                  -> Armor
                  -> Accessory
     -> SyntehsisItem
     -> BattleUseItem -> HealingItem
                      -> ThrowingItem -> ThrowsAsAttackItem

言うまでもなく、乱雑になり始めており、これらは複数のタイプである必要があるアイテムに対する簡単な解決策ではありませんでした(つまり、一部の機器はアイテム合成で使用され、一部の機器はスロー可能です)。

次に、機能をリファクタリングして基本アイテムクラスに配置しようとしました。しかし、それから私はアイテムが未使用/余分なデータをたくさん持っていることを指摘していました。現在、私は他のゲームクラスに実行する前に、少なくとも私のアイテムに対して、アーキテクチャのようなコンポーネントを実行しようとしています。

これが、コンポーネントのセットアップについて私が現在考えていることです。

次のようなさまざまなコンポーネントのスロット(つまり、機器コンポーネントスロット、ヒーリングコンポーネントスロットなど、および任意のコンポーネントのマップ)を持つ基本アイテムクラスがあります。

class Item
{
    //Basic item properties (name, ID, etc.) excluded
    EquipmentComponent* equipmentComponent;
    HealingComponent* healingComponent;
    SynthesisComponent* synthesisComponent;
    ThrowComponent* throwComponent;
    boost::unordered_map<std::string, std::pair<bool, ItemComponent*> > AdditionalComponents;
} 

すべての項目コンポーネントは基本のItemComponentクラスから継承し、各コンポーネントタイプは、その機能を実装する方法をエンジンに伝える責任があります。つまり、HealingComponentは、アイテムをヒーリングアイテムとして使用する方法を戦闘力学に指示し、ThrowComponentは、アイテムをスロー可能なアイテムとして処理する方法を戦闘エンジンに指示します。

マップは、コアアイテムコンポーネントではない任意のコンポーネントを格納するために使用されます。私はそれをboolとペアにして、Item ContainerがItemComponentを管理するべきか、それとも外部ソースによって管理されているべきかを示しています。

ここでの私の考えは、ゲームエンジンで使用されるコアコンポーネントを事前に定義し、アイテムファクトリはアイテムが実際に持っているコンポーネントを割り当て、そうでなければnullであるということでした。マップには、通常スクリプトファイルによって追加/使用される任意のコンポーネントが含まれます。

私の質問は、これは良いデザインですか?そうでない場合、どのように改善できますか?すべてのコンポーネントをマップにグループ化することを検討しましたが、文字列インデックスを使用することは、コアアイテムコンポーネントには不要であるように見えました

回答:


8

それは非常に合理的な最初のステップのようです。

一般性(「追加コンポーネント」マップ)とルックアップパフォーマンス(ハードコードされたメンバー)の組み合わせを選択しています。これは、事前最適化のビットかもしれません-文字列ベースの一般的な非効率性に関するあなたのポイントルックアップはよくできていますが、ハッシュするのが速いものでコンポーネントにインデックスを付けることを選択することでそれを軽減できます。1つのアプローチは、各コンポーネントタイプに一意のタイプID(基本的には軽量のカスタムRTTIを実装している)とそれに基づくインデックスを与えることです。

いずれにしても、ハードコードされたコンポーネントや追加のコンポーネントなど、任意のコンポーネントを統一的な方法で要求できるようにするItemオブジェクトのパブリックAPIを公開することをお勧めします。これにより、すべてのアイテムコンポーネントクライアントをリファクタリングする必要なく、ハードコードされた/ハードコードされていないコンポーネントの基本的な表現またはバランスを簡単に変更できます。

また、ハードコードされた各コンポーネントの「ダミー」no-opバージョンを提供することを検討し、それらが常に割り当てられるようにすることもできます。その後、ポインターの代わりに参照メンバーを使用でき、NULLポインターを確認する必要がなくなります。ハードコードされたコンポーネントクラスの1つと対話する前。そのコンポーネントのメンバーと対話するための動的ディスパッチのコストは引き続き発生しますが、それはポインターメンバーでも発生します。パフォーマンスへの影響はほとんどあり得ないので、これはコードの清浄度の問題です。

2種類のライフタイムスコープを使用するのは良い考えではないと思います(つまり、追加のコンポーネントマップにあるブール値は素晴らしいアイデアではないと思います)。それはシステムを複雑にし、破壊とリソース解放がひどく決定論的ではないことを意味します。エンティティがコンポーネントのライフタイムを管理するか、コンポーネントが実現するサブシステムのどちらかを選択した場合、コンポーネントへのAPIはより明確になります(外部コンポーネントとのペアリングが優れているため、後者が好まれます)。アプローチについては、次に説明します)。

私があなたのアプローチで目にする大きな欠点は、「エンティティ」オブジェクトにすべてのコンポーネントをまとめていることです。これは、実際には常に最良の設計とは限りません。私の関連の答え別のコンポーネントベースの質問に:

コンポーネントの大きなマップとゲームオブジェクトでのupdate()呼び出しを使用するアプローチは、非常に最適ではありません(このような種類のシステムを最初に構築する場合の一般的な落とし穴)。更新中のキャッシュの一貫性が非常に悪くなり、同時実行性と、データまたは動作の大きなバッチのSIMDスタイルのプロセスへの傾向を一度に利用することができません。多くの場合、ゲームオブジェクトがコンポーネントを更新せず、コンポーネント自体を担当するサブシステムが一度にすべてを更新する設計を使用する方が良いでしょう。

コンポーネントをアイテムエンティティに格納することで、基本的に同じアプローチをとっています(これも、完全に受け入れ可能な最初のステップです)。パフォーマンスについて懸念しているコンポーネントへのアクセスの大部分は、それらを更新することだけであり、コンポーネントがキャッシュコヒーレントに保持されているコンポーネント編成に対してより外部のアプローチを使用することを選択した場合に気付くかもしれません。、彼らのニーズを最もよく理解しているサブシステムによる(ドメインの)効率的なデータ構造により、はるかに優れた、より並列化可能な更新パフォーマンスを実現できます。

しかし、私はこれを将来の方向性として考慮すべきものとしてのみ指摘します-これをやり過ぎてやりすぎたくないのは確かです。継続的なリファクタリングを通じて段階的な移行を行うことができます。または、現在の実装がニーズを完全に満たし、反復する必要がないことに気付く場合があります。


1
+1は、Itemオブジェクトの削除を提案します。事前の作業は増えますが、最終的にはより優れたコンポーネントシステムが作成されます。
James

さらにいくつか質問がありました。新しいトピアックを開始する必要があるかどうか不明なので、まずここで試してみましょう。私のアイテムクラスには、evertフレームを呼び出す(または閉じる)メソッドはありません。私のグラフィックスサブシステムについては、あなたの助言を得て、システムの下で更新するすべてのオブジェクトを保持します。もう1つの質問は、コンポーネントのチェックをどのように処理するかです。たとえば、Xとしてアイテムを使用できるかどうかを確認したいので、当然、Xを実行するために必要なコンポーネントがアイテムにあるかどうかを確認します。これを行う正しい方法はありますか?答えてくれてありがとう
user127817
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.