C ++ 'da Varlık Bileşen Sistemi Arasındaki Bağlantıya İlişkin Öneriler


10

Varlık bileşeni sistemi hakkında birkaç belgeyi okuduktan sonra, madeni uygulamaya karar verdim. Şimdiye kadar, varlıkları ve sistem yöneticisini (sistemlerini) içeren bir Dünya sınıfı, std :: map olarak bileşenleri içeren Varlık sınıfı ve birkaç sistemi var. Dünyada std :: vector olarak varlıkları tutuyorum. Şimdiye kadar sorun yok. Beni şaşırtan şey varlıkların yinelemesidir, bu konuda net bir zihnim olamaz, bu yüzden hala bu kısmı uygulayamıyorum. Her sistem, ilgilendikleri yerel bir varlık listesine sahip olmalı mı? Yoksa sadece Dünya sınıfındaki varlıklar arasında yineleme yapmalı ve sistemler arasında yineleme yapmak ve varlığın sistemin ilgilendiği bileşenlere sahip olup olmadığını kontrol etmeli miyim? Demek istediğim :

for (entity x : listofentities) {
   for (system y : listofsystems) {
       if ((x.componentBitmask & y.bitmask) == y.bitmask)
             y.update(x, deltatime)
       }
 }

ama bence bir bitmask sistemi bir betik dili yerleştirilmesi durumunda esnekliği engelleyecektir. Veya her sistem için yerel listelerin olması sınıfların bellek kullanımını artıracaktır. Çok kafam karıştı.


Neden bitmask yaklaşımının komut dosyası bağlamalarını engellemesini bekliyorsunuz? Bir kenara, varlıkların ve sistemlerin kopyalanmasını önlemek için her bir döngü için referansları (mümkünse const) kullanın.
Benjamin Kloster

bir int gibi bir bit maskesi kullanmak, sadece 32 farklı bileşeni tutacaktır. 32'den fazla bileşen olacağını ima etmiyorum ama eğer varsa? Başka bir int veya 64bit int oluşturmak zorunda kalacak, dinamik olmayacak.
deniz

Çalışma zamanı dinamik olmasını isteyip istemediğinize bağlı olarak std :: bitset veya std :: vector <bool> kullanabilirsiniz.
Benjamin Kloster

Yanıtlar:


7

Her sistem için yerel listelerin olması sınıfların bellek kullanımını artıracaktır.

Bu, geleneksel bir uzay-zaman dengesidir .

Tüm varlıklar arasında yineleme yapmak ve imzalarını kontrol etmek doğrudan kodlamak olsa da, sistem sayınız büyüdükçe verimsiz hale gelebilir - binlerce ilgisiz varlık arasında muhtemelen tek bir ilgi alanı arayan özel bir sistem hayal edin (girdi olsun) .

Bununla birlikte, bu yaklaşım hedeflerinize bağlı olarak hala yeterince iyi olabilir.

Bununla birlikte, hız konusunda endişeleniyorsanız, elbette dikkate alınması gereken başka çözümler var.

Her sistem, ilgilendikleri yerel bir varlık listesine sahip olmalı mı?

Kesinlikle. Bu size iyi bir performans vermesi ve uygulanması oldukça kolay olan standart bir yaklaşımdır. Bence bellek yükü ihmal edilebilir - işaretçileri saklamaktan bahsediyoruz.

Şimdi bu "ilgi listelerinin" nasıl muhafaza edileceği o kadar açık olmayabilir. Veri kabına gelince, std::vector<entity*> targetssistemin sınıfının içi mükemmeldir. Şimdi ne yaptığım:

  • Varlık yaratılışta boştur ve hiçbir sisteme ait değildir.
  • Bir varlığa her bileşen eklediğimde:

    • onun elde akım bit imza ,
    • Bileşenin boyutunu yeterli yığın boyutuna sahip dünya havuzuyla eşleştirin (kişisel olarak boost :: pool kullanıyorum) ve bileşeni oraya ayırın
    • varlığın yeni bit imzasını al (yalnızca "geçerli bit imzası" artı yeni bileşen)
    • tüm dünyanın sistemleri ile yinelerler ve kimin imzası bir sistem varsa gelmez işletmenin mevcut imzaya ve yok yeni imzaya, biz orada bizim varlık işaretçiyi push_back gerektiğini belirginleşmektedir.

          for(auto sys = owner_world.systems.begin(); sys != owner_world.systems.end(); ++sys)
                  if((*sys)->components_signature.matches(new_signature) && !(*sys)->components_signature.matches(old_signature)) 
                          (*sys)->add(this);

Bir varlığın kaldırılması tamamen benzerdir, tek bir sistem mevcut imzamızla eşleşirse (varlığın orada olduğu anlamına gelir) ve yeni imzayla eşleşmediğinde (yani varlığın artık orada olmaması gerektiği anlamına gelirse) kaldırdığımız tek farkla ).

Şimdi std :: list'in kullanımını düşünüyor olabilirsiniz, çünkü vektörden kaldırma O (n) 'dir, ortadan her kaldırdığınızda büyük veri yığınını kaydırmanız gerekeceğinden bahsetmezsiniz. Aslında, bunu yapmak zorunda değilsiniz - bu seviyedeki işleme siparişini umursamadığımız için sadece std :: remove'i arayabilir ve her silme işleminde sadece O (n) arama yapmak zorunda olduğumuz gerçeğiyle yaşayabiliriz. kaldırılacak varlık.

