死んだエンティティをゲームループから削除するにはどうすればよいですか?


16

わかりましたので、ループして更新するすべてのエンティティの大きなリストがあります。AS3では、これを配列(動的な長さ、型なし)、ベクター(型付き)、またはリンクリスト(ネイティブではない)として保存できます。現時点では配列を使用していますが、より高速な場合はベクターまたはリンクリストに変更する予定です。

とにかく、私の質問、エンティティが破壊された場合、リストからエンティティを削除するにはどうすればよいですか?その位置を無効にするか、スプライスアウトするか、フラグを立てて「私をスキップして、死んだ」と言うことができます。私はエンティティをプールしているので、死んだエンティティはある時点で再び生きる可能性が高いです。コレクションの種類ごとに、私の最善の戦略は何ですか?また、コレクションの種類と削除方法のどの組み合わせが最適ですか?


ベクターの長さは固定されていませんが、入力されているため、配列よりも優れています。欠点は、事前に入力されたリストを定義するための簡単な構文がないことですが、それは必要ないと思います。
バートヴァンヒューケロム

回答:


13

すべての追加/削除を別々のリストに保存し、更新ループを繰り返した後にこれらの操作を実行します。


10

Flixelフレームワークは、デッドフラグ(実際には、描画、更新などを行うかどうかを決定するいくつかのフラグ)を使用します。エンティティを復活させる場合、およびパフォーマンスが問題になる場合は、デッドフラグを使用すると言います。私の経験では、新しいエンティティをインスタンス化することは、説明するユースケースで最もコストのかかる操作であり、Flashのときどきガベージコレクションを考えると、要素のスプライシングまたはヌル化はメモリの膨張につながる可能性があります。


1
フリクセルの+1。リサイクルするdeadことはパフォーマンスに本当に役立ちます。
スノーブラインド

3

一部の手法は本質的に他の手法よりも効率的ですが、ターゲットプラットフォームでサイクルが不足している場合にのみ問題になります。どんなテクニックを使っても、ゲームをより速く仕上げることができます。それまでは、コンテナデータ構造の特定の実装に依存しないようにしてください。そうすれば、必要に応じて後で最適化するのに役立ちます。

ここで他の人が既に議論したテクニックのいくつかに対処するために エンティティの順序が重要な場合、デッドフラグを使用すると、次のフレームの更新ループ中にスプライスできます。例えば。非常に単純な擬似コード:

void updateGame()
{
  // updateEntities()
  Entity* pSrcEntity = &mEntities[0];
  Entity* pDstEntity = &mEntities[0];
  newNumEntities = 0;
  for (int i = 0; i < numEntities; i++)
  {
    if (!pSrcEntity->isDead)
    {
       // could be inline but whatever.
       updateEntity(pDstEntity, pSrcEntity);
       // if entity just died, don't update the pDstEntity pointer, 
       // and just let the next entity updated overwrite it.
       if (!pDstEntity->isDead)
       {
          pDstEntity++;
          newNumEntities++;
       }
    }
    pSrcEntity++;
  }
}
numEntities = newNumEntities;

これらはこのスキームの特徴です:

  • エンティティの自然なコンパクトさ(ただし、エンティティスロットを回収できるまでに1フレームの遅延が発生する可能性があります)。
  • ランダムな並べ替えの問題はありません。
  • 二重リンクリストにはO(1)の挿入/削除がありますが、最適なキャッシュレイテンシの非表示のためにプリフェッチするのは非常に困難です。それらをコンパクトな配列に保持することにより、ブロックのプリフェッチ手法をうまく機能させることができます。
  • 複数オブジェクトを破棄する場合、順序とコンパクトさを維持するために冗長なシフトコピーを行う必要はありません(すべて更新パス中に1回行われます)
  • 更新中に既にキャッシュにある必要があるデータに触れることを利用します。
  • 送信元と送信先のエンティティポイトナーが配列を分離する場合に有効です。その後、マルチコアを活用するためにエンティティ配列をダブルバッファリングできます。1つのスレッドはフレームNのエンティティを更新/書き込み、別のスレッドはフレームN-1の前のフレームのエンティティをレンダリングします。
  • コンパクトさは、より多くのCPU作業をオフロードするために、異種プロセッサにロット全体をDMAすることが簡単であることを意味します。SPUまたはGPU。

+1。私はこれが好き。プール内で更新を注文する必要はほとんどありませんが、状況に
遭遇

2

私の一般的なプログラミングの経験から言えば、スプライシングは通常、すべての既存の要素を1つ上にシフトすることを伴う遅い操作です。ここでは、nullに設定するのが最善の解決策になると思います。デッドフラグは機能しますが、コードを混乱させないように注意する必要があります。

しかし、実際にはチャットルームでのリソースプーリングについて話していました。それは非常に良い習慣であり、あなたがそうしていると聞いて良いことです。:)


1
更新順序が重要でない場合、スプライシングは最後のエンティティを現在のインデックスに移動し、プールカウントとイテレータインデックスを減らすだけの簡単なものにする必要があります。
カイ

うわー、非常に良い点カイ!:)
Ricket

2

