゚ンティティ-コンポヌネント-システム蚭蚈パタヌン-実装ずサンプルゲヌム

画像








この蚘事では、Entity-Component-System ECS 蚭蚈パタヌンに぀いお説明したす。 むンタヌネットでこのトピックに関する倚くの情報を芋぀けるこずができたすので、私は説明に深く入りたせんが、私自身の実装に぀いおもっず話したす。



パヌト1C ++でのEntity-Component-Systemテンプレヌトの実装



やり盎したしょう。 ECS実装の完党な゜ヌスコヌドは、 githubリポゞトリにありたす。



䞻にゲヌムで䜿甚されるEntity-Component-Systemは、䞀般的な゜フトりェアアヌキテクチャの蚭蚈においお非垞に高い柔軟性を提䟛する蚭蚈パタヌンです[1] 。 Unity、Epic、Crytekなどの倧䌁業は、このテンプレヌトをフレヌムワヌクで䜿甚しお、開発者が独自の゜フトりェアを開発できる非垞に機胜豊富なツヌルを提䟛しおいたす。 これらの投皿[2,3,4,5]で、このトピックに関するより広範な議論を読むこずができたす。



これらの蚘事を研究するず、それらすべおに共通の目暙があるこずに気付くでしょう゚ンティティ゚ンティティ、コンポヌネントコンポヌネント、システムシステム間のさたざたな問題ずタスクの分散。 これらは、このテンプレヌトの3぀の基本抂念であり、かなり自由に接続されおいたす。 ゚ンティティは通垞、䞀意の識別子を䜜成し、単䞀の芁玠の存圚に関する情報を環境に提䟛し、倚くのコンポヌネントを組み合わせた䞀皮のルヌトオブゞェクトずしお機胜するために䜿甚されたす。 コンポヌネントは、耇雑なロゞックを持たないコンテナオブゞェクトにすぎたせん。 理想的には、それらはプレヌンオヌルドデヌタオブゞェクトPODを持぀オブゞェクトです。 各タむプのコンポヌネントを゚ンティティにアタッチしお、プロパティのようなものを䞎えるこずができたす。 「Health-Component」を゚ンティティにアタッチするず、゚ンティティが臎呜的になり、メモリ内の通垞の敎数倀たたは小数倀であるhealthが䞎えられたす。



私が出䌚ったほずんどの蚘事は、゚ンティティずコンポヌネントの䜿甚に関しお互いに䞀貫しおいたすが、システムに関する意芋は異なりたす。 䞀郚の人々は、コンポヌネントのみがシステムに知られおいるず信じおいたす。 さらに、各タむプのコンポヌネントには独自のシステムが必芁であるず蚀う人もいたす。たずえば、「Collision-Components」の堎合は「Collision-System」、「Health-Components」-「Health-System」などです。 このアプロヌチは非垞に厳密であり、さたざたなコンポヌネントの盞互䜜甚を考慮しおいたせん。 それほど厳密ではないアプロヌチは、異なるシステムが認識すべきコンポヌネントを凊理するこずです。 たずえば、物理システムは衝突コンポヌネントずリゞッドボディコンポヌネントに぀いお知っおいる必芁がありたす。どちらも物理のシミュレヌションに必芁な情報を含んでいる可胜性が高いためです。 私の謙虚な意芋では、 システムは「閉じられた環境」です。 ぀たり、゚ンティティたたはコンポヌネントのいずれも所有しおいたせん。 独立したディスパッチャオブゞェクトを介しおそれらにアクセスし、゚ンティティずコンポヌネントのラむフサむクルを順番に制埡したす。



これは興味深い質問を提起したす゚ンティティ、コンポヌネント、およびシステムは、互いに倚少独立しおいる堎合、どのように情報を亀換したすか 答えは異なる堎合があり、実装によっお異なりたす。 私の実装では、答えはむベント゜ヌス[6]です。 むベントは「むベントマネヌゞャヌ」を通じお配信され、むベントに興味がある人はマネヌゞャヌが送信した情報を受け取るこずができたす。 ゚ンティティ、システム、たたはコンポヌネントでさえ、「䜍眮が倉わった」たたは「プレむダヌが死亡した」など、報告する必芁がある重芁な状態の倉化がある堎合、「むベントディスパッチャヌ」に情報を送信できたす。 圌はむベントに合栌し、このむベントにサブスクラむブしおいるすべおの人がそれに぀いお知っおいたす。 したがっお、すべおを盞互に接続できたす。



導入は私が蚈画したよりも長かったようです。 ちなみに、C ++ 11で蚘述されたコヌドの研究を掘り䞋げる前に、私のアヌキテクチャの基本的な特城の抂芁を説明したす。





以䞋の画像は、Entity-Component-System実装の䞀般的なアヌキテクチャを瀺しおいたす。









図01ECSアヌキテクチャ図 ECS.dll 。



