N nesneden oluşan bir sistemin çarpışma kontrolü verimliliğini artırmanın bir yolu var mı?


9

Biri oyuncu olan birçok ekran nesnesinden oluşan bir oyun yapıyorum. Her iterasyonda hangi nesnelerin çarpıştığını bilmem gerekiyor.

Ben böyle bir şey yaptım:

for (o in objects)
{
   o.stuff();
   for (other in objects)
      if (collision(o, other))
          doStuff();

   bla.draw();
}

Bu kötü olduğunu söyledi O (n ^ 2) vardır. Bunu nasıl daha verimli yapabilirim, hatta mümkün mü? Bunu Javascript'te yapıyorum ve n genellikle 30'dan küçük olacak, bu aynı kalırsa bir sorun olacak mı?


3
Nasıl çalıştığını görmek için kodu çalıştırmayı denediniz mi?
thedaian

Hayır, sığınak, sadece O (n ^ 2) yüzünden kötü olduğunu varsayıyorum.
jcora

1
Sadece 30 nesne mi? Mekânsal bölümlemeyi tavsiye ederdim, ama sadece 30 nesne ile sonuç vermezdi. Diğerlerinin işaret ettiği bazı küçük optimizasyonlar var, ancak hepsi bahsettiğiniz ölçekte küçük optimizasyonlar.
John McDonald

Yanıtlar:


16

En fazla 30 nesne ile, aynı iki çifti her karede bir kereden fazla kontrol etmekten başka optimizasyona ihtiyacınız olmamalıdır. Aşağıdaki kod örneği kapsayacaktır. Ancak bir fizik motorunun kullanacağı farklı optimizasyonlarla ilgileniyorsanız, bu yazının geri kalanını okumaya devam edin.

İhtiyacınız olan şey , Octree (3D oyunlar için) veya Quadtree (2D oyunlar için ) gibi bir uzamsal bölümleme uygulamasıdır . Bunlar dünyayı alt bölümlere ayırır ve daha sonra her alt bölüm, en az boyuta bölünene kadar aynı malikanede daha fazla bölümlenir. Bu, dünyanın hangi bölgesinde bulunan diğer nesnelerin diğeriyle aynı bölgede olduğunu çok hızlı bir şekilde kontrol etmenizi sağlar, bu da kontrol etmeniz gereken çarpışma miktarını sınırlar.

Uzamsal bölümlemeye ek olarak , fizik nesnelerinizin her biri için bir AABB ( Eksen hizalı sınırlama kutusu ) oluşturmanızı öneririm . Bu, bir nesnenin AABB'sini başka bir nesneye karşı kontrol etmenizi sağlar; bu, nesneler arasında ayrıntılı bir çoklu denetimden daha hızlıdır.

Bu, karmaşık veya büyük fizik nesneleri için başka bir adım daha ileri gidebilir, burada fizik ağını alt bölümlere ayırabilir ve her alt şekle, yalnızca iki nesnenin AABB'leri çakışıyorsa kontrol edebileceğiniz kendi AABB'sini verir.

Çoğu fizik motoru, dinlendikten sonra fizik bedenlerindeki aktif fizik simülasyonunu devre dışı bırakır. Bir fizik gövdesi devre dışı bırakıldığında, sadece her karesinde AABB'ye karşı çarpışma olup olmadığını kontrol etmelidir ve AABB ile bir şey çarpışırsa, o zaman yeniden etkinleştirilir ve daha ayrıntılı bir çarpışma kontrolü yapar. Bu simülasyon sürelerini azaltır.

Ayrıca, birçok fizik motoru birbirine yakın olan bir grup fizik bedeninin birlikte gruplandığı 'simülasyon adaları' kullanır. Simülasyon adasındaki her şey dinleniyorsa, simülasyon adasının kendisi deaktiftir. Simülasyon adasının yararı, içindeki tüm cesetlerin, ada aktif olmadığında çarpışma kontrolünü durdurabilmesidir ve her çerçevenin tek kontrolü, adanın AABB'sine bir şey girip girmediğini kontrol etmektir. Adanın AABB'sine yalnızca bir şey girdiğinde, adanın içindeki cesetlerin her birinin çarpışmaları kontrol etmesi gerekir. Simülasyon adası, içindeki herhangi bir vücut kendi kendine tekrar hareket etmeye başlarsa da yeniden etkinleştirilir. Bir vücut grubun merkezinden yeterince uzaklaşırsa, adadan kaldırılır.

