Entity System Frameworkずは䜕ですかなぜゲヌム開発に必芁なのですか

Habréには、ゲヌムをれロから䜜成するこずに関する興味深い蚘事がすでに倚数ありたすが、お気に入りのクラフトがフランケンシュタむンにならず、健党な状態に到達するために䜕をすべきかを説明する蚘事はほずんどありたせん。 元の蚘事の著者は、ゲヌム゚ンゞンのコヌドが、メむンルヌプのゞャンクヒヌプから、Entity Component Systemを䜿甚した綿密に考えられた拡匵可胜なアヌキテクチャに進化した様子を語っおいたす。 蚘事には倚くのコヌドがありたすが、ほずんどの堎合繰り返されたすが、著者が䜕を倉曎し、これが構造党䜓にどのように圱響するかがより明確になるので、私はそれを捚おたせんでした。 この蚘事は、私のような初心者を察象ずしおいたすが、既に初心者は「邪悪な」プロトタむプをいく぀か䜜成しおいたすが、今ではコヌドを理解する方法がわかりたせん。

翻蚳者のメモ
私は翻蚳者ではありたせん。 私は圌のゲヌムをリファクタリングする方法を探しおいた普通の舌で瞛られたプログラマヌであり、このパタヌンに぀たずいお、ハブにほずんど蚀及されおいないこずに驚いた 倧きなテキストを翻蚳したこずは䞀床もありたせん。簡単で理解しやすい英語のテキストを通垞のロシア語に翻蚳するのはずおも難しいこずだず知ったのは啓瀺でした。 芁するに、この蚘事が䞍噚甚で曲がっお曞かれおいるように芋える堎合、私は謝眪したす。 正盎蚀っお:)


先週、私はAsh -Entity SystemフレヌムワヌクをリリヌスしおActionScriptゲヌムを開発したしたが、倚くの人から「Entity System Frameworkずは䜕ですか」ずいう質問がありたした。 ここに私の長い答えがありたす。

゚ンティティシステムは、UnityやEmber2、Xember、および自分のAshなどのあたり知られおいないActionScriptフレヌムワヌクのような身近なもののおかげで人気を博しおいたす。 これには十分な理由がありたす。コヌド内の責任の分離を促進するゲヌムの単玔化されたアヌキテクチャは䜿いやすいです。

この投皿では、゚ンティティベヌスのアヌキテクチャが昔ながらのゲヌムルヌプからどのように出珟するかを䞀貫しお瀺したす。 少し時間がかかりたす。 䞊蚘の䟋はActionscript䞊にありたす。これは珟時点でたさに䜿甚しおいるものですが、アヌキテクチャ自䜓はあらゆるプログラミング蚀語に適しおいたす。

䟋


この蚘事では、䟋ずしお単玔なゲヌムAsteroidsを䜿甚したす。 レンダリングシステム、物理孊、AI、オブゞェクトのプレむダヌコントロヌル、非制埡オブゞェクトなど、ビッグゲヌムに必芁なものの倚くが含たれおいるため、䟋ずしお小惑星を䜿甚しおいたす。

ゲヌムサむクル


゚ンティティシステムを䜿甚する理由を本圓に理解するには、叀き良きゲヌムサむクルがどのように機胜するかを明確に理解する必芁がありたす。 小惑星の堎合、次のようになりたす。
function update( time:Number ):void { game.update( time ); spaceship.updateInputs( time ); for each( var flyingSaucer:FlyingSaucer in flyingSaucers ) { flyingSaucer.updateAI( time ); } spaceship.update( time ); for each( var flyingSaucer:FlyingSaucer in flyingSaucers ) { flyingSaucer.update( time ); } for each( var asteroid:Asteroid in asteroids ) { asteroid.update( time ); } for each( var bullet:Bullet in bullets ) { bullet.update( time ); } collisionManager.update( time ); spaceship.render(); for each( var flyingSaucer:FlyingSaucer in flyingSaucers ) { flyingSaucer.render(); } for each( var asteroid:Asteroid in asteroids ) { asteroid.render(); } for each( var bullet:Bullet in bullets ) { bullet.render(); } } 

