Bir varlığı öldüğünde oyun döngümden en iyi nasıl kaldırabilirim?


16

Tamam, ben üzerinden döngü ve güncelleme tüm varlıkları büyük bir listesi var. AS3'te bunu Array (dinamik uzunluk, türsüz), Vector (yazılan) veya bağlantılı bir liste (yerel değil) olarak saklayabilirim. Şu anda Array kullanıyorum ancak daha hızlıysa Vector veya bağlantılı listeye geçmeyi planlıyorum.

Her neyse, sorum, bir varlık yok edildiğinde, onu listeden nasıl kaldırmalıyım? Konumunu geçersiz kılabilir, ekleyebilir ya da üzerine "atla, ölüyüm" diyen bir bayrak koyabilirdim. Varlıklarımı birleştiriyorum, bu yüzden ölü bir Varlığın bir noktada tekrar hayatta olması muhtemeldir. Her bir toplama türü için en iyi stratejim nedir ve hangi toplama türü ve kaldırma yönteminin kombinasyonu en iyi şekilde çalışır?


Bir Vector uzunluğu sabit değildir, ancak yazılmıştır ve bu da onu Array üzerinde üstün kılar. Dezavantajı, önceden doldurulmuş bir liste tanımlamak için hızlı bir sözdizimi yoktur, ancak bence gerekmez.
Bart van Heukelom

Yanıtlar:


13

Tüm ekleme / kaldırmaları ayrı listelerde saklar ve güncelleme döngüsünü yineledikten sonra bu işlemleri yaparım.


10

Flixel çerçevesi ölü bayrağı kullanır (aslında çizilmesi, güncellenmesi vb. Gerektiğini belirleyen birkaç bayrak). Varlıkları canlandıracaksanız ve performans bir sorunsa, ölü bayrağı kullandığınızı söyleyebilirim. Deneyimlerime göre, yeni varlıkları başlatmak, tarif ettiğiniz kullanım durumunda en pahalı işlemdir ve Flash'ın bazen squirrely çöp toplanması göz önüne alındığında, ekleme veya boşaltma öğeleri bellek şişmesine neden olabilir.


1
Flixel için +1. Geri dönüşüm deadgerçekten performansa yardımcı olur.
Snow Blind

3

Bazı teknikler doğal olarak diğerlerinden daha verimli olsa da, yalnızca hedef platformunuzdaki döngülerin bitmesi önemli olacaktır. Oyununuzu daha hızlı yapmanıza izin veren tekniği kullanın. Bu arada kap veri yapılarınızın özel uygulanmasına güvenmemeye çalışın ve ihtiyacınız olduğunda daha sonra optimizasyon yapmanıza yardımcı olur.

Sadece burada başkaları tarafından tartışılan bazı teknikleri ele almak için. Varlıkların sırası önemliyse, ölü bayrak bir sonraki karedeki güncelleme döngünüz sırasında ekleme yapmanızı sağlayabilir. Örneğin. çok basit sözde kod:

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;

Bunlar bu şemanın özellikleri:

  • varlıkların doğal kompaktlığı (bir varlık yuvasının geri kazanılmasından önce 1 kare gecikme olsa da).
  • rastgele yeniden sıralama sorunları yok.
  • iki kez Bağlantılı listelerde O (1) ekleme / silme bulunur, ancak en iyi önbellek gecikme gizlemesi için önceden getirilmesi çok zordur. Bunları kompakt bir dizide tutmak blok ön getirme tekniklerinin iyi çalışmasını sağlar.
  • Çok nesneli imha durumunda, düzeni ve kompaktlığı korumak için gereksiz vardiya kopyaları yapmanız gerekmez (hepsi güncelleme geçişi sırasında bir kez yapılır)
  • Güncelleme sırasında zaten önbellekte olması gereken verilere dokunmaktan faydalanırsınız.
  • Kaynak ve hedef varlıklarınız, dizileri ayırmak için iyi çalışır. Daha sonra çok çekirdekli / örneğin avantajlarından yararlanmak için varlık dizilerinizi iki kez arabelleğe alabilirsiniz. bir iş parçacığı N karesi için varlıkları güncelleştirir / yazar, diğer bir iş parçacığı ise N-1 karesi için önceki karenin varlıklarını görüntüler.
  • Kompaktlık, daha da fazla CPU iş yükü için örn. SPU'lar veya GPU'lar.

+1. Bunu severim. Bir havuz içinde güncellemeler sipariş etmek gerekmemesine rağmen, duruma
girersem

2

Genel programlama deneyimime göre konuşma, ekleme, genellikle mevcut tüm öğelerin bire kaydırılmasını içeren yavaş bir işlemdir. Ben null olarak ayarlamak burada en iyi çözüm olacağını düşünüyorum ; ölü bir bayrak işe yarayacaktır, ancak kodunuzu dağınık hale getirmemesine dikkat etmeniz gerekir.

Aslında aslında sohbet odasında kaynak havuzu hakkında konuşuyorduk aslında. Bu çok iyi bir uygulama ve bunu yaptığınızı duymak güzel. :)


1
Güncelleme sırası önemli değilse ekleme, son varlığı geçerli dizine taşımak ve havuz sayınızı ve yineleyici dizinini azaltmak kadar basit olmalıdır.
Kaj