Sonunda böyle bir şey kaldı (sözde kodda):

// Go through each leaf node in the octree. This could be more efficient
// by keeping a list of leaf nodes with objects in it.
for ( node in octreeLeafNodes )
{
    // We only need to check for collision if more than one object
    // or island is in the bounds of this octree node.
    if ( node.numAABBsInBounds > 1)
    {
        for ( int i = 0; i < AABBNodes.size(); ++i )
        {
           // Using i+1 here allows us to skip duplicate checks between AABBS
           // e.g (If there are 5 bodies, and i = 0, we only check i against
           //      indexes 1,2,3,4. Once i = 1, we only check i against indexes
           //      2,3,4)
           for ( int j = i + 1; j < AABBNodes.size(); ++j )
           {
               if ( AABBOverlaps( AABBNodes[i], AABBNodes[j] ) )
               {
                   // If the AABB we checked against was a simulation island
                   // then we now check against the nodes in the simulation island

                   // Once you find overlaps between two actual object AABBs
                   // you can now check sub-nodes with each object, if you went
                   // that far in optimizing physics meshes.
               {
           }
        }
    }
}

Ayrıca böyle döngüler içinde çok fazla döngüye sahip olmamanızı tavsiye ederim, yukarıdaki örnek sadece fikri aldım, size yukarıda gösterilen gibi aynı işlevselliği veren birden fazla işleve ayırırdım.

Ayrıca, döngü sırasında AABBNodes kapsayıcısını değiştirmediğinizden emin olun; bu, kaçırılan çarpışma kontrolleri anlamına gelebilir. Bu sağduyu gibi gelebilir, ancak çarpışmalara tepki veren şeylerin tahmin etmediğiniz değişikliklere neden olmasının ne kadar kolay olduğuna şaşıracaksınız. Örneğin, bir çarpışma, çarpışan nesnelerden birinin, onları kontrol etmekte olduğunuz Octree düğümünün AABB'sinden çıkarmak için yeterince pozisyon değiştirmesine neden olduysa, o kap değiştirebilir. Bunu çözmek için, kontroller sırasında meydana gelen tüm çarpışma olaylarının bir listesini tutmanızı ve ardından tüm kontroller tamamlandıktan sonra listeden geçip herhangi bir çarpışma olayını göndermenizi tavsiye ederim.


4
Okuyucu zihnini mevcut yöntemlere açmak için güzel ve kullanışlı teknik hassasiyetlerle çok tutarlı bir cevap. +1
Valkea

Çarpışan nesneyi kaldırmam gerekirse ne olur? Konteyneri değiştirebilir miyim? "Nesne yok" olduğu için artık nesneye ihtiyacım olmadığından kaptan çıkarmak demek. Çarpışma tespiti sırasında kaldırmazsam, çarpışma olaylarını çalıştırmak için bir döngü daha gerekir.
newguy

Çarpışan nesneyi kaldırmak iyidir, ancak tüm simülasyon üzerinde çarpışma geçişi yapılana kadar bunu yapmayı beklemenizi tavsiye ederim. Genellikle kaldırılması gereken nesneleri işaretler veya kaldırılacak nesnelerin listesini oluşturur ve çarpışma simülasyonu yapıldıktan sonra bu değişiklikleri uygularsınız.
Nic Foster

4

Örneğin, her bir nesne çiftini birden çok kez test edin.

0,1,2,3 içeren bir dizi ile çok basit bir örnek verelim

Kodunuzla bunu elde edersiniz:

  • Döngü 0'da 1, 2 ve 3'e karşı test edersiniz
  • Döngü 1'de 0, 2 ve 3'e karşı test yaparsınız ===> (0-1 zaten test edilmiştir)
  • 2. döngüde 0, 1 ve 3'e karşı test yaparsınız ===> (0-2 / 1-2 zaten test edilmiştir)
  • 3. döngüde 0, 1 ve 2'ye karşı test yaparsınız ===> (0-3 / 1-3 / 2-3 zaten test edilmiştir)

Şimdi aşağıdaki kodu görelim:

for(i=0;i<=objects.length;i++)
{
    objects[i].stuff();

    for(j=i+1;j<=objects.length;j++)
    {
        if (collision(objects[i], objects[j]))
        doStuff();
    }

    bla.draw();
}

0,1,2,3 içeren diziyi bir kez daha kullanırsak, aşağıdaki davranışlarımız olur:

  • Döngü 0'da 1, 2, 3'e karşı test edersiniz
  • Döngü 1'de 2, 3'e karşı test edersiniz
  • Döngü 2'de 3'e karşı test edersiniz
  • 3. döngüde hiçbir şeye karşı test edersiniz

İkinci algoritma ile 6 çarpışma testi yapılırken, önceki algoritma 12 çarpışma testi istedi.


Bu algoritma N(N-1)/2hala O (N ^ 2) performansı olan karşılaştırmalar yapar .
Kai

1
Talep edildiği gibi 30 nesne ile bu, 870'e karşı 465 çarpışma testi anlamına gelir ... muhtemelen sizin bakış açınıza benzer, ancak benimkinden değil. Ayrıca, diğer cevapta sunulan çözüm tamamen aynı algoritma :)
Valkea

1
@Valkea: Bunun bir parçası. :)
Nic Foster

@NicFoster: evet haklısın;) Kesinlikle seçilen nesneler arasındaki çarpışma testi hakkında konuşuyordum, algoritmanın bölümleme kısmı hakkında değil, ki bu benim örneğimde eklemeyi bile düşünmediğim çok değerli bir ek Ben yazıyordum.
Valkea

