C ++ Entity-Component-Systems'ımdaki bileşenlere nasıl düzgün bir şekilde erişebilirim?


18

(Açıkladığım şey bu tasarıma dayanıyor: Varlık sistemi çerçevesi nedir ?, Aşağı kaydırın ve bulacaksınız)

C ++ bir varlık bileşen sistemi oluşturmakta bazı sorunlar yaşıyorum. Bileşen sınıfım var:

class Component { /* ... */ };

Bu aslında diğer bileşenler için yaratılacak bir arayüz. Böylece, özel bir bileşen oluşturmak için, sadece arayüzü uyguluyorum ve oyun içinde kullanılacak verileri ekliyorum:

class SampleComponent : public Component { int foo, float bar ... };

Bu bileşenler, her Entity örneğine benzersiz bir kimlik veren bir Entity sınıfının içinde saklanır:

class Entity {
     int ID;
     std::unordered_map<string, Component*> components;
     string getName();
     /* ... */
};

Bileşenler, bileşenin adını karmak suretiyle varlığa eklenir (bu muhtemelen harika bir fikir değildir). Özel bir bileşen eklediğimde, Bileşen türü (temel sınıf) olarak depolanır.

Öte yandan, içinde bir Düğüm arabirimi kullanan bir Sistem arabirimim var. Düğüm sınıfı, tek bir varlığın bileşenlerinden bazılarını depolamak için kullanılır (Sistem, varlığın tüm bileşenlerini kullanmak istemiyorsa). Sistem gerektiğinde update(), yalnızca farklı varlıklardan oluşturulan depoladığı Düğümler üzerinden yineleme yapılması gerekir. Yani:

/* System and Node implementations: (not the interfaces!) */

class SampleSystem : public System {
        std::list<SampleNode> nodes; //uses SampleNode, not Node
        void update();
        /* ... */
};

class SampleNode : public Node {
        /* Here I define which components SampleNode (and SampleSystem) "needs" */
        SampleComponent* sc;
        PhysicsComponent* pc;
        /* ... more components could go here */
};

Şimdi sorun: diyelim ki bir varlığı SampleSystem'a geçirerek SampleNodes'u oluşturdum. Ardından SampleNode, varlığın SampleSystem tarafından kullanılacak gerekli bileşenlere sahip olup olmadığını "kontrol eder". Varlık içinde istenen bileşene erişmem gerektiğinde sorun ortaya çıkıyor: bileşen bir Component(temel sınıf) koleksiyonunda saklanıyor , bu yüzden bileşene erişemiyorum ve yeni düğüme kopyalayamıyorum. Sorunu geçici olarak Componenttüretilmiş bir türe dökerek çözdüm , ancak bunu yapmanın daha iyi bir yolu olup olmadığını bilmek istedim. Bunun zaten sahip olduğum şeyi yeniden tasarlamak anlamına gelip gelmediğini anlıyorum. Teşekkürler.

Yanıtlar:


23

Leri Componentbir koleksiyonda bir arada saklayacaksanız, koleksiyonda saklanan tür olarak ortak bir temel sınıf kullanmanız gerekir ve bu nedenle Componentkoleksiyondaki s'ye erişmeye çalıştığınızda doğru türe atamanız gerekir . Yanlış türetilmiş sınıfa geçmeye çalışmanın sorunları, şablonların ve typeidfonksiyonun akıllıca kullanılmasıyla ortadan kaldırılabilir :

Bir harita şöyle beyan edildiğinde:

std::unordered_map<const std::type_info* , Component *> components;

gibi bir addComponent işlevi:

components[&typeid(*component)] = component;

ve bir getComponent:

template <typename T>
T* getComponent()
{
    if(components.count(&typeid(T)) != 0)
    {
        return static_cast<T*>(components[&typeid(T)]);
    }
    else 
    {
        return NullComponent;
    }
}

Yanlış kullanım olmayacak. Bunun nedeni typeid, bileşenin çalışma zamanı türünün (en türetilmiş tür) tür bilgisine bir işaretçi döndürecektir. Bileşen, anahtar olarak bu tür bilgilerle birlikte depolandığından, döküm, eşleşmeyen türler nedeniyle sorunlara neden olamaz. Bir tür Bileşeni türetilen veya olmak zorunda olduğu da şablon türüne derleme zamanı tür denetlemesi almak static_cast<T*>ile türlerini uyumsuz olacaktır unordered_map.

Bununla birlikte, farklı tipteki bileşenleri ortak koleksiyonda saklamanız gerekmez. Entityİçeren bir Components fikrinden vazgeçerseniz ve bunun yerine her bir Componentdepoya sahipseniz Entity(gerçekte muhtemelen bir tamsayı kimliği olacaktır), o zaman türetilmiş her bileşen türünü, yerine türetilmiş türün kendi koleksiyonunda saklayabilirsiniz ortak taban türünü seçin ve bu kimliğe göre Component"ait" s'i bulun Entity.