Vay be, çok iyi bir nokta Kaj! :)
Ricket

2

Şahsen, bağlantılı bir liste kullanırdım. Sevilen bir liste üzerinden yineleme yapmak, öğeleri eklemek ve kaldırmak hızlıdır. Yapıdaki öğelere doğrudan erişmeniz gerekiyorsa (örn. Bir dizine erişim) Array veya Vector kullanmak iyi bir seçim olacaktır, ancak ihtiyacınız olduğu gibi görünmüyor.

Bağlantılı listeden bir öğeyi her kaldırışınızda, bunu bir bellek havuzuna kaydetmek için yeniden dönüştürülebilen bir nesne havuzuna ekleyebilirsiniz.

Çokgen-veri yapılarını birkaç projede kullandım ve onlardan çok memnun kaldım .

Düzenleme: Üzgünüm, cevap kaldırma stratejisi açısından çok net olmadığını düşünüyorum: Öldüğü anda öğeyi listeden kaldırmanızı ve doğrudan havuz yapısına (geri dönüşüm) eklemenizi öneririm . Bağlantılı listeden bir öğeyi kaldırmak çok performanslı olduğu için bunu yaparken bir sorun görmüyorum.


1
Burada çift bağlantılı bir liste öneriyoruz? (ileri / geri)? Ayrıca: Bağlantı öğeleri üzerinde bir tür havuz mu öneriyorsunuz veya her işaretçi tutucusunu dinamik olarak bağlantılı listede mi ayırıyorsunuz?
Simon

Evet, bu görev için en uygun çift bağlantılı bir liste olması gerekir. Bunu işaret ettiğiniz için teşekkürler! Maddelerin yeniden kullanımı ile ilgili olarak: Talep üzerine yeni nesneler yaratan veya havuzda bazıları varsa mevcut örnekleri kullanan özel bir havuzlama sınıfı / veri yapısı düşünüyordum. Bu nedenle, listeden "ölü" öğeleri kaldırmak ve daha sonra kullanmak üzere havuza eklemek iyi olur.
bummzack

Tek bir bağlantılı liste iyi sonuç verecektir. Çift bağlantılı listeler yalnızca her iki yönde yineleme avantajı sağlar. Geçerli öğeyi kaldırma seçeneğiyle tekil bağlantılı bir liste üzerinden yineleme yapmak için bir önceki girişi izlemeniz gerekir.
deft_code

@caspin evet aynen. Tek bağlantılı bir liste kullanıyorsanız, önceki düğümleri takip etmeniz ve nextsilinmiş bir listeden sonra işaretçilerini düğüme bağlamanız gerekir . Bunu kendiniz yapmanın zorluğunu istemiyorsanız, çift bağlantılı bir liste seçilen DataStructure olacaktır.
bummzack

1

"sadece üzerime atla, ölüyüm" demek için bir bayrak ayarladım. Varlıklarımı birleştiriyorum, bu yüzden ölü bir Varlığın bir noktada tekrar hayatta olması muhtemeldir "

Bu özel uygulama ile ilgili kendi sorunuzu cevapladığınızı düşünüyorum. Eğer onlar üzerinde itme ve pop dışında bir iş yapmayı planlıyorsanız dizilerden uzaklaşacaktım. Ağır operasyonlar yapmayı planlıyorsanız bağlantılı listeler daha akıllıca bir yol olacaktır. Bununla birlikte, aynı varlığı tekrar oyuna entegre etmeyi planlıyorsanız, sadece bir boolean değişkeni ayarlamak ve oyun operasyon döngüleri sırasında kontrol etmek mantıklıdır.


0

Kullandığım bir lib'de bulduğum temiz ve genel bir çözüm kilitlenebilir bir harita kullanmaktı.

2 işleminiz var lock()ve unlock()harita üzerinde yinelemeye devam edeceksiniz lock(), şimdi bu noktadan sonra haritayı değiştiren her işlem yürürlüğe girmiyor, CommandQueuearadığınızda çalıştırılacak olana itiliyor unlock().

Dolayısıyla bir varlığın kaldırılması aşağıdaki sahte kod olacaktır:

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

ve sen ne zaman unlock()

isLocked = false
commandQueue.execute(this);

Dikkate almanız gereken tek şey, varlığı yalnızca döngüden sonra kaldırmanızdır.

EDIT: Simon tarafından önerilen çözüm budur.



0

İki yöntemim var.

Silinecek bir nesneyi çağırdığınızda, gerçekten iki bayrak ayarlar:

1.Kapsayıcıya bir nesnenin silindiğini bildirmek için

2.Kapsayıcıya hangi nesnelerin silinmesi istendiğini bildirmek için

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

Bir nesne vektörü kullanma

std::vector<object*> objects;

Ardından güncelleme fonksiyonunda, bir nesnenin silinip silinmediğini kontrol edin ve eğer öyleyse tüm nesneler arasında tekrarlayın ve silme işareti olanları kaldırın

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;
    }
}

İki Nesnelerin (işaretçi) vektörü kullanılarak.

std::vector<object*> *objects;

Güncelleme işlevinde, bir nesne silinecekse, nesneler arasında yineleme yapın ve silinmeyecekleri yeni bir vektöre ekleyin. nesne vektörünü sil ve işaretçiyi yeni vektöre ayarla

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;
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.