Dörtlü ağaç vs Izgara tabanlı çarpışma algılama


27

4 kişilik bir co-op r-tipi oyun yapıyorum ve çarpışma algılama kodunu uygulamak üzereyim. Çarpışma algılamanın nasıl gerçekleştirileceğiyle ilgili çok sayıda makale ve malzeme okudum, ancak ne yapacağını bulmakta zorlanıyorum. Dört ağaç gitmek için en yaygın yol gibi görünüyor, ancak bazı kaynaklarda grid tabanlı çözümden bahsediyorlar. Bir önceki oyunda tespitler için bir ızgara kullandığı için, onunla rahatım, ama aslında dörtlü bir ağaçtan daha mı iyi? Hangisinin en iyi performansı sağladığından emin değilim ve biraz da kıyaslama yaptım, her iki çözüm arasında da bir fark yok.

Biri diğerinden daha iyi mi? ya da daha zarif? Hangisini kullanmam gerektiğinden gerçekten emin değilim.

Herhangi bir tavsiye açığız. Teşekkürler.

Yanıtlar:


31

Doğru cevap biraz tasarladığınız asıl oyuna bağlıdır ve birbirinden bir tanesini seçmek, oyununuzda hangisinin daha fazla zamana veya alanınıza daha verimli olduğunu bulmak için profil yapmayı gerektirir.

Izgara algılama, yalnızca hareketli nesneler ve statik bir arka plan arasındaki çarpışmaları tespit etmek için geçerli görünmektedir. Bunun en büyük avantajı, statik arkaplanın bitişik bir bellek dizisi olarak temsil edilmesidir ve birden fazla okuma yapmanız gerekiyorsa (varlıklar ızgarada birden fazla hücreyi kapladığından) her çarpışma araması, iyi bir konuma sahip olan O (1) 'dir. Dezavantajı, statik arka plan büyükse, şebekenin alandan daha fazla israf olması olabilir.

Bunun yerine statik arka planı dörtlü olarak temsil ederseniz, bireysel aramaların maliyeti artar, ancak arka planın büyük blokları az miktarda yer kapladığından, bellek gereksinimleri azalır ve böylece arka planın daha fazlası içinde oturabilir. önbelleği. Böyle bir yapıda arama yapmak için 10 kat fazla okuma yapsa bile, hepsi önbellekte ise, yine de önbellek özeti olan tek bir aramaya göre 10 kat daha hızlı olacaktır.

Seçim ile karşı karşıya kalsam? Izgara uygulamasıyla giderdim, çünkü aptalca basit, zamanımı diğer, daha ilginç problemlere daha iyi harcardım. Oyunumun biraz yavaş çalıştığını fark edersem, biraz profil çizerim ve yardımın ne kullanabileceğini görürüm. Oyunun çarpışma tespiti yapmak için çok zaman harcadığı görülüyorsa, dörtlü gibi başka bir uygulama denerim (ilk önce tüm kolay düzeltmeleri uyguladıktan sonra) ve yardım edip etmediğini bulurdum.

Düzenleme: Şebeke çarpışma algılamasının çoklu, mobil varlıkların çarpışmalarını algılamakla nasıl ilişkili olduğuna dair hiçbir fikrim yok, ancak bunun yerine, uzamsal bir dizinin (Quadtree) yinelemeli çözüm üzerindeki algılama performansını nasıl iyileştirdiğini yanıtlayacağım. Naif (ve genelde mükemmel derecede ince) çözümü şöyle görünür:

foreach actor in actorList:
    foreach target in actorList:
        if (actor != target) and actor.boundingbox intersects target.boundingbox:
            actor.doCollision(target)

Bu açıkça, O (n ^ 2) civarında performans gösteriyor; n, şu anda mermiler, uzay gemileri ve uzaylılar dahil olmak üzere oyunda canlı olan oyuncu sayısı. Ayrıca küçük statik engeller içerebilir.

Bu tür nesnelerin sayısı oldukça küçük olduğu sürece fevkalade iyi çalışıyor, ancak kontrol edilmesi gereken birkaç yüzün üzerinde nesne olduğunda biraz zayıf görünmeye başlıyor. 10 nesne yalnızca 100 çarpışma kontrolüyle sonuçlanır, 100 sonuç 10.000 kontrolle sonuçlanır. Bir milyon çekle 1000 sonuç.

Bir uzaysal endeks (dörtgenler gibi), topladığı maddeleri geometrik ilişkilere göre verimli bir şekilde numaralandırabilir. bu çarpışma algoritmasını şunun gibi bir şeye değiştirir:

foreach actor in actorList:
    foreach target in actorIndex.neighbors(actor.boundingbox):
       if (actor != target) and actor.boundingbox intersects target.boundingbox:
            actor.doCollision(target)

