エンティティシステムのバリアントを実装しています。
Entityクラス IDより少しであることが結合コンポーネント一緒に
「コンポーネントロジック」がなく、データのみを持つコンポーネントクラスの束
多数のシステムクラス(別名「サブシステム」、「マネージャー」)。これらはすべてのエンティティロジック処理を行います。ほとんどの基本的な場合、システムは、関心のあるエンティティのリストを反復処理し、各エンティティに対してアクションを実行するだけです。
MessageChannelクラスオブジェクトのすべてのゲームシステムで共有されています。各システムは、特定のタイプのメッセージをサブスクライブしてリッスンし、チャネルを使用して他のシステムにメッセージをブロードキャストすることもできます
システムメッセージ処理の最初のバリアントは、次のようなものでした。
- 各ゲームシステムで順次更新を実行する
システムがコンポーネントに対して何かを実行し、そのアクションが他のシステムにとって重要である場合、システムは適切なメッセージを送信します(たとえば、システム呼び出し
messageChannel.Broadcast(new EntityMovedMessage(entity, oldPosition, newPosition))
エンティティが移動されるたびに)
特定のメッセージをサブスクライブした各システムは、そのメッセージ処理メソッドを取得します
システムがイベントを処理しており、イベント処理ロジックで別のメッセージをブロードキャストする必要がある場合、メッセージはすぐにブロードキャストされ、メッセージ処理メソッドの別のチェーンが呼び出されます
このバリアントは、衝突検出システムの最適化を開始するまでは問題ありませんでした(エンティティの数が増えると、本当に遅くなりました)。最初は、単純なブルートフォースアルゴリズムを使用して各エンティティペアを繰り返します。次に、特定のセルの領域内にあるエンティティを格納するセルのグリッドを持つ「空間インデックス」を追加しました。これにより、隣接セルのエンティティのみをチェックできます。
エンティティが移動するたびに、衝突システムはエンティティが新しい位置にあるものと衝突しているかどうかを確認します。そうである場合、衝突が検出されます。衝突するエンティティが両方とも「物理オブジェクト」である場合(両方にRigidBodyコンポーネントがあり、同じスペースを占有しないように互いを押しのけようとしている場合)、専用の剛体分離システムがエンティティを移動するように移動システムに要求しますそれらを分離する特定の位置。これにより、移動システムは変更されたエンティティの位置を通知するメッセージを送信します。衝突検出システムは、空間インデックスを更新する必要があるため、反応するように設計されています。
場合によっては、セルの内容(C#のEntityオブジェクトの汎用リスト)が繰り返し処理されている間に変更され、イテレーターによって例外がスローされるため、問題が発生します。
だから... 衝突をチェックしている間に衝突システムが中断されるのを防ぐにはどうすればよいですか?
もちろん、セルの内容が正しく繰り返されることを保証する「巧妙な」/「トリッキーな」ロジックを追加することもできますが、問題は衝突システム自体にあるのではないと思います(他のシステムにも同様の問題がありました)メッセージはシステムからシステムへ移動するときに処理されます。私が必要とするのは、特定のイベント処理メソッドが中断することなく仕事を確実に行うための何らかの方法です。
私が試したもの:
- 着信メッセージキュー。あるシステムがメッセージをブロードキャストするたびに、そのメッセージは関心のあるシステムのメッセージキューに追加されます。これらのメッセージは、システム更新が各フレームで呼び出されると処理されます。問題:システムAがシステムBのキューにメッセージを追加する場合、システムBがシステムAよりも後(同じゲームフレーム内)に更新される場合、それはうまく機能します。それ以外の場合、メッセージが次のゲームフレームを処理します(一部のシステムでは望ましくありません)
- 発信メッセージキュー。システムがイベントを処理している間、システムがブロードキャストするメッセージは送信メッセージキューに追加されます。メッセージは、システムの更新が処理されるのを待つ必要はありません。最初のメッセージハンドラが処理を完了した後、「すぐに」処理されます。メッセージの処理によって他のメッセージがブロードキャストされる場合、それらも発信キューに追加されるため、すべてのメッセージが同じフレームで処理されます。問題:エンティティライフタイムシステム(システムでエンティティライフタイム管理を実装)がエンティティを作成した場合、いくつかのシステムAとBに通知します。システムAがメッセージを処理している間、メッセージチェーンが発生し、最終的に作成されたエンティティが破棄されます(たとえば、弾丸エンティティが障害物と衝突する場所で作成され、弾丸が自己破壊します)。メッセージチェーンが解決されている間、システムBはエンティティ作成メッセージを取得しません。したがって、システムBがエンティティ破壊メッセージにも関心がある場合、システムBはそれを取得し、「チェーン」の解決が完了した後にのみ、最初のエンティティ作成メッセージを取得します。これにより、破棄メッセージは無視され、作成メッセージは「受け入れられます」、
編集-質問への回答、コメント:
- 衝突システムがセルの内容を繰り返し処理している間、誰がセルの内容を変更しますか?
衝突システムが一部のエンティティとその近隣で衝突チェックを行っている間、衝突が検出される可能性があり、エンティティシステムは他のシステムがすぐに反応するメッセージを送信します。メッセージに対する反応により、他のメッセージが作成され、すぐに処理される場合があります。そのため、他のシステムは、以前の衝突チェックがまだ終了していない場合でも、衝突システムがすぐに処理する必要があるメッセージを作成する場合があります(たとえば、衝突システムが空間インデックスを更新する必要があるため、エンティティを移動します)。
- グローバルな送信メッセージキューを使用できませんか?
最近、単一のグローバルキューを試しました。新しい問題を引き起こします。問題:タンクエンティティを壁エンティティに移動します(タンクはキーボードで制御されます)。それからタンクの方向を変えることにしました。タンクと壁を各フレームに分離するために、CollidingRigidBodySeparationSystemはタンクを壁から可能な限り最小の距離だけ移動します。分離方向は、戦車の移動方向の反対方向でなければなりません(ゲームの描画が開始されると、戦車は壁に移動したことがないように見えるはずです)。しかし、方向はNEW方向とは逆になり、タンクを壁の最初とは異なる側に移動します。問題が発生する理由:これがメッセージの処理方法です(簡略化されたコード):
public void Update(int deltaTime)
{
m_messageQueue.Enqueue(new TimePassedMessage(deltaTime));
while (m_messageQueue.Count > 0)
{
Message message = m_messageQueue.Dequeue();
this.Broadcast(message);
}
}
private void Broadcast(Message message)
{
if (m_messageListenersByMessageType.ContainsKey(message.GetType()))
{
// NOTE: all IMessageListener objects here are systems.
List<IMessageListener> messageListeners = m_messageListenersByMessageType[message.GetType()];
foreach (IMessageListener listener in messageListeners)
{
listener.ReceiveMessage(message);
}
}
}
コードは次のように流れます(最初のゲームフレームではないと仮定しましょう)。
- システムは TimePassedMessageの処理を開始します
- InputHandingSystemはキーの押下をエンティティアクションに変換します(この場合、左矢印はMoveWestアクションに変わります)。エンティティアクションはActionExecutorコンポーネントに保存されます
- ActionExecutionSystemは、エンティティアクションに対応して、MovementDirectionChangeRequestedMessageをメッセージキューの最後に追加します
- MovementSystemは、Velocityコンポーネントデータに基づいてエンティティの位置を移動し、PositionChangedMessageメッセージをキューの最後に追加します。移動は、前のフレームの移動方向/速度を使用して行われます(北に向かってみましょう)
- システムは TimePassedMessageの処理を停止します
- システムは MovementDirectionChangeRequestedMessageの処理を開始します
- MovementSystemは、要求に応じてエンティティの速度/移動方向を変更します
- システムは MovementDirectionChangeRequestedMessageの処理を停止します
- システムは PositionChangedMessageの処理を開始します
- CollisionDetectionSystemは、エンティティが移動したために別のエンティティにぶつかったことを検出します(タンクが壁の内側に入りました)。CollisionOccuredMessageをキューに追加します
- システムは PositionChangedMessageの処理を停止します
- システムは CollisionOccuredMessageの処理を開始します
- CollidingRigidBodySeparationSystemは、タンクと壁を分離することで衝突に反応します。壁は静止しているため、タンクのみが移動します。戦車の移動方向は、戦車がどこから来たかを示す指標として使用されます。反対方向にオフセットされています
BUG:戦車がこのフレームを移動したとき、前のフレームからの移動方向を使用して移動しましたが、分離されたときは、このフレームからの移動方向が使用されていました。それはそれがどのように機能すべきかではありません!
このバグを防ぐには、古い移動方向をどこかに保存する必要があります。この特定のバグを修正するためだけにコンポーネントに追加することもできますが、このケースはメッセージを処理する根本的に間違った方法を示していませんか?分離システムが使用する移動方向を考慮する必要があるのはなぜですか?この問題をエレガントに解決するにはどうすればよいですか?
- gamadu.com/artemisを読んで、Aspectsで何をしたかを確認することをお勧めします。
実際、私はかなり長い間、アルテミスに精通しています。ソースコードを調べたり、フォーラムを読んだりします。しかし、「アスペクト」が言及されているのはごく少数の場所でしか見ていません。理解できる限り、それらは基本的に「システム」を意味します。しかし、Artemisが私の問題のいくつかをどのように踏んでいるかはわかりません。メッセージも使用しません。
- 参照:「エンティティ通信:メッセージキューvsパブリッシュ/サブスクライブvsシグナル/スロット」
エンティティシステムに関するgamedev.stackexchangeの質問はすべて読んでいます。これは私が直面している問題を議論していないようです。何か不足していますか?
- 2つのケースを別々に処理します。グリッドの更新は衝突システムの一部であるため、移動メッセージに依存する必要はありません。
どういう意味かわかりません。CollisionDetectionSystemの古い実装では、更新時に衝突をチェックするだけでした(TimePassedMessageが処理されたとき)が、パフォーマンスのためにチェックを最小限に抑える必要がありました。そこで、エンティティが移動したときに衝突チェックに切り替えました(ゲーム内のほとんどのエンティティは静的です)。