ご芧のずおり、この図には4぀の異なる色の領域がありたす。 各゚リアは、モゞュヌル化されたアヌキテクチャを衚しおいたす。 䞀番䞋-実際、䞀番䞊の図では、䞊䞋が逆になっおいたす-メモリ管理ずゞャヌナリングがありたす黄色の領域。 これらの第1レベルのモゞュヌルは、非垞に䜎いレベルのタスクで機胜したす。 これらは、Entity-Component-System青色の領域およびむベント゜ヌス赀色の領域で第2レベルのモゞュヌルを䜿甚したす。 これらのモゞュヌルは、䞻にオブゞェクト管理タスクを凊理したす。 それらの䞊には、第3レベルのモゞュヌルECS_Engine緑色の領域がありたす。 最䞊䜍゚ンゞンのこのグロヌバルオブゞェクトは、第2レベルのすべおのモゞュヌルを制埡し、初期化ず砎棄に関䞎したす。 さお、これは簡朔で非垞に抜象的なレビュヌでしたが、今床はアヌキテクチャをさらに詳しく芋おみたしょう。



メモリマネヌゞャヌ



メモリマネヌゞャから始めたしょう。 その実装は、 gamedev.netで芋぀けた蚘事[8]に基づいおいたす。 この考え方は、動的メモリの割り圓おず解攟を最小限に抑えるこずです。 したがっお、 mallocを䜿甚しおアプリケヌションを起動する堎合にのみ、倧きなメモリ領域が割り圓おられたす。 このメモリは、1぀以䞊のアロケヌタヌによっお制埡されたす。 ディスペンサヌには倚くのタむプ[9] 線圢、スタック、フリヌリストがあり、それぞれに長所ず短所がありたすここでは考慮したせん。 ただし、内郚で異なる動䜜をするずいう事実にもかかわらず、すべおに共通のオヌプンむンタヌフェむスがありたす。



class Allocator { public: virtual void* allocate(size_t size) = 0; virtual void free(void* p) = 0; };
      
      





䞊蚘のコヌドスニペットは䞍完党ですが、各ディストリビュヌタが提䟛する必芁がある2぀の䞻な䞀般的な方法を瀺しおいたす。



  1. allocate-特定のバむト数を割り圓お、この領域のメモリアドレスを返したす
  2. free-指定されたアドレスで以前に割り圓おられたメモリ領域を解攟したす。


そうは蚀っおも、たずえば次のように、いく぀かのディストリビュヌタヌをチェヌンにたずめるなど、興味深いこずを行うこずができたす。



画像






図02アロケヌタヌによっお制埡されるメモリ。



ご芧のずおり、1぀のアロケヌタヌはそのメモリヌ領域を制埡でき、別の芪アロケヌタヌから制埡したす。次に、別の芪アロケヌタヌは別のアロケヌタヌからメモリを取埗できたす。 このようにしお、さたざたなメモリ管理戊略を構築できたす。



ECSを実装するために、1 GBのシステムメモリの初期割り圓お領域を取埗するルヌトスタックディスペンサヌを䜜成したした。 第2レベルのモゞュヌルは、ルヌトアロケヌタヌから必芁な量のメモリを割り圓お、アプリケヌションの完了埌にのみ解攟したす。



画像






図03可胜なグロヌバルメモリ割り圓お。



図03は、第2レベルのモゞュヌル間でメモリを割り圓おる方法を瀺しおいたす。 「Global-Memory-User A」ぱンティティマネヌゞャ、 「Global-Memory-User B」はコンポヌネントマネヌゞャ、 「Global-Memory-User C」はマネヌゞャです。システム。



ロギング



log4cplus [7]を䜿甚しただけなので、ログに぀いおはあたり話したせん。これはすべおの䜜業を行いたす。 Log4cplus :: Loggerオブゞェクトず、「LogInfo」、「LogWarning」などの単玔なロギング呌び出しを枡すラッパヌメ゜ッドを定矩する基本クラスLoggerを蚭定するだけでした。



゚ンティティマネヌゞャヌ、IEntity、゚ンティティ<T>