Bunun verimliliği (varlıkların tekdüze dağılımını varsayarsak): genellikle O (n ^ 1.5 log (n)), çünkü endeks travers için log (n) karşılaştırmaları alır, karşılaştırmak için yaklaşık sqrt (n) komşuları olur. ve kontrol edilecek n aktörler var. Gerçekçi olsa da, komşuların sayısı her zaman oldukça sınırlıdır, çünkü bir çarpışma olursa, nesnelerin çoğu silinir veya çarpışmadan uzaklaştırılır. Böylece sadece O (n log (n)) alırsınız. 10 varlık için (yaklaşık) 10 karşılaştırma yaparsınız, 100 için, 200 yaparsınız, 1000 için 3000 yaparsınız.

Gerçekten akıllı bir dizin, komşu aramayı toplu yinelemeyle birleştirebilir ve kesişen her varlık üzerinde geri arama yapabilir. İndeks, n kez sorgulanmak yerine bir kez tarandığından, bu, yaklaşık O (n) performansı verecektir.


"Statik arkaplan" derken neyi kastettiğinizi bildiğimden emin değilim. Bununla uğraştığım şey temelde 2B atıcı, yani uzay gemileri ve uzaylılar, mermiler ve duvarlarla çarpışma algılama.
dotminic

2
Az önce özel "Harika Cevap" rozeti kazandın!
Felixyz

Bu kulağa aptalca gelebilir, ancak bir nesneyi çarpışmalarını test etmek için hangi nesnelere karşı seçmem için quadtree'imi nasıl kullanırım? Bunun nasıl yapıldığından emin değilim. İkinci bir soru ortaya çıkıyor. Düğümde, başka bir düğümün komşusu olmayan bir nesneye sahip olduğumu, ancak nesnenin birkaç düğüme yayılacak kadar büyük olduğunu, ağacın olmadığını düşünebileceğini tahmin ettiğimden, gerçek bir çarpışmayı nasıl kontrol edebileceğimi söyleyin. Uzaktaki bir düğümdeki nesnelerle çarpışacak kadar yakın mı? Bir düğüme tamamen uymayan nesneler ana düğümde tutulmalı mı?
dotminic

2
Quat ağacı, üst üste binen sınırlayıcı kutu aramaları için doğal olarak optimaldir. Bunun için en iyi seçenek genellikle bir R-Tree. Dörtlü ağaçlar için, çoğu nesne kabaca noktaya benziyorsa, evet, nesneleri iç düğümlerde tutmak ve bulanık bir komşu aramasında tam çarpışma testi yapmak mantıklıdır. Dizindeki nesnelerin çoğu büyükse ve çarpışmadan çakışıyorsa, dörtlü bir ağaç muhtemelen kötü bir seçimdir.
Bununla

Bütün bunlar oldukça kafa karıştırıcı! bilgi için teşekkürler.
dotminic

3

Eski ipliği diriltdiğim için üzgünüm ama IMHO düz eski ızgaraları bu durumlar için yeterince kullanılmıyor. Hücre yerleştirme / çıkarma işleminde kir ucuzdur. Bir hücrenin serbest bırakılması ile uğraşmanıza gerek yok çünkü şebekenin seyrek sunumlar için optimize etme amacı yok. Seçim çerçevesini kısalttığımı, eski bir kod tabanında bir dizi element seçmenin sadece dört ağacı bir ızgara ile değiştirerek 1200ms'den 20ms'ye düşürdüğünü söylüyorum. Adil olmak gerekirse, bu dört ağaç gerçekten zayıf bir şekilde uygulandı, elemanlar için yaprak düğümü başına ayrı bir dinamik dizi depolandı.

Son derece yararlı bulduğum bir diğeri ise, çizim şekilleri için klasik rasterleştirme algoritmalarınızın ızgaraya arama yapmak için kullanılabiliyor olmasıdır. Örneğin, bir çizgiyle kesişen öğeleri aramak için Bresenham çizgisi rasterleştirmesini, hangi hücrelerin çokgenin kesiştiğini bulmak için tarama çizgisini rasterleştirmeyi vb. Kullanabilirsiniz. optimize edilmiş kod Bir ızgaradaki hareketli nesnelere karşı kesişimleri tespit etmek için kullandığım şekilde pikselleri bir görüntüye çizmek için kullanıyorum.

Bununla birlikte, bir ızgarayı verimli hale getirmek için ızgara hücresi başına 32-bit'den daha fazlasına gerek duymamanız gerekir. Bir milyon hücreyi 4 megabaytın altında depolayabilmelisiniz. Her bir ızgara hücresi, hücredeki ilk öğeyi dizine ekleyebilir ve hücredeki ilk öğe daha sonra hücredeki bir sonraki öğeyi dizine ekleyebilir. Her bir hücreyle bir çeşit dolu şişeli kap saklıyorsanız, bu bellek kullanımı ve tahsilerde hızla patlar. Bunun yerine sadece yapabilirsiniz:

struct Node
{
    int32_t next;
    ...
};

