Temel sistem-bileşen-varlık yaklaşımından başlıyoruz .
Sadece bileşen türleri hakkında bilgi dışında derlemeler ( bu makaleden türetilen terim) oluşturalım . Çalışma zamanında dinamik olarak yapılır, tıpkı bir varlığa bileşenleri tek tek ekleyeceğimiz / kaldıracağımız gibi, ancak yalnızca tür bilgileriyle ilgili olduğundan daha kesin bir şekilde adlandıralım.
Sonra her biri için montaj belirleyen varlıklar inşa ediyoruz . Varlığı oluşturduktan sonra, montajı değişmezdir, bu da onu yerinde doğrudan değiştiremeyeceğimiz anlamına gelir, ancak yine de mevcut varlığın yerel bir kopyaya imzasını alabilir (içeriklerle birlikte), üzerinde uygun değişiklikler yapabilir ve yeni bir varlık oluşturabiliriz. onun.
Şimdi anahtar kavram için: bir varlık yaratıldığında, montaj kovası adı verilen bir nesneye atanır , bu da aynı imzanın tüm varlıklarının aynı kapta (örn. Std :: vector'da) olacağı anlamına gelir .
Şimdi sistemler sadece ilgilendikleri her kovadan tekrar ediyorlar ve işlerini yapıyorlar.
Bu yaklaşımın bazı avantajları vardır:
- bileşenler birkaç (tam olarak: kova sayısı) bitişik bellek yığınında saklanır - bu bellek kolaylığını artırır ve tüm oyun durumunu boşaltmak daha kolaydır
- sistemler bileşenleri doğrusal bir şekilde işler, bu da gelişmiş önbellek tutarlılığı - güle güle sözlükleri ve rastgele bellek sıçramaları anlamına gelir
- yeni bir varlık oluşturmak, bir montajı kovaya eşlemek ve gerekli bileşenleri vektörüne geri itmek kadar kolaydır
- bir varlığın silinmesi std :: 'ye yapılan bir çağrı kadar kolaydır, çünkü son öğe silinmiş olanla değiştirilir.
Tamamen farklı imzalara sahip çok sayıda varlığımız varsa, önbellek tutarlılığının faydaları biraz azalır, ancak çoğu uygulamada olacağını düşünüyorum.
Vektörler yeniden tahsis edildikten sonra işaretçi geçersiz kılma ile ilgili bir sorun da vardır - bu, aşağıdaki gibi bir yapı eklenerek çözülebilir:
struct assemblage_bucket {
struct entity_watcher {
assemblage_bucket* owner;
entity_id real_index_in_vector;
};
std::unordered_map<entity_id, std::vector<entity_watcher*>> subscribers;
//...
};
Bu nedenle, oyun mantığımızda herhangi bir nedenle yeni oluşturulan bir varlığı takip etmek istediğimizde, kova içinde bir entity_watcher kaydederiz ve varlık kaldırılırken std :: move'd olması gerektiğinde, izleyicilerini arar ve güncelleriz onların real_index_in_vector
yeni değerlere. Çoğu zaman bu, her varlık silme işlemi için yalnızca tek bir sözlük araması uygular.
Bu yaklaşımın başka dezavantajları var mı?
Oldukça açık olmasına rağmen, çözüm neden hiçbir yerde belirtilmiyor?
EDIT : Yorumlar yetersiz olduğu için "cevapları cevaplamak" sorusunu düzenliyoruz.
statik sınıf yapısından uzaklaşmak için özel olarak yaratılan takılabilir bileşenlerin dinamik doğasını kaybedersiniz.
Yapmıyorum. Belki yeterince açık bir şekilde açıklamadım:
auto signature = world.get_signature(entity_id); // this would just return entity_id.bucket_owner->bucket_signature or so
signature.add(foo_component);
signature.remove(bar_component);
world.delete_entity(entity_id); // entity_id would hold information about its bucket owner
world.create_entity(signature); // automatically assigns new entity to an existing or a new bucket
Mevcut varlığın imzasını almak, değiştirmek ve yeni bir varlık olarak tekrar yüklemek kadar basittir. Takılabilir, dinamik doğa ? Elbette. Burada sadece bir "montaj" ve bir "kova" sınıfı olduğunu vurgulamak istiyorum . Kovalar veriye dayalıdır ve çalışma zamanında optimum miktarda oluşturulur.
geçerli bir hedef içerebilecek tüm bölümlerden geçmeniz gerekir. Harici bir veri yapısı olmadan, çarpışma tespiti de aynı derecede zor olabilir.
Bu yüzden yukarıda belirtilen dış veri yapılarına sahibiz . Çözüm, System sınıfında bir sonraki gruba ne zaman atlanacağını algılayan bir yineleyici eklemek kadar basittir. Atlama mantığına tamamen şeffaf olacaktır.