std :: list size O (1) 'i kaldırır ama diğer tarafta biraz ek bellek yükünüz vardır. Ayrıca çoğu zaman varlıkları işleyeceğinizi ve bunları kaldırmayacağınızı unutmayın - ve bu kesinlikle std :: vector kullanılarak daha hızlı yapılır.

Performans açısından kritik öneme sahipseniz, başka bir veri erişim modelini bile düşünebilirsiniz , ancak her iki şekilde de bir tür "ilgi listesi" tutmayı düşünebilirsiniz . Varlık Sistemi API'nızı yeterince soyutlu tutarsanız, çerçeveniz bunlardan dolayı düşerse sistemlerin varlık işleme yöntemlerini iyileştirmenin bir sorun olmaması gerektiğini unutmayın - bu nedenle şimdilik kodlamanız için en kolay yöntemi seçin - yalnızca daha sonra profil oluşturun ve gerekirse iyileştirin.


5

Her sistemin kendisiyle ilişkili bileşenlere nerede sahip olduğunu ve varlıkların yalnızca bunlara atıfta bulunduğunu düşünmeye değer bir yaklaşım vardır. Temel olarak, (basitleştirilmiş) Entitysınıfınız şöyle görünür:

class Entity {
  std::map<ComponentType, Component*> components;
};

Bir RigidBodybileşenine bağlı bir bileşen söylediğinizde Entity, bunu Physicssisteminizden istersiniz . Sistem bileşeni oluşturur ve varlığın bir işaretçi tutmasına izin verir. Sisteminiz daha sonra şöyle görünür:

class PhysicsSystem {
  std::vector<RigidBodyComponent> rigidBodyComponents;
};

Şimdi, bu başlangıçta biraz sezgisel görünebilir, ancak avantaj, bileşen varlık sistemlerinin durumlarını güncelleme biçiminde yatmaktadır. Genellikle, sistemlerinizi tekrarlar ve ilişkili bileşenleri güncellemelerini istersiniz

for(auto it = systems.begin(); it != systems.end(); ++it) {
  it->update();
}

Sistemin sahip olduğu tüm bileşenlerin bitişik belleğe sahip olmasının gücü, sisteminiz her bileşen üzerinde tekrarladığında ve güncellediğinde, temelde yalnızca

for(auto it = rigidBodyComponents.begin(); it != rigidBodyComponents.end(); ++it) {
  it->update();
}

Potansiyel olarak güncellemeleri gereken bir bileşeni olmayan tüm varlıkları tekrar etmek zorunda değildir ve ayrıca bileşenlerin hepsi bitişik olarak saklanacağı için çok iyi bir önbellek performansı potansiyeli vardır . Bu, bu yöntemin en büyük avantajı değilse de budur. Belirli bir zamanda yüzlerce ve binlerce bileşene sahip olursunuz, mümkün olduğunca performans göstermeye çalışabilirsiniz.

Bu noktada Worldsistemleriniz updatearasında dolaşır ve varlıkları tekrarlamaya gerek kalmadan onları çağırırsınız . (İmho) daha iyi bir tasarım çünkü o zaman sistemlerin sorumlulukları çok daha açık.

Tabii ki, bu tür tasarımların sayısız var, bu yüzden oyununuzun ihtiyaçlarını dikkatlice değerlendirmeli ve en uygun olanı seçmelisiniz, ancak burada görebildiğimiz gibi, bazen fark yaratabilecek küçük tasarım detayları.


iyi cevap, teşekkürler. ancak bileşenlerin işlevleri (update () gibi) yoktur, yalnızca veriler vardır. ve sistem bu verileri işler. yani örneğinize göre, bileşen sınıfı için sanal bir güncelleme ve her bileşen için bir varlık göstergesi eklemeliyim, doğru mu?
deniz

@deniz Her şey tasarımınıza bağlı. Bileşenlerinizde yalnızca veri değil yöntem varsa, sistem yine de bunları yineleyebilir ve gerekli eylemleri gerçekleştirebilir. Varlıklara tekrar bağlanma konusunda, evet, bileşenin kendisinde sahip varlığa bir işaretçi depolayabilir veya sisteminizin bileşen tanıtıcıları ve varlıklar arasında bir harita tutmasını sağlayabilirsiniz. Genellikle, bileşenlerinizin mümkün olduğunca bağımsız olmasını istersiniz. Ana varlığı hakkında hiç bir şey bilmeyen bir bileşen idealdir. Bu yönde iletişime ihtiyacınız varsa, olayları ve benzerlerini tercih edin.
pwny

Verimlilik için daha iyi olacağını söylüyorsanız, deseninizi kullanacağım.
deniz

@deniz gerçekte kod erken profil emin olun ve genellikle çalışır ve belirli engin :) için değil ne tanımlamak için
pwny

tamam :) Ben biraz stres testi yapacağım
deniz

1

Kanımca, iyi bir mimari varlıklarda bir bileşenler katmanı oluşturmak ve bu bileşenler katmanındaki her bir sistemin yönetimini ayırmaktır. Örneğin, mantık sistemi varlıklarını etkileyen bazı mantık bileşenlerine sahiptir ve varlıktaki tüm bileşenlerle paylaşılan ortak öznitelikleri depolar.

Bundan sonra, her sistemin nesnelerini farklı noktalarda veya belirli bir sırayla yönetmek istiyorsanız, her sistemde aktif bileşenlerin bir listesini oluşturmak daha iyidir. Sistemlerde oluşturabileceğiniz ve yönetebileceğiniz tüm işaretçi listeleri birden fazla yüklü kaynaktır.

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.