struct Grid
{
     vector<int32_t> cells;
     vector<Node> nodes;
};

Gibi:

görüntü tanımını buraya girin

Tamam, eksilere göre. Kuşkusuz buna ızgaralara karşı bir önyargı ve tercihle geliyorum, ancak ana dezavantajı seyrek olmamasıdır.

Bir koordinat verilen belirli bir ızgara hücresine erişmek sabit zamanlıdır ve daha ucuz olan bir ağacın aşağıya inmesini gerektirmez, ancak ızgara yoğundur, yoğun değildir, bu nedenle gerekenden daha fazla hücreyi kontrol etmek zorunda kalabilirsiniz. Verilerinizin çok seyrek dağıldığı durumlarda, kılavuz, kesişen öğelerin bir çizgi veya dolu bir çokgen veya bir dikdörtgen veya bir sınırlama dairesi olduğunu söyleyen öğelerin belirlenmesi için daha fazla kontrol yapılmasını gerektirebilir. Izgara tamamen boş olsa bile bu 32-bit hücreyi saklamalıdır ve bir şekil kesişme sorgusu yaparken, sizin şeklinizi kesiştikleri takdirde bu boş hücreleri kontrol etmeniz gerekir.

Dört ağacın ana yararı, doğal olarak seyrek verileri saklama ve sadece gerektiği kadar alt bölme yeteneğidir. Bununla birlikte, özellikle her karede hareket eden şeyler varsa, gerçekten iyi uygulanması zor. Ağacın alt düğümleri ayırması ve çocuk düğümleri serbest bırakması gerekir, aksi halde ebeveyn bağlantılarını saklamak için yükü boşa harcayan yoğun bir ızgaraya dönüşür. Yukarıda ızgarada tarif ettiğim şeye çok benzer teknikler kullanarak verimli bir dörtlü ağaç uygulamak çok mümkün, ancak genellikle daha yoğun olacak. Ve bunu ızgarada yaptığım gibi yaparsanız, her ikisi de mutlaka optimal olmaz, çünkü dörtlü bir ağaç düğümünün dört çocuğunun da hepsinin bitişik olarak depolandığını garanti etme yeteneğinde bir kayba yol açacaktır.

Ayrıca, hem dörtlü ağaç hem de ızgara, tüm sahneyi kaplayan çok sayıda büyük öğeniz varsa, ancak en azından ızgara düz kalır ve bu durumlarda birinci dereceye geçmiyorsa, muhteşem bir iş çıkarmaz. . Dörtlü ağaç öğeleri dallarda saklamalı ve yalnızca bu gibi durumları makul bir şekilde ele alacak yaprakları bırakmamalı, aksi takdirde çılgın gibi bölünerek son derece hızlı bir şekilde bozulacaktır. En geniş içerik yelpazesini ele almasını istiyorsanız, dörtlü bir ağaçla ilgilenmeniz gereken bunun gibi daha patolojik durumlar vardır. Örneğin, dörtlü bir ağacı gerçekten tetikleyebilecek başka bir durum, eğer bir çakışan elementler kayığınız varsa. Bu noktada bazı insanlar sınırsızca bölünmesini engellemek için dörtlü ağaçlarının derinlik sınırını koymaya başvuruyorlar. Şebekenin düzgün bir iş çıkardığına dair bir temyiz başvurusu var.

Kararlılık ve öngörülebilirlik de bir oyun bağlamında faydalıdır, çünkü bazen nadir durumlarda senaryolarda oldukça hızlı olan bir çözüme karşılık nadir durumlarda senaryolarda sıkıntıya yol açabiliyorsa, genel dava için mümkün olan en hızlı çözümü istemezsiniz. etrafında ancak bu tür hıçkırıklara yol açmaz ve kare hızlarını pürüzsüz ve öngörülebilir tutar. Bir ızgara bunun için böyle bir kaliteye sahiptir.

Bütün bunlarla, gerçekten programcının işine bağlı olduğunu düşünüyorum. Grid vs quad-tree veya octree vs. kd-tree vs. BVH gibi şeylerle oy kullandı, kullandığı veri yapısı ne olursa olsun çok verimli çözümler üretme rekoru olan en üretken geliştirici üzerinde. Multithreading, SIMD, önbellek dostu bellek düzenleri ve erişim düzenleri gibi mikro düzeyde de bir sürü şey var. Bazı insanlar bu mikroları düşünebilir, ancak mutlaka bir mikro etkisi olmaz. Bu tür şeyler bir çözümden diğerine 100 kat fark yaratabilir. Buna rağmen, birkaç gün kişisel olarak bana verildi ve her karenin etrafında hareket eden öğelerin çarpışma algılamasını hızlı bir şekilde hızlandırmak için bir veri yapısı uygulamam gerektiği söylenirse, kısa sürede bir ızgara uygulayarak kısa sürede daha iyi yaparım Ağaç.

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.