Buna amortisman deniyor mu? Her neyse, teşekkürler!
jcora

3

Algoritmanızı ihtiyaçlarınız doğrultusunda tasarlayın, ancak uygulama ayrıntılarını kapsama altında tutun. Javascript'te bile, temel OOP kavramları geçerlidir.

Çünkü N =~ 30, O(N*N)bir endişe değildir ve doğrusal aramanız muhtemelen herhangi bir alternatif kadar hızlı olacaktır. Ancak varsayımları kodunuzda kodlamak istemezsiniz. Sahte kodda bir arayüzünüz olur

interface itemContainer { 
    add(BoundingBox);
    remove(BoundingBox);
    BoundingBox[] getIntersections();
}

Bu, öğe listenizin neler yapabileceğini açıklar. Sonra bu arabirimi uygulayan bir ArrayContainer sınıfı yazabilirsiniz. Javascript'te, kod şöyle görünecektir:

function ArrayContainer() { ... } // this uses an array to store my objects
ArrayContainer.prototype.add = function(box) { ... };
ArrayContainer.prototype.remove = function(box) { ... };
ArrayContainer.prototype.getIntersections = function() { ... };

function QuadTreeContainer { ... } // this uses a quadtree to store my objects
... and implement in the add/remove/getIntersections for QuadTreeContainer too

Ve burada 300 sınırlayıcı kutu oluşturan ve tüm kavşakları alan örnek kod. Doğru ArrayContainer ve QuadTreeContainer uyguladıysanız, kodunuzda değiştirmek gerekir tek şey değişimdir var allMyObjects = new ArrayContainer()için var allMyObjects = QuadTreeContainer().

var r = Math.random;
var allMyObjects = new ArrayContainer();
for(var i=0; i<300; i++)
    allMyObjects.add(new BoundingBox(r(), r()));
var intersections = allMyObjects.getIntersections();

Devam ettim ve standart ArrayContainer uygulamasını burada çırptım:

http://jsfiddle.net/SKkN5/1/


Not: Bu cevap Bane'nin kod tabanının çok büyük, dağınık ve yönetilmesi zor olduğu şikayeti ile motive edildi. Bir Array'ı bir Tree vs kullanarak kullanma konusundaki tartışmaya fazla bir şey eklemese de, umarım bu kodunu daha iyi organize etme konusunda nasıl özel olarak gidebileceği ile ilgili bir cevaptır.
Jimmy

2

Ayrıca, çarpışabilecek nesnelerin türlerini de göz önünde bulundurmalısınız.

Örneğin, oyuncunun muhtemelen kendi mermileri dışındaki her şeyle çarpışma açısından kontrol edilmesi gerekir. Ancak düşmanların sadece oyuncu mermilerine karşı kontrol etmesi gerekebilir. Mermilerin neredeyse kesinlikle birbirleriyle çarpışmasına gerek yoktur.

Bunu verimli bir şekilde uygulamak için, her nesne türü için bir tane olmak üzere ayrı nesne listeleri tutmak isteyebilirsiniz.

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.