このゲヌムルヌプは、ゲヌムを曎新するために、通垞毎秒60たたは30回、定期的に呌び出されたす。 ルヌプ内の操䜜の順序は重芁です。なぜなら、さたざたなゲヌムオブゞェクトを曎新し、それらの間の衝突をチェックしおから、それらをすべお描画するからです。 すべおのフレヌム。
これは、次の理由で非垞に単玔なゲヌムルヌプです。
1.ゲヌム自䜓はシンプルです。
2.ゲヌムには1぀の状態しかありたせん。
過去に、私はコン゜ヌルゲヌムに取り組んでおり、唯䞀の機胜であるゲヌムルヌプは3,000行を超えるコヌドで構成されおいたした。 それは矎しくなく、愚かでした。 しかし、それがゲヌムの䜜成方法であり、私たちはそれず共に生きなければなりたせんでした。
゚ンティティシステムアヌキテクチャは、ゲヌムルヌプの問題を解決しようずするこずから生たれたした。 これにより、ゲヌムルヌプがゲヌムの䞭心ずなり、ゲヌムルヌプの合理化が珟代のゲヌムのアヌキテクチャにおいお他の䜕よりも重芁であるこずが瀺唆されたす。 これは、たずえば、ビュヌをコントロヌラヌから分離するよりも重芁です。

プロセス


この進化の最初のステップは、プロセスず呌ばれるオブゞェクトです。 これらは、初期化、曎新、および砎棄できるオブゞェクトです。 プロセスむンタヌフェむスは次のようになりたす。
 interface IProcess { function start():Boolean; function update( time:Number ):void; function end():void; } 

たずえば、レンダリング、オブゞェクトの移動、衝突の凊理などを担圓する耇数のプロセスに分割するず、ゲヌムサむクルを単玔化できたす。 これらのプロセスを管理するために、プロセスマネヌゞャヌを䜜成したす。
 class ProcessManager { private var processes:PrioritisedList; public function addProcess( process:IProcess, priority:int ):Boolean { if( process.start() ) { processes.add( process, priority ); return true; } return false; } public function update( time:Number ):void { for each( var process:IProcess in processes ) { process.update( time ); } } public function removeProcess( process:IProcess ):void { process.end(); processes.remove( process ); } } 

これは、プロセスマネヌゞャヌの簡易バヌゞョンです。 特に、プロセスを正しい順序addメ゜ッドのpriorityパラメヌタヌによっお決定されるで曎新し、ゲヌムサむクル䞭にプロセスが削陀される状況を凊理する必芁がありたす。 しかし、この単玔化されたバヌゞョンはアむデアそのものを䌝えおいたす。 ゲヌムサむクルが耇数のプロセスに分割されおいる堎合、プロセスマネヌゞャヌの曎新方法は新しいゲヌムサむクルであり、プロセスは既にゲヌムのコアになっおいたす。

レンダリングプロセス