Bu ikinci uygulama, birincisinden daha düşünmek için biraz daha sezgisel değildir, ancak muhtemelen bir arayüzün arkasındaki uygulama ayrıntıları olarak gizlenebilir, böylece sistem kullanıcılarının ilgilenmesi gerekmez. Ben gerçekten ikinci kullanmadım gibi hangi daha iyi yorum yapmayacağım, ama static_cast ilk uygulama sağladığı gibi türleri üzerinde güçlü bir garanti ile bir sorun olarak görmüyorum. Platform ve / veya felsefi inançlara bağlı olarak bir sorun olabilecek veya olmayabilecek RTTI gerektirdiğini unutmayın.


3
Yaklaşık 6 yıldır C ++ kullanıyorum, ancak her hafta yeni bir numara öğreniyorum.
knight666

Cevabın için teşekkür ederim. İlk önce ilk yöntemi kullanmayı deneyeceğim ve belki daha sonra diğerini kullanmanın bir yolunu düşüneceğim. Ancak, addComponent()yöntemin de bir şablon yöntemi olması gerekmez mi? Bir tanımlanırsam addComponent(Component* c), eklediğim herhangi bir alt bileşen bir Componentişaretçide depolanır ve typeidher zaman Componenttemel sınıfa başvurur .
Federico

2
Typeid, işaretçi bir temel sınıfta olsa bile, işaret edilen nesnenin gerçek türünü verecektir
Chewy Gumball 22:30 '

Gerçekten chewy'nin cevabını beğendim, bu yüzden mingw32'de bir uygulamayı denedim. Ben fid riko tarafından addComponent () her şeyi bir bileşen olarak depolar çünkü typeid her şeyin türü olarak bileşen döndürüyor çünkü belirtilen koştu. Burada birisi, tip kimliğinin, işaretçi bir temel sınıfa olsa bile, işaret edilen nesnenin gerçek türünü vermesi gerektiğini belirtti, ancak bence derleyiciye vb. Göre değişebilir. Başka kimse bunu doğrulayabilir mi? Windows 7'de g ++ std = c ++ 11 mingw32 kullanıyordum Sadece getComponent () 'i bir şablon olarak değiştirerek bitirdim, daha sonra bu türden türüne kaydettim
shwoseph 1

Bu derleyiciye özgü değildir. Büyük olasılıkla, typeid işlevine argüman olarak doğru ifadeye sahip değilsinizdir.
Chewy Gumball

17

Chewy doğru, ancak C ++ 11 kullanıyorsanız kullanabileceğiniz bazı yeni türleriniz var.

const std::type_info*Haritanızda anahtar olarak kullanmak yerine, etrafındaki bir sarıcı olan std::type_index( bkz. Cppreference.com ) kullanabilirsiniz std::type_info. Neden kullanasın ki? std::type_indexAslında ilişki saklayan std::type_infobir işaretçi olarak, ancak bu konuda endişe sizin için bir işaretçi az.

Gerçekten C ++ 11 kullanıyorsanız, Componentreferansları akıllı işaretçilerin içinde saklamanızı tavsiye ederim . Harita şöyle olabilir:

std::map<std::type_index, std::shared_ptr<Component> > components

Yeni bir giriş eklemek şu şekilde yapılabilir:

components[std::type_index(typeid(*component))] = component

componenttürü nerede std::shared_ptr<Component>. Belirli bir tür referansı almak şu şekilde Componentolabilir:

template <typename T>
std::shared_ptr<T> getComponent()
{
    std::type_index index(typeid(T));
    if(components.count(std::type_index(typeid(T)) != 0)
    {
        return static_pointer_cast<T>(components[index]);
    }
    else
    {
        return NullComponent
    }
}

static_pointer_castBunun yerine kullanımını da unutmayın static_cast.


1
Aslında bu tür bir yaklaşımı kendi projemde kullanıyorum.
vijoc

Bu aslında oldukça uygundur, çünkü referans olarak C ++ 11 standardını kullanarak C ++ öğreniyordum. Yine de fark ettiğim bir şey, web'de bulduğum tüm varlık bileşen sistemlerinin bir çeşit kullanması cast. Bunu ya da benzeri bir sistem tasarımını yayın yapmanın imkansız olacağını düşünmeye başlıyorum.
Federico

@Fede İşaretçilerin Componenttek bir kapta saklanması zorunlu olarak türetilmiş türe dönüştürülmesini gerektirir. Ancak, Chewy'nin belirttiği gibi, döküm gerektirmeyen başka seçenekleriniz de var. Tasarımda bu tür kalıplara sahip olmanın kendisinde "kötü" bir şey görmüyorum, çünkü nispeten güvenli.
vijoc

@vijoc Girebilecekleri bellek tutarlılığı sorunu nedeniyle bazen kötü olarak değerlendirilirler.
akaltar
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.