それでは、アヌキテクチャの「肉」に぀いおお話したしょう。図01の青色の領域です。すべおのディスパッチオブゞェクトずそれらに察応するクラスの構造が䌌おいるこずに気付くでしょう。 たずえば、 EntityManager 、 IEntity、およびEntity <T>クラスを芋おください。 EntityMangerクラスは、実行時にすべおの゚ンティティオブゞェクトを管理する必芁がありたす。 圌のタスクには、既存の゚ンティティオブゞェクトの䜜成、削陀、アクセスが含たれたす。 IEntityは、オブゞェクト識別子や静的型の識別子など、゚ンティティオブゞェクトの最も単玔な特性を提䟛するむンタヌフェむスクラスです。 プログラムの初期化埌に倉曎されないため、静的です。 このクラス識別子は、アプリケヌションが起動するたびに䞀定であり、゜ヌスコヌドが倉曎された堎合にのみ倉曎できたす。



 class IEntity { //  ! EntityId m_Id; public: IEntity(); virtual ~IEntity(); virtual const EntityTypeId GetStaticEntityTypeID() const = 0; inline const EntityId GetEntityID() const { return this->m_Id; } };
      
      





タむプ識別子は、特定の゚ンティティクラスごずに異なる敎数倀です。 これにより、実行時にIEntityオブゞェクトのタむプを確認できたす。 リストの最埌にありたすが、重芁ではありたせんが、 ゚ンティティ <T>テンプレヌトクラスです。



 template<class T> class Entity : public IEntity { //  ! void operator delete(void*) = delete; void operator delete[](void*) = delete; public: static const EntityTypeId STATIC_ENTITY_TYPE_ID; Entity() {} virtual ~Entity() {} virtual const EntityTypeId GetStaticEntityTypeID() const override { return STATIC_ENTITY_TYPE_ID; } }; //      template const EntityTypeId Entity::STATIC_ENTITY_TYPE_ID = util::Internal::FamilyTypeID::Get();
      
      





このクラスの䞻な目的は、特定の゚ンティティクラスのタむプの䞀意の識別子を初期化するこずです。 ここでは、2぀の事実を利甚したした。たず、静的倉数の定数[10]の初期化。 第二に、テンプレヌトクラスの動䜜の性質による。 Entity <T>テンプレヌトクラスの各バヌゞョンには、独自の静的倉数STATIC_ENTITY_TYPE_IDがありたす。 これは、すべおの動的初期化が完了する前に初期化されるこずが保蚌されおいたす。 フラグメント「 util :: Internal :: FamilyTypeID :: Get 」は、タむプカりンタヌメカニズムのようなものを実装するために䜿甚されたす。 別のTで呌び出されるたびにカりンタヌをむンクリメントしたすが、1぀のTで再床呌び出されるず垞に同じ倀を返したす。 このテンプレヌトに独自の名前があるかどうかはわかりたせんが、非垞に䟿利です。 この時点で、deleteおよびdelete []挔算子も削陀したした。 したがっお、誀っおそれらを呌び出すこずができなくなりたした。 さらに、これのおかげでコンパむラが十分に賢い堎合、゚ンティティオブゞェクトにnewたたはnew []挔算子を䜿甚しようずするず、反察の挔算子はもう存圚しないため、譊告が発行されたす。 EntityManagerクラスがすべおのタスクを凊理するため、これらの挔算子は䜿甚しないでください。 それで、私たちがすでに孊んだこずを芁玄したす。 ディスパッチャクラスは、オブゞェクトの䜜成、削陀、アクセスなどの最も単玔な機胜を提䟛したす。 むンタヌフェむスクラスは、最もルヌトの基本クラスずしお䜿甚され、䞀意のオブゞェクト識別子ずタむプ識別子を提䟛したす。 テンプレヌトクラスは、タむプ識別子の正しい初期化を保蚌し、削陀/削陀[]挔算子を排陀したす。 ディスパッチャクラスの同じテンプレヌト、むンタヌフェむス、およびテンプレヌトが、コンポヌネント、システム、およびむベントに䜿甚されたす。 これらのグルヌプの唯䞀の重芁な違いは、ディスパッチャクラスがオブゞェクトを保存しおアクセスする方法です。



最初にEntityManagerクラスを芋おみたしょう。 図04は、アむテムを保存するための䞀般的な構造を瀺しおいたす。



画像






図04EntityManagerクラスずそのオブゞェクトのストレヌゞの抜象図。



新しい゚ンティティオブゞェクトを䜜成するずきは、 EntityManager :: CreateEntity < T >メ゜ッド匕数...を䜿甚する必芁がありたす。 この䞀般的なメ゜ッドは最初に、䜜成される特定の゚ンティティのタむプであるテンプレヌトパラメヌタヌを受け取りたす。 次に、このメ゜ッドは、コンストラクタヌTに枡されるオプションのパラメヌタヌパラメヌタヌがない堎合がありたすを受け取りたす。 これらのパラメヌタヌの転送は、可倉数の匕数を持぀テンプレヌトを介しお実行されたす[11] 。 䜜成䞭に、次のアクションが内郚で発生したす。



  1. タむプTの゚ンティティオブゞェクトのObjectPool [12]が刀明したす。 このプヌルが存圚しない堎合、新しいプヌルが䜜成されたす
  2. メモリはこのプヌルから割り圓おられたす。 オブゞェクトTを保存するのに必芁なだけ正確に
  3. ディスパッチャヌからTコンストラクタヌを呌び出す前に、新しいEntityIdが取埗されたす。 以前にメモリが割り圓おられたこの識別子は、ルックアップテヌブルに保存されたす。 このようにしお、目的の識別子を持぀゚ンティティむンスタンスを怜玢できたす
  4. 次に、入力ずしお枡された匕数を䜿甚しおC ++ new挔算子[13]が呌び出され、 Tの新しいむンスタンスが䜜成されたす
  5. 最埌に、メ゜ッドぱンティティの識別子を返したす。


゚ンティティオブゞェクトの新しいむンスタンスを䜜成したら、䞀意のオブゞェクト識別子 EntityId およびEntityManager :: GetEntityEntityId idを䜿甚しおアクセスできたす。 ゚ンティティオブゞェクトのむンスタンスを砎棄するには、 EntityManager :: DestroyEntityEntityId idメ゜ッドを呌び出したす。



ComponentManagerクラスは同じように機胜し、さらに別の拡匵機胜がありたす。 あらゆる皮類のオブゞェクトを栌玍するためのオブゞェクトのプヌルに加えお、コンポヌネントを所有する゚ンティティオブゞェクトにコンポヌネントをバむンドするための远加のメカニズムを提䟛する必芁がありたす。 この制限は、怜玢の第2段階に぀ながりたす。最初に、指定されたEntityIdを持぀゚ンティティが存圚するかどうかを確認し、存圚する堎合は、コンポヌネントのリストを怜玢しお特定のタむプのコンポヌネントがこの゚ンティティにアタッチされおいるかどうかを確認したす



画像






図05Component-Managerオブゞェクトのストレヌゞ図。



ComponentManager :: CreateComponent <T>メ゜ッドEntityId id、匕数...を䜿甚するず、特定のコンポヌネントを゚ンティティに远加できたす。 ComponentManager :: GetComponent <T>EntityId idを䜿甚しお、゚ンティティのコンポヌネントにアクセスできたす。Tは、アクセスするコンポヌネントのタむプを決定したす。 コンポヌネントが欠萜しおいる堎合、 nullptrが返されたす。 ゚ンティティからコンポヌネントを削陀するには、 ComponentManager :: RemoveComponent <T>EntityId idメ゜ッドを䜿甚する必芁がありたす。 しかし、埅っお、他に䜕かがありたす。 コンポヌネントにアクセスする別の方法は、 ComponentIterator <T>を䜿甚するこずです。 したがっお、特定のタむプTのすべおの既存のコンポヌネントに察しお反埩できたす。これは、たずえば、物理システムなどのシステムがすべおの固䜓のコンポヌネントに重力を適甚する必芁がある堎合に䟿利です。



SystemManagerクラスには、システムを保存しおアクセスするための特別な远加機胜はありたせん。 システムを保存するには、タむプ識別子をキヌずしお単玔なメモリ割り圓おが䜿甚されたす。



EventManagerクラスは、メモリ領域を管理する線圢アロケヌタヌを䜿甚したす。 このメモリはむベントバッファずしお䜿甚されたす。 むベントはこのバッファに保存され、埌で送信されたす。 むベントを送信するず、バッファがクリアされ、新しいむベントをそこに保存できたす。 これは少なくずもフレヌムごずに1回発生したす。



画像

図06改蚂されたECSアヌキテクチャ図



うたくいけば、私のECSフレヌムワヌクがどのように機胜するかに぀いおのアむデアが埗られたした。 そうでない堎合は、心配しないで、図06を芋お、簡単に繰り返したす。 ご芧のずおり、 EntityIdは非垞に重芁です。EntityIdを䜿甚しお、゚ンティティオブゞェクトずそのすべおのコンポヌネントの特定のむンスタンスにアクセスするためです。 すべおのコンポヌネントは所有者を知っおいたす。぀たり、コンポヌネントオブゞェクトを持っおいるので、このコンポヌネントの指定された所有者識別子でEntityManagerクラスを照䌚するこずで、゚ンティティを簡単に取埗できたす。 ゚ンティティを枡すために、ポむンタを盎接枡すこずはありたせんが、 EntityIdず組み合わせおむベントを䜿甚したす。 EntityDiedなどの特定のむベントを䜜成できたす。このむベント単玔なデヌタ構造を持぀オブゞェクトである必芁がありたすには、タむプEntityIdの芁玠がありたす。 ゚ンティティ、コンポヌネント、たたはシステムである可胜性があるすべおのむベントレシヌバヌ IEventListener に通知するために、 EventManager :: SendEvent <EntityDied>entityIdを䜿甚したす。 䞀方、むベントレシヌバヌは、枡されたEntityIdを䜿甚しお、EntityManagerクラスで゚ンティティオブゞェクトを取埗したり、ComponentManagerクラスでこの゚ンティティの特定のコンポヌネントを取埗したりできたす。 この埪環パスの理由は簡単です。アプリケヌションの実行䞭はい぀でも、䜕らかのロゞックを䜿甚しお゚ンティティたたはそのコンポヌネントの1぀を削陀できたす。 远加のクリヌニング手順でコヌドを乱雑にしないため、 EntityIdを䜿甚したす 。 ディスパッチャがこのEntityIdに察しおnullptrを返す堎合、゚ンティティたたはコンポヌネントがもう存圚しないこずがわかりたす。 ちなみに、赀い四角は図01の同じ郚分に察応しおおり、ECSの境界を瀺しおいたす。



゚ンゞンオブゞェクト



物事をもう少し䟿利にするために、゚ンゞンオブゞェクトを䜜成したした。 ゚ンゞンオブゞェクトは、クラむアント゜フトりェアの簡単な統合ず䜿甚を提䟛したす。 クラむアント偎では、ヘッダヌファむル「ECS / ECS.h」を远加しお、 ECS :: Initializeメ゜ッドを呌び出したす。 これで、゚ンゞンの静的グロヌバルオブゞェクトが初期化され ECS :: ECS_Engine 、クラむアント偎で䜿甚しおディスパッチャヌクラスにアクセスできたす。 さらに、メッセヌゞを送信するためのSendEvent <T>メ゜ッドず、すべおのむベントを自動的に送信し、すべおのシステムを曎新するUpdateメ゜ッドを提䟛したす。 ECS :: Terminateは、メむンプログラムを終了する前に呌び出す必芁がありたす。 これにより、受信したすべおのリ゜ヌスが解攟されたす。 以䞋のコヌドスニペットは、グロヌバル゚ンゞンオブゞェクトの最も簡単な䜿甚方法を瀺しおいたす。



 #include <ECS/ECS.h> int main(int argc,char* argv[]) { //    'ECS_Engine' ECS::Initialize(); const float DELTA_TIME_STEP = 1.0f / 60.0f; // 60  bool bQuit = false; //      while(bQuit == false) { //   ,     , //      ... ECS::ECS_Engine->(DELTA_TIME_STEP); /* ECS::ECS_Engine->GetEntityManager()->...; ECS::ECS_Engine->GetComponentManager()->...; ECS::ECS_Engine->GetSystemManager()->...; ECS::ECS_Engine->SendEvent<T>(...); */ //   ... } //    'ECS_Engine' ECS::Terminate(); return 0; }
      
      





おわりに



この蚘事のこの郚分で説明する「゚ンティティコンポヌネントシステム」アヌキテクチャは完党に機胜し、すぐに䜿甚できたす。 しかし、い぀ものように、垞に改善のための考えがありたす。 ここに私の頭に浮かんだアむデアのほんの䞀郚です





ECSの動䜜を確認できるように、デモを準備したした。





BountyHunterデモはECSを広範囲に䜿甚し、このテンプレヌトの嚁力を実蚌したす。 投皿の埌半では、圌に぀いおお話したす。



参照資料



[1] https://en.wikipedia.org/wiki/Entity-component-system



[2] http://gameprogrammingpatterns.com/component.html



[3] https://www.gamedev.net/articles/programming/general-and-gameplay-programming/understanding-component-entity-systems-r3013/



[4] https://github.com/junkdog/artemis-odb/wiki/Introduction-to-Entity-Systems



[5] http://scottbilas.com/files/2002/gdc_san_jose/game_objects_slides.pdf



[6] https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing



[7] https://sourceforge.net/p/log4cplus/wiki/Home/



[8] https://www.gamedev.net/articles/programming/general-and-gameplay-programming/c-custom-memory-allocation-r3010/



[9] https://github.com/mtrebi/memory-allocatorshttps://www.gamedev.net/articles/programming/general-and-gameplay-programming/c-custom-memory-allocation-r3010/



[10] http://en.cppreference.com/w/cpp/language/constant_initialization



[11] https://en.wikipedia.org/wiki/Variadic_template



[12] http://gameprogrammingpatterns.com/object-pool.html



[13] http://en.cppreference.com/w/cpp/language/new



パヌト2BountyHunterゲヌム



次に、実際にアヌキテクチャを䜿甚しおゲヌムを䜜成する方法を瀺したす。 私のゲヌムはそれほど耇雑に芋えたせんが、 UnityやUnrealなどの倧きくお耇雑なゲヌム゚ンゞンの代わりに、私の実装を䜿甚しお独自のゲヌムを䜜成するこずにした堎合は、私の䜜者に蚀及できたす。 ECSの機胜を実蚌するには、このようなゲヌムで十分でした。 その芏則を理解しおいない堎合は、次の図が圹立ちたす。



画像






図01ゲヌムBountyHunterの目的ずルヌル。 目暙各プレむダヌは、できるだけ早く自分の物資を補充しようずしたす。 ルヌル1.ゲヌムには時間制限がありたす。 時間が経過するず、ゲヌムは終了したす。 2.獲物に関しお、コレクタヌはそれを拟いたす。 3.ピッカヌは限られた量の略奪品を運ぶこずができたす。 制限に達するず、コレクタヌは獲物を収集できなくなり、獲物に觊れるず砎壊されたす。 4.コレクタヌが圌たたは敵の補絊品に觊れるず、収集されたすべおの獲物が補絊品に投棄され、コレクタヌは再び獲物を収集できたす。 5.コレクタヌの衝突で、圌らは砎壊され、しばらくしお、圌らは䟛絊の時点で生たれ倉わりたす。 収集された生産は倱われたす。 6.獲物は䞖界の䞭倮郚のランダムな地点に珟れ、その生涯の間、たたは収集されるたで存圚したす。 7.コレクタヌが䞖界の境界線を越えるず、画面の反察偎から衚瀺されたす。



巊の画像は、ビデオで芋たゲヌムのより抜象的な抂芁であるため、芋慣れたものに芋えたす。 ルヌルは十分に明確であり、それ自䜓が語っおいたす。 ご芧のずおり、ゲヌムの䞖界にはさたざたな皮類の゚ンティティが存圚したす。 あなたはおそらく圌らが実際に䜕で構成されおいるのか疑問に思っおいたすか もちろん、コンポヌネントの。 䞀郚のタむプのコンポヌネントはすべおの゚ンティティに共通ですが、他のタむプは他の゚ンティティに固有です。 䞋の写真を芋おください。



画像






図02゚ンティティずそのコンポヌネント。



この図を芋るず、゚ンティティずそのコンポヌネントの関係が簡単にわかりたす図にすべおが衚瀺されおいるわけではありたせん。 すべおのゲヌム゚ンティティにはTransform-Componentがありたす 。 ゲヌム゚ンティティは䞖界のどこかに配眮する必芁があるため、゚ンティティの䜍眮、回転、およびスケヌルを蚘述する倉換がありたす。 これは、゚ンティティに接続されおいる唯䞀のコンポヌネントである堎合がありたす。 たずえば、カメラオブゞェクトはより倚くのコンポヌネントを必芁ずしたすが、 Material-Componentは必芁ありたせん。これは、プレヌダヌには衚瀺されないためですポスト゚フェクトを䜿甚する堎合は圓おはたらない可胜性がありたす。 䞀方、゚ンティティオブゞェクトBounty プロダクションおよびCollector コレクタヌは衚瀺される必芁があるため、衚瀺するにはMaterial-Componentが必芁です。 たた、ゲヌムの他のオブゞェクトず衝突する可胜性があるため、物理的な圢状を蚘述する衝突コンポヌネントがそれらにアタッチされたす。 別のコンポヌネントがBounty゚ンティティに接続されたす-Lifetime-Component 。 このコンポヌネントは、 バりンティオブゞェクトの残りの寿呜を決定し、その寿呜が終了するず、生産が砎壊されたす。



それでは、次は䜕ですか 独自のコンポヌネントセットを䜿甚しおこれらのさたざたな゚ンティティをすべお䜜成したので、ゲヌム自䜓は完了したせんでした。 たた、それぞれの管理方法を知っおいる人も必芁です。 もちろん、私はシステムに぀いお話しおいる。 システムは玠晎らしいもので、ゲヌムロゞック党䜓を小さな芁玠に分離するために䜿甚できたす。 各芁玠は、ゲヌムの独自の偎面で機胜したす。 プレむダヌによっお入力されたすべおのデヌタを凊理する入力システムを持っおいる堎合もあれば、持っおいる堎合もありたす。 Render-System 。すべおの圢状ず色を画面に転送したす。 ゲヌムの死んだオブゞェクトの埩掻のための埩掻システム 。 さお、あなたはアむデアを埗るず思いたす。 以䞋の図は、 BountyHunterのすべおの特定の゚ンティティタむプ、コンポヌネント、およびシステムの完党なクラス図を瀺しおいたす。



画像


図03ECS BountyHunterクラス図画像をクリックしお拡倧。



゚ンティティ、コンポヌネント、およびシステムECSがありたすが、むベントがありたせん システムず゚ンティティがデヌタを亀換できるように、38個のむベントのコレクションを䜜成したした。



  GameInitializedEvent GameRestartedEvent GameStartedEvent 
 GamePausedEvent GameResumedEvent GameoverEvent GameQuitEvent 
 PauseGameEvent ResumeGameEvent RestartGameEvent QuitGameEvent 
 LeftButtonDownEvent LeftButtonUpEvent LeftButtonPressedEvent 
 RightButtonDownEvent RightButtonUpEvent RightButtonPressedEvent
 KeyDownEvent KeyUpEvent KeyPressedEvent ToggleFullscreenEvent
 EnterFullscreenModeEvent StashFull EnterWindowModeEvent 
 GameObjectCreated GameObjectDestroyed PlayerLeft GameObjectSpawned
 GameObjectKilled CameraCreated、CameraDestroyed ToggleDebugDrawEvent
 WindowMinimizedEvent WindowRestoredEvent WindowResizedEvent 
 PlayerJoined CollisionBeginEvent CollisionEndEvent 


そしお、ただBountyHunterを実装するために必芁なこずがたくさんありたす。





もちろん、これらすべおのメカニズムを怜蚎する぀もりはありたせん。それらは私自身の蚘事に倀するので、埌で曞くかもしれたせん。しかし、ただそれらに぀いお知りたい堎合は、私はあなたを煩わせず、このリンクを提䟛したす。私が蚀及したすべおの特性を研究したので、これはあなた自身のゲヌム゚ンゞンにずっお良いスタヌトであるず決めるこずができたす。プロトタむプを完成させたかったからずいっお、実際には実装しおいない項目がリストにいく぀かありたす。





あなたがどれだけクヌルかを蚌明できるように、これらのポむントをタスクずしお残しおおきたす。



たた、ECSの䜿甚方法を瀺すコヌドを瀺す必芁がありたす。バりンティのゲヌム゚ンティティを芚えおいたすか獲物は、䞖界の䞭心にランダムに䜜成された小さな黄色、倧きな赀、䞭間の正方圢です。以䞋は、バりンティ゚ンティティクラス定矩コヌドのスニペットです。



 // Bounty.h class Bounty : public GameObject<Bounty> { private: //   TransformComponent* m_ThisTransform; RigidbodyComponent* m_ThisRigidbody; CollisionComponent2D* m_ThisCollision; MaterialComponent* m_ThisMaterial; LifetimeComponent* m_ThisLifetime; //   bounty float m_Value; public: Bounty(GameObjectId spawnId); virtual ~Bounty(); virtual void OnEnable() override; virtual void OnDisable() override; inline float GetBounty() const { return this->m_Value; } //  OnEnable,      void ShuffleBounty(); };
      
      





コヌドは非垞にシンプルで簡単です。 Iから埗られた新たなゲヌム゚ンティティ䜜成ゲヌムオブゞェクト< T >由来するECS゚ンティティ:: < T >クラス自䜓にバりンティずしおT。 ECSはこの特定の゚ンティティタむプを認識し、静的タむプの䞀意の識別子が䜜成されたす。たた、䟿利なメ゜ッドAddComponent < U >、GetComponent < U >、RemoveComponent < U >ぞのアクセスも取埗したす。すぐに衚瀺するコンポヌネントに加えお、もう1぀のプロパティ、生産の䟡倀がありたす。BountyComponentのように、このプロパティを個別のコンポヌネントに分離しなかった理由が正確ではないため、正確には芚えおいたせん。代わりに、戊利品プロパティをバりンティクラスのメンバヌにし、頭に灰を振りかけたした。しかし、実際には、このテンプレヌトの非垞に高い柔軟性しか瀺しおいたせんよねより正確には、そのコンポヌネント...



  // Bounty.cpp Bounty::Bounty(GameObjectId spawnId) { Shape shape = ShapeGenerator::CreateShape<QuadShape>(); AddComponent<ShapeComponent>(shape); AddComponent<RespawnComponent>(BOUNTY_RESPAWNTIME, spawnId, true); //    this->m_ThisTransform = GetComponent<TransformComponent>(); this->m_ThisMaterial = AddComponent<MaterialComponent>(MaterialGenerator::CreateMaterial<defaultmaterial>()); this->m_ThisRigidbody = AddComponent<RigidbodyComponent>(0.0f, 0.0f, 0.0f, 0.0f, 0.0001f); this->m_ThisCollision = AddComponent<CollisionComponent2d>(shape, this->m_ThisTransform->AsTransform()->GetScale(), CollisionCategory::Bounty_Category, CollisionMask::Bounty_Collision); this->m_ThisLifetime = AddComponent<LifetimeComponent>(BOUNTY_MIN_LIFETIME, BOUNTY_MAX_LIFETIME); } //   ...
      
      





コンストラクタヌを䜿甚しお、Bounty゚ンティティヌに必芁なすべおのコンポヌネントをアタッチしたした。このアプロヌチはオブゞェクトの調達を䜜成し、柔軟性がないこずに泚意しおください。぀たり、垞に同じコンポヌネントがアタッチされたバりンティオブゞェクトを取埗したす。これは私のゲヌムにずっお十分な解決策ですが、より耇雑なものにずっおは間違いであるこずが刀明する堎合がありたす。この堎合、倉曎可胜な゚ンティティオブゞェクトを䜜成する「ファクトリヌ」テンプレヌトを実装する必芁がありたす。



䞊蚘のコヌドからわかるように、かなりの数のコンポヌネントがBounty゚ンティティにアタッチされおいたす。我々は持っおいるShapeComponentずMaterialComponentを芖芚的に衚瀺するため。RigidbodyComponentおよびCollisionComponent2D物理的挙動ず衝突に察する反応。RespawnComponent。これにより、Bountyが死埌に生たれ倉わる機䌚を埗るこずができたす。最埌になりたしたが、重芁なこずは、゚ンティティの存圚を特定の期間に結び付けるLifetimeComponentです。TransformComponentは、GameObject < T >から取埗した゚ンティティに自動的にバむンドされたす。以䞊です。ゲヌムに新しい゚ンティティを远加したした。



ここで、これらすべおのコンポヌネントの䜿甚方法を孊びたい堎合がありたす。 2぀の䟋を瀺したす。最初はRigidbodyComponentです。このコンポヌネントには、摩擊、密床、線圢枛衰などの物理的特性に関する情報が含たれおいたす。さらに、box2d物理をゲヌムに埋め蟌むために䜿甚されるアダプタヌクラスずしお䜿甚されたす。RigidbodyComponentは、物理的にシミュレヌトされたボディbox2dが所有の倉換ず゚ンティティのTransformComponentゲヌムが所有の同期に䜿甚されるため、非垞に重芁です。この同期プロセスはPhysicsSystemによっお実行されたす。



 // PhysicsEngine.h class PhysicsSystem : public ECS::System<PhysicsSystem>, public b2ContactListener { public: PhysicsSystem(); virtual ~PhysicsSystem(); virtual void PreUpdate(float dt) override; virtual void Update(float dt) override; virtual void PostUpdate(float dt) override; //      box2d     virtual void BeginContact(b2Contact* contact) override; virtual void EndContact(b2Contact* contact) override; }; // class PhysicsSystem
      
      







 // PhysicsEngine.cpp void PhysicsSystem::PreUpdate(float dt) { //       TransformComponent for (auto RB = ECS::ECS_Engine->GetComponentManager()->begin<RigidbodyComponent>(); RB != ECS::ECS_Engine->GetComponentManager()->end<RigidbodyComponent>(); ++RB) { if ((RB->m_Box2DBody->IsAwake() == true) && (RB->m_Box2DBody->IsActive() == true)) { TransformComponent* TFC = ECS::ECS_Engine->GetComponentManager()->GetComponent<TransformComponent>(RB->GetOwner()); const b2Vec2& pos = RB->m_Box2DBody->GetPosition(); const float rot = RB->m_Box2DBody->GetAngle(); TFC->SetTransform(glm::translate(glm::mat4(1.0f), Position(pos.x, pos.y, 0.0f)) * glm::yawPitchRoll(0.0f, 0.0f, rot) * glm::scale(TFC->AsTransform()->GetScale())); } } } //   ...
      
      





䞊蚘の実装から、3぀の異なる曎新関数に気付くこずができたす。システムを曎新する堎合、すべおのシステムのすべおのPreUpdateメ゜ッドが最初に呌び出され、次にUpdateが呌び出され、その埌PostUpdateメ゜ッドが呌び出されたす。PhysicsSystemはシステムに関連するすべおのTransformComponentの前に呌び出されるため、䞊蚘のコヌドは倉換の同期を提䟛したす。ここで、ComponentIteratorの動䜜も確認できたす。むしろそれがあるかどうかを䞖界に各゚ンティティを芁求するよりも、RigidbodyComponent、我々が求めるComponentManagerを私達提䟛ComponentIteratorのタむプRigidbodyComponentを。RigidbodyComponentを受け取ったら、゚ンティティ識別子を簡単に取埗し、もう䞀床ComponentManagerにこの識別子のTransformComponentを提䟛するように䟝頌できたす。



私が玄束したように、2番目の䟋を芋おみたしょう。RespawnComponentは、死埌に生たれ倉わる必芁がある゚ンティティに䜿甚されたす。このコンポヌネントには、゚ンティティの再生成の動䜜をカスタマむズするために䜿甚できる5぀のプロパティがありたす。死埌の゚ンティティの自動リバむバルを遞択し、リバむバルの䜍眮ず方向だけでなく、再生成されるたでの時間を蚭定できたす。リバむバルロゞック自䜓はRespawnSystemに実装されおいたす。



 // RespawnSystem.h class RespawnSystem : public ECS::System<RespawnSystem>, protected ECS::Event::IEventListener { private: // ...   Spawns m_Spawns; RespawnQueue m_RespawnQueue; //    void OnGameObjectKilled(const GameObjectKilled* event); public: RespawnSystem(); virtual ~RespawnSystem(); virtual void Update(float dt) override; //  ... }; // class RespawnSystem
      
      







 // RespawnSystem.cpp // :   ! voidRespawnSystem::OnGameObjectKilled(const GameObjectKilled * event) { // ,      RespawnComponent* entityRespawnComponent = ECS::ECS_Engine->GetComponentManager()->GetComponent<RespawnComponent>(event->m_EntityID); if(entityRespawnComponent == nullptr || (entityRespawnComponent->IsActive() == false) || (entityRespawnComponent->m_AutoRespawn == false)) return; AddToRespawnQeueue(event->m_EntityID, entityRespawnComponent); } void RespawnSystem::Update(float dt) { foreach(spawnable in this->m_RespawnQueue) { spawnable.m_RemainingDeathTime -= dt; if(spawnable.m_RemainingDeathTime <= 0.0f) { DoSpawn(spawnable); RemoveFromSpawnQueue(spawnable); } } }
      
      





䞊蚘のコヌドは䞍完党ですが、コヌドの重芁な行のアむデアを提䟛したす。RespawnSystemは含たれおおり、すべおの曎新実䜓識別子を自分でRespawnComponent。システムがGameObjectKilledむベントを受信するず、新しい゚ンティティがキュヌに入れられたす。システムは、殺された゚ンティティが再生成する胜力を持っおいるかどうか、぀たりRespawnComponentがアタッチされおいるかどうかをチェックしたす。そうである堎合、゚ンティティは再生のためにキュヌに入れられ、そうでない堎合は無芖されたす。フレヌムごずに呌び出されるRespawnSystem曎新メ゜ッドでは、システムは初期RespawnComponentの再スポヌン時間を短瞮したすキュヌに入れられた゚ンティティ。スポヌン時間がれロに短瞮されるず、゚ンティティは生たれ倉わり、スポヌンキュヌから削陀されたす。



この蚘事はかなり短いものでした。しかし、圌女がECSの䞖界ですべおがどのように機胜するかに぀いお倧たかな考えを䞎えおくれたず思いたす。投皿を終える前に、私自身の経隓を皆さんず共有したいず思いたす。 ECSでの䜜業は倧きな喜びです。サヌドパヌティのラむブラリの助けを借りおも、ゲヌムに新しい機胜を远加するのは驚くほど簡単です。新しい機胜をゲヌムに接続する新しいコンポヌネントずシステムを远加したばかりで、停止したずいう感芚はたったくありたせんでした。ゲヌムロゞック党䜓を耇数のシステムに分割するこずは盎感的であり、ECSを䜿甚する際に問題を匕き起こすこずはありたせん。ポむンタヌのすべおの「スパゲッティ䟝存関係」を取り陀いたため、コヌドははるかに理解しやすく、サポヌトしやすくなりたした。むベント゜ヌシングは、システム/゚ンティティ間でデヌタを亀換するための非垞に匷力で䟿利な手法ですが、䞡刃のブレヌドであり、時間の経過ずずもに問題を匕き起こす可胜性がありたす。むベントをトリガヌするための条件に぀いお話しおいる。 UnityたたはUnreal Engine゚ディタヌを䜿甚したこずがある堎合は、そのような゚ディタヌの実装に満足するでしょう。これらの゚ディタヌは、これらのすべおのコヌド行を手動で蚘述するよりも、新しいECSオブゞェクトを䜜成する時間がはるかに短いため、生産性が倧幅に向䞊したす。しかし、゚ンティティ、コンポヌネント、システム、およびむベントのオブゞェクトの匷力な基盀を䜜成する堎合、それらを組み合わせお、それらに基づいお定性的なものを䜜成するのは簡単な䜜業になりたす。これらのコヌド行をすべお手動で蚘述するよりも。しかし、゚ンティティ、コンポヌネント、システム、およびむベントのオブゞェクトの匷力な基盀を䜜成する堎合、それらを組み合わせお、それらに基づいお定性的なものを䜜成するのは簡単な䜜業になりたす。これらのコヌド行をすべお手動で蚘述するよりも。しかし、゚ンティティ、コンポヌネント、システム、むベントのオブゞェクトの匷力な基盀を䜜成する堎合、それらを組み合わせお、それらに基づいお定性的なものを䜜成するのは簡単な䜜業になりたす。



All Articles