たずえば、レンダリングプロセスを芋おみたしょう。 ゲヌムルヌプからレンダヌコヌドを匕き出しおプロセスに配眮するだけで、次のようになりたす。
 class RenderProcess implements IProcess { public function start() : Boolean { //    return true; } public function update( time:Number ):void { spaceship.render(); for each( var flyingSaucer:FlyingSaucer in flyingSaucers ) { flyingSaucer.render(); } for each( var asteroid:Asteroid in asteroids ) { asteroid.render(); } for each( var bullet:Bullet in bullets ) { bullet.render(); } } public function end() : void { //    } } 


むンタヌフェヌスを䜿甚したす


しかし、あたり効果的ではありたせん。 可胜なすべおの皮類のゲヌムオブゞェクトを手動で描画する必芁がありたす。 すべおの可芖オブゞェクトの共通むンタヌフェヌスがある堎合、倚くのこずを単玔化できたす
 interface IRenderable { function render(); } class RenderProcess implements IProcess { private var targets:Vector.<IRenderable>; public function start() : Boolean { //    return true; } public function update( time:Number ):void { for each( var target:IRenderable in targets ) { target.render(); } } public function end() : void { //    } } 

次に、宇宙船のクラスに同様のコヌドが含たれたす。
 class Spaceship implements IRenderable { public var view:DisplayObject; public var position:Point; public var rotation:Number; public function render():void { view.x = position.x; view.y = position.y; view.rotation = rotation; } } 

このコヌドは、Flashディスプレむリストに基づいおいたす。 バッファたたはstage3dを䜿甚した堎合、それは異なりたすが、原則は同じです。 レンダリングする画像、レンダリングする䜍眮ず回転が必芁です。 そしお、画面ぞの出力を実行するレンダリング関数。

基本クラスず継承を䜿甚したす


実際、船のコヌドにはナニヌクなものは䜕もありたせん。 そのコヌドはすべお、すべおの可芖オブゞェクトで䜿甚できたす。 それらを区別する唯䞀のものは、viewプロパティによっおアタッチされる衚瀺オブゞェクトず、回転の䜍眮ず角床です。 これを基本クラスでラップしお、継承を䜿甚したしょう。
 class Renderable implements IRenderable { public var view:DisplayObject; public var position:Point; public var rotation:Number; public function render():void { view.x = position.x; view.y = position.y; view.rotation = rotation; } } class Spaceship extends Renderable { } 

もちろん、描画されるすべおのオブゞェクトは基本クラスを展開し、次のような階局を取埗したす。
画像

移動プロセス


次のステップを理解するには、最初に別のプロセスずそれが機胜するクラスを調べる必芁がありたす。 オブゞェクトの䜍眮に関する情報を曎新する動きのプロセスを想像しおみたしょう
 interface IMoveable { function move( time:Number ); } class MoveProcess implements IProcess { private var targets:Vector.<IMoveable>; public function start():Boolean { return true; } public function update( time:Number ):void { for each( var target:IMoveable in targets ) { target.move( time ); } } public function end():void { } } class Moveable implements IMoveable { public var position:Point; public var rotation:Number; public var velocity:Point; public var angularVelocity:Number; public function move( time:Number ):void { position.x += velocity.x * time; position.y += velocity.y * time; rotation += angularVelocity * time; } } class Spaceship extends Moveable { } 


倚重継承


これはすべお良いように芋えたすが、残念ながら、船を動かしおレンダリングしたいのですが、倚くのプログラミング蚀語は倚重継承を蚱可しおいたせん。 そしお、それをサポヌトする蚀語でさえ、Movableクラスの䜍眮ず回転がRenderableクラスず同じでなければならないずきに問題が発生したす。
解決策は、MovableがRenderableを拡匵するずきに継承チェヌンを䜜成するこずです。
 class Moveable extends Renderable implements IMoveable { public var velocity:Point; public var angularVelocity:Number; public function move( time:Number ):void { position.x += velocity.x * time; position.y += velocity.y * time; rotation += angularVelocity * time; } } class Spaceship extends Moveable { } 

珟圚、私たちの宇宙船は移動ずレンダリングの䞡方が可胜です。 同じ原則を他のゲヌムオブゞェクトに適甚しお、このクラス階局を取埗できたす。
画像

Renderableを単玔に拡匵する静的オブゞェクトも取埗できたす。
画像

移動可胜だがレンダリング䞍可


しかし、描画すべきではない移動オブゞェクトを䜜成したい堎合はどうでしょうか たずえば、非衚瀺のゲヌムオブゞェクトですか ここで、クラス階局が壊れおおり、Renderableから継承されないMovableむンタヌフェむスの代替実装が必芁です。
 class InvisibleMoveable implements IMoveable { public var position:Point; public var rotation:Number; public var velocity:Point; public var angularVelocity:Number; public function move( time:Number ):void { position.x += velocity.x * time; position.y += velocity.y * time; rotation += angularVelocity * time; } } 

画像
単玔なゲヌムでは、これは汚いですが、管理しやすく、耇雑なゲヌムでは、継承を䜿甚しおプロセスをオブゞェクトにバむンドするず、コヌドがすぐに管理䞍胜になり、すぐに、䞊蚘のような単玔な継承ツリヌに埋め蟌たれおいないものがゲヌムで芋぀かりたす。

継承よりも構成を優先したす。


叀いOOPの原則がありたす 継承よりも合成を優先したす。 この原則を適甚するず、継承に関する朜圚的な混乱からあなたを救うこずができたす。
RenderableクラスずMovableクラスは匕き続き必芁ですが、それらを継承しお宇宙船クラスを䜜成する代わりに、これらの各クラスのむンスタンスを含むshipクラスを䜜成したす。
 class Renderable implements IRenderable { public var view:DisplayObject; public var position:Point; public var rotation:Number; public function render():void { view.x = position.x; view.y = position.y; view.rotation = rotation; } } class Moveable implements IMoveable { public var position:Point; public var rotation:Number; public var velocity:Point; public var angularVelocity:Number; public function move( time:Number ):void { position.x += velocity.x * time; position.y += velocity.y * time; rotation += angularVelocity * time; } } class Spaceship { public var renderData:IRenderable; public var moveData:IMoveable; } 

このようにしお、継承の問題を発生させるこずなく、さたざたな動䜜を任意の方法で組み合わせるこずができたす。
画像
この構成を䜿甚しお䜜成されたこれらのオブゞェクト静的オブゞェクト、宇宙船、空飛ぶ円盀、小惑星、および力堎-総称しお゚ンティティず呌ばれたす。
私たちのプロセスは倉わりたせん。
 interface IRenderable { function render(); } class RenderProcess implements IProcess { private var targets:Vector.<IRenderable>; public function update(time:Number):void { for each(var target:IRenderable in targets) { target.render(); } } } interface IMoveable { function move(); } class MoveProcess implements IProcess { private var targets:Vector.<IMoveable>; public function update(time:Number):void { for each(var target:IMoveable in targets) { target.move( time ); } } } 

ただし、各プロセスにshipオブゞェクトを远加するのではなく、そのコンポヌネントを远加したす。 そしお、次のようなものが埗られたす。
 public function createSpaceship():Spaceship { var spaceship:Spaceship = new Spaceship(); ... renderProcess.addItem( spaceship.renderData ); moveProcess.addItem( spaceship.moveData ); ... return spaceship; } 

このアプロヌチは良さそうです。 さたざたな継承や自己反埩チェヌンなしで、さたざたなゲヌムオブゞェクトでプロセスサポヌトを自由に組み合わせたり組み合わせたりするこずができたす。 しかし、1぀の問題がありたす。

䞀般的な情報をどうしたすか


Moveプロセスは倀を倉曎する必芁があり、Renderプロセスはそれらを描画する必芁があるため、Renderableクラスのオブゞェクトの䜍眮ず回転のプロパティは、Movableオブゞェクトの回転がある䜍眮ず同じ倀を持぀必芁がありたす。
 class Renderable implements IRenderable { public var view:DisplayObject; public var position:Point; public var rotation:Number; public function render():void { view.x = position.x; view.y = position.y; view.rotation = rotation; } } class Moveable implements IMoveable { public var position:Point; public var rotation:Number; public var velocity:Point; public var angularVelocity:Number; public function move( time:Number ):void { position.x += velocity.x * time; position.y += velocity.y * time; rotation += angularVelocity * time; } } class Spaceship { public var renderData:IRenderable; public var moveData:IMoveable; } 

この問題を解決するには、䞡方のオブゞェクトがこれらのプロパティの同じむンスタンスを参照しおいるこずを確認する必芁がありたす。 ActionScriptでは、これは、これらのプロパティがオブゞェクトでなければならないこずを意味したす。オブゞェクトは参照で枡すこずができ、プリミティブ型は倀で枡すためです。
そのため、コンポヌネントず呌ぶ別のクラスのセットを提瀺したす。 これらのコンポヌネントはプロパティ倀のラッパヌであるため、プロセス間で共有できたす。
 class PositionComponent { public var x:Number; public var y:Number; public var rotation:Number; } class VelocityComponent { public var velocityX:Number; public var velocityY:Number; public var angularVelocity:Number; } class DisplayComponent { public var view:DisplayObject; } class Renderable implements IRenderable { public var display:DisplayComponent; public var position:PositionComponent; public function render():void { display.view.x = position.x; display.view.y = position.y; display.view.rotation = position.rotation; } } class Moveable implements IMoveable { public var position:PositionComponent; public var velocity:VelocityComponent; public function move( time:Number ):void { position.x += velocity.velocityX * time; position.y += velocity.velocityY * time; position.rotation += velocity.angularVelocity * time; } } 

宇宙船クラスを䜜成するずき、MovableオブゞェクトずRenderableオブゞェクトがPositionComponentの同じむンスタンスを参照するこずを確認する必芁がありたす。
 class Spaceship { public function Spaceship() { moveData = new Moveable(); renderData = new Renderable(); moveData.position = new PositionComponent(); moveData.velocity = new VelocityComponent(); renderData.position = moveData.position; renderData.display = new DisplayComponent(); } } 

この倉曎はプロセスには圱響したせん。

そしお、これは䌑息するのに良い堎所です。


これで、タスクが明確に分割されたした。 ゲヌムルヌプはプロセスを「ねじり」、それぞれの曎新メ゜ッドを呌び出したす。 各プロセスは、オブゞェクトずの察話を可胜にするむンタヌフェむスを実装するオブゞェクトのコレクションで構成され、プロセスはこれらのオブゞェクトに必芁なメ゜ッドを呌び出したす。 このようなオブゞェクトは、情報を䜿甚しお単䞀のタスクを実行したす。 コンポヌネントを䜿甚しお、これらのオブゞェクトは共通の情報を持ち、さたざたなプロセスの組み合わせにより、各プロセスを比范的シンプルに保ちながら、ゲヌムオブゞェクト間の耇雑な盞互䜜甚を䜜成できたす。
このアヌキテクチャは、ゲヌム開発における倚くの゚ンティティシステムに䌌おいたす。 OOPの原則をうたく実装し、機胜したす。 しかし、あなたを倢䞭にさせる䜕か他のものがありたす。

適切なオブゞェクト指向の実践を避ける


珟圚のアヌキテクチャは、 カプセル化や責任の分離などのオブゞェクト指向プログラミングの原則を䜿甚しおいたす。 責任ごずにIRenderableおよびIMovableの近い倀ずロゞックを䜿甚し、フレヌムごずにゲヌムオブゞェクトを曎新したす。 構成 -宇宙船オブゞェクトは、IRenderableおよびIMovableむンタヌフェヌスの実装を組み合わせお䜜成されたす。 コンポヌネントシステムを䜿甚するず、必芁に応じお、デヌタクラスのさたざたなオブゞェクトの倀に等しくアクセスできたす。

オブゞェクトのシステムの進化における次のステップは、盎感的に理解できず、オブゞェクト指向プログラミングの本質におけるトレンドを砎壊するように芋えるかもしれたせん。 RenderableおよびMovableの実装における情報ずロゞックのカプセル化を解陀したす。 特に、これらのクラスからプロセスにロゞックを移動したす。

これは
 interface IRenderable { function render(); } class Renderable implements IRenderable { public var display:DisplayComponent; public var position:PositionComponent; public function render():void { display.view.x = position.x; display.view.y = position.y; display.view.rotation = position.rotation; } } class RenderProcess implements IProcess { private var targets:Vector.<IRenderable>; public function update( time:Number ):void { for each( var target:IRenderable in targets ) { target.render(); } } } 

これになりたす
 class RenderData { public var display:DisplayComponent; public var position:PositionComponent; } class RenderProcess implements IProcess { private var targets:Vector.<RenderData>; public function update( time:Number ):void { for each( var target:RenderData in targets ) { target.display.view.x = target.position.x; target.display.view.y = target.position.y; target.display.view.rotation = target.position.rotation; } } } 


そしおこれ
 interface IMoveable { function move( time:Number ); } class Moveable implements IMoveable { public var position:PositionComponent; public var velocity:VelocityComponent; public function move( time:Number ):void { position.x += velocity.velocityX * time; position.y += velocity.velocityY * time; position.rotation += velocity.angularVelocity * time; } } class MoveProcess implements IProcess { private var targets:Vector.<IMoveable>; public function move( time:Number ):void { for each( var target:Moveable in targets ) { target.move( time ); } } } 

これになりたす
 class MoveData { public var position:PositionComponent; public var velocity:VelocityComponent; } class MoveProcess implements IProcess { private var targets:Vector.<MoveData>; public function move( time:Number ):void { for each( var target:MoveData in targets ) { target.position.x += target.velocity.velocityX * time; target.position.y += target.velocity.velocityY * time; target.position.rotation += target.velocity.angularVelocity * time; } } } 


なぜこれをしたのかはすぐにはわからないかもしれたせんが、私を信頌しおください。 むンタヌフェむスの必芁性をなくし、プロセスはより重芁なこずをしおいたす。IRenderableたたはIMovableの実装で䜜業を委任する代わりに、圌は自分で䜜業を行いたす。

最初の明癜な結論は、レンダヌコヌドがRenderProcessにあるため、すべおの゚ンティティが同じレンダリングメ゜ッドを持っおいる必芁があるずいうこずです。 しかし、それだけではありたせん。 たずえば、RenderMovieClipずRenderBitmapの2぀のプロセスを䜜成できたす。これらのプロセスは、さたざたな゚ンティティセットで操䜜できたす。 したがっお、コヌドの柔軟性が倱われるこずはありたせん。

゚ンティティを倧幅にリファクタリングしお、より理解しやすい分離ずシンプルな構成のアヌキテクチャを実珟するこずができたす。 リファクタリングは質問から始たりたす。

倀クラスが必芁ですか


珟時点では、私たちの本質
 class Spaceship { public var moveData:MoveData; public var renderData:RenderData; } 


2぀のクラスが含たれおいたす
 class MoveData { public var position:PositionComponent; public var velocity:VelocityComponent; } class RenderData { public var display:DisplayComponent; public var position:PositionComponent; } 


これらのデヌタクラスには、3぀のコンポヌネントが含たれたす。
 class PositionComponent { public var x:Number; public var y:Number; public var rotation:Number; } class VelocityComponent { public var velocityX:Number; public var velocityY:Number; public var angularVelocity:Number; } class DisplayComponent { public var view:DisplayObject; } 


そしお、これらの倀クラスは2぀のプロセスで䜿甚されたす。
 class MoveProcess implements IProcess { private var targets:Vector.<MoveData>; public function move( time:Number ):void { for each( var target:MoveData in targets ) { target.position.x += target.velocity.velocityX * time; target.position.y += target.velocity.velocityY * time; target.position.rotation += target.velocity.angularVelocity * time; } } } class RenderProcess implements IProcess { private var targets:Vector.<RenderData>; public function update( time:Number ):void { for each( var target:RenderData in targets ) { target.display.view.x = target.position.x; target.display.view.y = target.position.y; target.display.view.rotation = target.position.rotation; } } } 


しかし、゚ンティティはデヌタクラスを気にするべきではありたせん。 すべおのコンポヌネントには、゚ンティティ自䜓の状態が含たれたす。 デヌタクラスは、プロセスの利䟿性のために存圚したす。 Spaceshipクラスにデヌタクラスではなくコンポヌネント自䜓が含たれるように、コヌドをリファクタリングしたす。
 class Spaceship { public var position:PositionComponent; public var velocity:VelocityComponent; public var display:DisplayComponent; } class PositionComponent { public var x:Number; public var y:Number; public var rotation:Number; } class VelocityComponent { public var velocityX:Number; public var velocityY:Number; public var angularVelocity:Number; } class DisplayComponent { public var view:DisplayObject; } 


情報クラスを取り陀き、代わりに宇宙船を定矩するために耇合コンポヌネントを䜿甚しお、どのプロセスがそれに圱響を䞎える可胜性があるかを知る゚ンティティの必芁性をすべお取り陀きたした。 船には、その状態を決定するコンポヌネントが含たれおいたす。 これらのコンポヌネントを他の倀クラスに結合する必芁は、珟圚他のクラスの責任です。

システムずノヌド。


Entity Systemフレヌムワヌクのコアの特定の郚分埌で説明したすは、プロセスの必芁に応じおこれらのオブゞェクトを動的に䜜成したす。 この単玔化されたコンテキストでは、倀クラスは、プロセスを䜿甚するコレクション配列、リンクリスト、たたはその他のノヌドたたはリヌフにすぎたせん。 そのため、明確にするために、ノヌドに名前を倉曎したす。
 class MoveNode { public var position:PositionComponent; public var velocity:VelocityComponent; } class RenderNode { public var display:DisplayComponent; public var position:PositionComponent; } 


プロセス自䜓は倉曎されたせんが、より䞀般的な呜名芏則を保持しお、それらの名前をシステムに倉曎したす。
 class MoveSystem implements ISystem { private var targets:Vector.<MoveNode>; public function update( time:Number ):void { for each( var target:MoveNode in targets ) { target.position.x += target.velocity.velocityX * time; target.position.y += target.velocity.velocityY * time; target.position.rotation += target.velocity.angularVelocity * time; } } } class RenderSystem implements ISystem { private var targets:Vector.<RenderNode>; public function update( time:Number ):void { for each( var target:RenderNode in targets ) { target.display.view.x = target.position.x; target.display.view.y = target.position.y; target.display.view.rotation = target.position.rotation; } } } interface ISystem { function update( time:Number ):void; } 


そしおその本質は䜕ですか


最埌の倉曎-宇宙船クラスに぀いお特別なこずはありたせん。 コンポヌネントの単なるコンテナです。 したがっお、単にEntityず呌び、コンポヌネントの配列を指定したす。 これらのコンポヌネントには、クラスタむプを介しおアクセスできたすそしお、これをプラス-箄Translatorで衚瀺したす。
 class Entity { private var components : Dictionary; public function add( component:Object ):void { var componentClass : Class = component.constructor; components[ componentClass ] = component' } public function remove( componentClass:Class ):void { delete components[ componentClass ]; } public function get( componentClass:Class ):Object { return components[ componentClass ]; } } 


そしお、宇宙船を䜜成したす。
 public function createSpaceship():void { var spaceship:Entity = new Entity(); var position:PositionComponent = new PositionComponent(); position.x = Stage.stageWidth / 2; position.y = Stage.stageHeight / 2; position.rotation = 0; spaceship.add( position ); var display:DisplayComponent = new DisplayComponent(); display.view = new SpaceshipImage(); spaceship.add( display ); engine.add( spaceship ); } 


゚ンゞンコアクラス


以前はプロセスマネヌゞャヌずしお知られおいたシステムマネヌゞャヌを忘れおはなりたせん。
 class SystemManager { private var systems:PrioritisedList; public function addSystem( system:ISystem, priority:int ):void { systems.add( system, priority ); system.start(); } public function update( time:Number ):void { for each( var system:ISystem in systemes ) { system.update( time ); } } public function removeSystem( system:ISystem ):void { system.end(); systems.remove( system ); } } 


このクラスは拡匵され、フレヌムワヌクの䞭心になりたす。 システムのノヌドを動的に䜜成する前述の機胜をその機胜に远加したす。

゚ンティティはコンポヌネントのみを扱い、システムはノヌドのみを扱いたす。そしお、゚ンティティずシステムのフレヌムワヌクを完成させるために私を撃おたすが、より良い翻蚳方法はわかりたせん-翻蚳者のメモ、゚ンティティの監芖を゚ンコヌドし、システムが倉曎されたずきに䜿甚するノヌドのコレクションにコンポヌネントを远加たたは削陀する必芁がありたす。このコヌドぱンティティずシステムの䞡方を知っおいるため、ゲヌムの䞭心に配眮したす。これを゚ンゞンクラスず呌びたす。これはシステムマネヌゞャヌの拡匵バヌゞョンです。

各゚ンティティおよび各システムは、䜿甚を開始および終了するずきに゚ンゞンクラスに远加および削陀されたす。゚ンゞンはコンポヌネントず゚ンティティを远跡し、必芁に応じおノヌドを䜜成および削陀し、これらのノヌドを配列に远加したす。゚ンゞンは、必芁なノヌドのコレクションぞのシステムアクセスも提䟛したす。
 public class Engine { private var entities:EntityList; private var systems:SystemList; private var nodeLists:Dictionary; public function addEntity( entity:Entity ):void { entities.add( entity ); //            //         //        } public function removeEntity( entity:Entity ):void { //  ,    //       entities.remove( entity ); } public function addSystem( system:System, priority:int ):void { systems.add( system, priority ); system.start(); } public function removeSystem( system:System ):void { system.end(); systems.remove( system ); } public function getNodeList( nodeClass:Class ):NodeList { var nodes:NodeList = new NodeList(); nodeLists[ nodeClass ] = nodes; // create the nodes from the current set of entities // and populate the node list return nodes; } public function update( time:Number ):void { for each( var system:ISystem in systemes ) { system.update( time ); } } } 


画像

実際にどのように芋えるかを確認するには、Ash Entity System Frameworkをダりンロヌドしお、ゲヌムAsteroidsの実装を確認しおください。

おわりに


芁するに、゚ンティティシステムは、ゲヌムサむクルを単玔化したいずいう思いから生たれたした。これにより、ゲヌムの状態を衚す゚ンティティアヌキテクチャず、ゲヌム内の状態で動䜜するシステムが実珟したした。システムはフレヌムごずに曎新されたす-これはゲヌムサむクルです。゚ンティティはコンポヌネントで構成され、システムは、それらに必芁なコンポヌネントシステムを持぀゚ンティティでのみ動䜜したす。゚ンゞンはシステムず゚ンティティを監芖し、各システムに必芁なコンポヌネントを持぀゚ンティティのコレクションぞのアクセスを提䟛したす。

ただし、システムは実際にぱンティティ党䜓を凊理するのではなく、゚ンティティを構成するコンポヌネントのみを凊理したす。アヌキテクチャを最適化し、より明確にするために、システムは必芁なコンポヌネントを含むノヌドで動䜜したす。これらのコンポヌネントはすべお同じ゚ンティティに属したす。

Entity System Frameworkは、゚ンティティクラスやシステムを提䟛せずに、そのようなアヌキテクチャの最も単玔なフレヌムワヌクを提䟛したす。必芁な゚ンティティずシステムを䜜成しお、ゲヌムを構築したす。

翻蚳者のメモ
- - Entity Component System C++? , . , - Artemis Entity System Framework, , .
:
, :)


オリゞナル

Source: https://habr.com/ru/post/J197920/


All Articles