個人的には、リンクリストを使用します。いいね!リストの繰り返しは高速で、アイテムの追加と削除も高速です。配列内のアイテムへの直接アクセス(インデックスへのアクセスなど)が必要な場合は、ArrayまたはVectorを使用することをお勧めしますが、必要なようには思えません。

リンクされたリストからアイテムを削除するときはいつでも、オブジェクトのプールにアイテムを追加できます。オブジェクトのプールは、メモリの割り当てを節約するためにリサイクルできます。

私はいくつかのプロジェクトで多角形のデータ構造を使用し、それらに非常に満足しています。

編集:申し訳ありませんが、削除戦略の面では答えはあまり明確ではなかったと思います:アイテムが死んだらすぐにリストから削除し、プール構造に直接追加することをお勧めします(リサイクル)。リンクされたリストからアイテムを削除することは非常に効率的であるため、そうすることに問題はありません。


1
ここで二重リンクリストを提案すると思いますか?(進む/戻る)?また、リンク要素を介して何らかのプールを提案していますか、またはリンクリストの各ポインターホルダーを動的に割り当てていますか?
サイモン

はい、それはそのタスクに最も適した二重リンクリストでなければなりません。それを指摘してくれてありがとう!アイテムの再利用に関して:専用のプールクラス/データ構造、つまり、オンデマンドで新しいオブジェクトを作成するか、プールに存在する場合は既存のインスタンスを使用することを考えていました。したがって、実際にリストから「デッド」アイテムを削除し、後で使用するためにプールに追加することをお勧めします。
-bummzack

単独でリンクされたリストは問題ありません。二重にリンクされたリストは、双方向で反復するという利点のみを提供します。現在のアイテムを削除するオプションを使用して、単一リンクリストを反復処理するには、前のエントリを追跡する必要があります。
deft_code

@caspinはい、正確に。単一リンクリストを使用している場合は、以前のノードを追跡nextし、削除されたノードの後にそれらのポインターをノードにリンクする必要があります。これを自分で行うのが面倒な場合は、二重リンクリストが最適なDataStructureになります。
-bummzack

1

「フラグを立てて「スキップして、死んだ」と言うだけです。エンティティをプールしているので、死んだエンティティはある時点で再び生きる可能性が高いです」

この特定のアプリケーションに関して、あなたはあなた自身の質問に答えたと思います。プッシュアンドポップ以外の作業を行う予定がある場合は、配列から離れます。リンクリストは、重い操作を行う予定がある場合に、よりスマートな方法です。とは言っても、同じエンティティをゲームに再統合する場合は、ブール演算変数を設定して、ゲーム操作ループ中にチェックするだけです。


0

使用したlibで見つかったクリーンで一般的なソリューションの1つは、ロック可能なマップを使用することでした。

2つの操作がlock()ありunlock()、マップを反復処理するときにlock()、この時点からマップを変更するすべての操作は有効にならず、CommandQueueを呼び出すと実行されるにプッシュされますunlock()

したがって、エンティティを削除すると、次の擬似コードが作成されます。

void lockableMap::remove(std::string id) {
   if(isLocked) {
       commandQueue.add(new RemoveCommand(id));
   } else {
       //remove element from map
   }

そしてあなたが unlock()

isLocked = false
commandQueue.execute(this);

考慮しなければならないことは、ループの後でのみエンティティを削除することです。

編集:これはサイモンによって提案されたソリューションです。



0

2つの方法があります。

削除するオブジェクトを呼び出すと、実際には2つのフラグが設定されます。

1.オブジェクトが削除されたことをコンテナに伝えるもの

2.削除するように要求されたオブジェクトをコンテナに伝えるもの

void object::deleteObject()
{
    container->objectHasBeenDeleted = true;
    isToDelete = true;
}

一つの オブジェクトのベクトルを使用して

std::vector<object*> objects;

次に、更新機能で、オブジェクトが削除されているかどうかを確認し、削除されている場合はすべてのオブジェクトを反復処理し、削除フラグのあるオブジェクトを削除します

void container::update()
{
    if (objectHasBeenDeleted)
    {
        std::vector<object*>::iterator ListIterator;
        for(ListIterator=objects.begin(); ListIterator!=objects.end();)
        {
            if( (*ListIterator)->isToDelete )
            {
                ListIterator = objects.erase(ListIterator);
                delete *ListIterator;
            }
            else {
                ++ListIterator;
            }
        }
    objectHasBeenDeleted = false;
    }
}

二つの オブジェクトのベクトル(へのポインタ)を使用。

std::vector<object*> *objects;

更新機能で、オブジェクトを削除する場合は、オブジェクトを繰り返し処理し、削除しないオブジェクトを新しいベクトルに追加します。オブジェクトのベクターを削除し、ポインターを新しいベクターに設定します

void container::update()
{
    if (objectHasBeenDeleted)
    {
        std::vector<object*> *newVector;
        unsigned long i;
        for (i = 0; i < objects->size(); i++)
        {
            if (!objects->at(i)->isToDelete)
            {
                newVector->push_back(objects->at(i));
            }
        }
        delete objects;
        objects = newVector;
        objectHasBeenDeleted = false;
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.