Bileşen tabanlı bir oyun tasarlama


16

Bir nişancı yazıyorum (1942, klasik 2D grafikler gibi) ve bileşen tabanlı bir yaklaşım kullanmak istiyorum. Şimdiye kadar aşağıdaki tasarımı düşündüm:

  1. Her oyun öğesi (zeplin, mermi, güçlendirme, düşman) bir Varlıktır

  2. Her Varlık, çalışma zamanında eklenebilen veya çıkarılabilen bir bileşenler kümesidir. Örnekler, Konum, Sprite, Sağlık, IA, Hasar, Sınırlayıcı Kutu vb.

Fikir, Zeplin, Mermi, Düşman, Güçlendirme oyun sınıfları DEĞİLDİR. Bir varlık yalnızca sahip olduğu bileşenler tarafından tanımlanır (ve bu süre içinde değişebilir). Böylece Airship oyuncusu Sprite, Position, Health ve Input bileşenleri ile başlar. Bir powerup Sprite, Position, BoundingBox'a sahiptir. Ve bunun gibi.

Ana döngü oyunu "fizik", yani bileşenlerin birbirleriyle nasıl etkileşime girdiğini yönetir:

foreach(entity (let it be entity1) with a Damage component)
    foreach(entity (let it be entity2) with a Health component)
    if(the entity1.BoundingBox collides with entity2.BoundingBox)
    {
        entity2.Health.decrease(entity1.Damage.amount());
    }

foreach(entity with a IA component)
    entity.IA.update(); 

foreach(entity with a Sprite component)
    draw(entity.Sprite.surface()); 

...

Bileşenler ana C ++ uygulamasında sabit kodlanmıştır. Varlıklar bir XML dosyasında tanımlanabilir (lua veya python dosyasındaki IA bölümü).

Ana döngü, varlıkları çok fazla önemsemez: sadece bileşenleri yönetir. Yazılım tasarımı şunları yapmalıdır:

  1. Bir bileşen verildiğinde, ait olduğu varlığı alın

  2. Bir varlık verildiğinde, "type" türündeki bileşeni alın

  3. Tüm varlıklar için bir şeyler yapın

  4. Tüm varlığın bileşeni için bir şeyler yapın (örneğin: seri hale getirme)

Aşağıdakileri düşünüyordum:

class Entity;
class Component { Entity* entity; ... virtual void serialize(filestream, op) = 0; ...}
class Sprite : public Component {...};
class Position : public Component {...};
class IA : public Component {... virtual void update() = 0; };

// I don't remember exactly the boost::fusion map syntax right now, sorry.
class Entity
{
   int id; // entity id
   boost::fusion::map< pair<Sprite, Sprite*>, pair<Position, Position*> > components;
   template <class C> bool has_component() { return components.at<C>() != 0; }
   template <class C> C* get_component() { return components.at<C>(); }
   template <class C> void add_component(C* c) { components.at<C>() = c; }
   template <class C> void remove_component(C* c) { components.at<C>() = 0; }
   void serialize(filestream, op) { /* Serialize all componets*/ }
...
};

std::list<Entity*> entity_list;

Bu tasarım ile # 1, # 2, # 3 (boost :: fusion :: map algoritmaları sayesinde) ve # 4 elde edebilirim. Ayrıca her şey O (1) (Tamam, tam olarak değil, ama yine de çok hızlı).

Daha "ortak" bir yaklaşım da vardır:

class Entity;
class Component { Entity* entity; ... virtual void serialize(filestream, op) = 0; ...}
class Sprite : public Component { static const int type_id = 0; };
class Position : public Component { static const int type_id = 1; };

class Entity
{
   int id; // entity id
   std::vector<Component*> components;
   bool has_component() { return components[i] != 0; }
   template <class C> C* get_component() { return dynamic_cast<C> components[C::id](); } // It's actually quite safe
...
};

Başka bir yaklaşım Entity sınıfından kurtulmaktır: her bir Bileşen türü kendi listesinde yaşar. Yani bir Sprite listesi, bir Sağlık listesi, Hasar listesi vb. Varlıkları biliyorum çünkü aynı mantık varlığına aitler. Bu daha basit, ancak daha yavaş: IA bileşenlerinin temel olarak diğer tüm varlık bileşenlerine erişmesi gerekir ve bu da her adımda birbirlerinin bileşenlerinin aranmasını gerektirir.

Hangi yaklaşımın daha iyi olduğunu düşünüyorsunuz? boost :: fusion haritası bu şekilde kullanılmaya uygun mu?


2
neden bir aşağı oy? Bu sorunun nesi var?
Emiliano

Yanıtlar:


6

Bileşen tabanlı tasarımın ve veri odaklı tasarımın el ele gittiğini gördüm. Bileşenlerin homojen listelerine sahip olmanın ve birinci sınıf varlık nesnesinin ortadan kaldırılmasının (bunun yerine bileşenlerin kendisinde varlık kimliğini seçmenin) "daha yavaş" olacağını söylüyorsunuz. bu sonuca varmak için her iki yaklaşımı da uygular. Nitekim, veri odaklı tasarımın çeşitli avantajları - daha kolay paralelleştirme, önbellek kullanımı, modülerlik vb.Nedeniyle bileşenlerinizi homojenleştirmenin ve geleneksel ağır sanallaştırmadan kaçınmanın daha hızlı olacağını neredeyse garanti edebilirim .

Bu yaklaşımın her şey için ideal olduğunu söylemiyorum, ancak temelde her karede aynı dönüşümleri gerektiren veri koleksiyonları olan bileşen sistemleri, sadece veri odaklı olmak için çığlık atıyor. Bileşenlerin farklı tipteki diğer bileşenlerle iletişim kurması gereken zamanlar olacaktır, ancak bu her iki şekilde de gerekli bir kötülük olacaktır. Bununla birlikte, tasarımı yönlendirmemelidir, çünkü tüm bileşenleri mesaj kuyrukları ve vadeli işlemler gibi paralel olarak işlenirse bile, bu sorunu çözmenin yolları vardır .

Kesinlikle Google, bileşen tabanlı sistemlerle ilgili olduğu için veri odaklı tasarım için etrafta, çünkü bu konu çok ortaya çıkıyor ve orada biraz tartışma ve anekdotsal veriler var.


"veri odaklı" ile ne demek istiyorsun?
Emiliano

Google'da çok fazla bilgi var, ancak burada üst düzey bir genel bakış sunması gereken bir makale ve ardından bileşen sistemleriyle ilgili bir tartışma var: gamesfromwithin.com/data-oriented-design , gamedev. net / topic /…
Skyler York

DOD ile ilgili her şeye katılıyorum, çünkü kendisinin tamamlanamayacağını düşünüyorum, sadece DOD veri depolamak için çok iyi bir aprroch önerebilir, ancak işlevleri veya prosedürleri çağırmak için prosedürel veya OOP yaklaşımı, sorun, bu iki yöntemin hem performans hem de kodlama kolaylığı için en fazla fayda sağlamak için nasıl birleştirileceği, yani. tüm varlıklar bazı bileşenleri paylaşmadığında performans sorunu olacağını, ancak DOD kullanarak kolayca çözülebileceğini önerdiğim yapıda, sadece farklı türler için farklı diziler yapmanız gerekir.
Ali1S232

Bu sorum doğrudan cevaplamıyor ama çok bilgilendirici. Üniversite günlerimde Dataflows hakkında bir şeyler hatırladım. Şimdiye kadarki en iyi cevap ve "kazanıyor".
Emiliano

-1

ben böyle bir kod yazmak olsaydı ben oldukça bu yaklaşımı kullanmak ot olurdu (ve sizin için önemli ise herhangi bir destek kullanmıyorum), çünkü istediğiniz her şeyi yapabilir ama sorun çok enteties olduğunda bazı bileşenleri paylaşmayan, sahip olanlar bulmak biraz zaman alacaktır. Bunun dışında ben bir şey olabilir başka bir sorun yok:

// declare components here------------------------------
class component
{
};

class health:public component
{
public:
    int value;
};

class boundingbox:public component
{
public :
    int left,right,top,bottom;
    bool collision(boundingbox& other)
    {
        if (left < other.right || right > other.left)
            if (top < other.bottom || bottom > other.top)
                return true;
        return false;
    }
};

class damage : public component
{
public:
    int value;
};

// declare enteties here------------------------------

class entity
{
    virtual int id() = 0;
    virtual int size() = 0;
};

class aircraft :public entity, public health,public boundingbox
{
    virtual int id(){return 1;}
    virtual int size() {return sizeof(*this);};
};

class bullet :public entity, public damage, public boundingbox
{
    virtual int id(){return 2;}
    virtual int size() {return sizeof(*this);};
};

int main()
{
    entity* gameobjects[3];
    gameobjects[0] = new aircraft;
    gameobjects[1] = new bullet;
    gameobjects[2] = new bullet;
    for (int i=0;i<3;i++)
        for(int j=0;j<3;j++)
            if (dynamic_cast<boundingbox*>(gameobjects[i]) && dynamic_cast<boundingbox*>(gameobjects[j]) &&
                dynamic_cast<boundingbox*>(gameobjects[i])->collision(*dynamic_cast<boundingbox*>(gameobjects[j])))
                if (dynamic_cast<health*>(gameobjects[i]) && dynamic_cast<damage*>(gameobjects[j]))
                    dynamic_cast<health*>(gameobjects[i])->value -= dynamic_cast<damage*>(gameobjects[j])->value;
}

bu yaklaşımda her bileşen bir varlık için temel oluşturur, bu nedenle bileşen göz önüne alındığında bileşen aynı zamanda bir varlıktır! İstediğiniz ikinci şey, bazı varlık bileşenlerine doğrudan erişime sahip olmaktır. kullandığım varlıklardan birinde hasara erişmem dynamic_cast<damage*>(entity)->valuegerektiğinde entity, hasar bileşeni varsa değeri iade edecektir. eğer entitybileşen hasarı olup olmadığından emin değilseniz, oyuncu kadrosu geçerli değilse ve aynı bir işaretçi değilse, ancak istenen tipte ise, if (dynamic_cast<damage*> (entity))dönüş değerinin dynamic_casther zaman NULL olup olmadığını kolayca kontrol edebilirsiniz . böylece entitiesbazı şeyleri olan bir şey componentyapmak için aşağıdaki gibi yapabilirsiniz

for (int i=0;i<enteties.size();i++)
    if (dynamic_cast<component*>(enteties[i]))
        //do somthing here

başka sorularınız varsa cevaplamaktan memnuniyet duyarım.


niye oy verdim? çözümümdeki sorun neydi?
Ali1S232

3
Bileşenleriniz oyun sınıflarınızdan ayrılmadığından çözümünüz aslında bileşen tabanlı bir çözüm değildir. Örnekleriniz, HAS A ilişkisi (kompozisyon) yerine IS A ilişkisine (kalıtım) dayanır. Kompozisyon yolunu (varlıklar birkaç bileşene hitap eder) size miras modeline göre çok fazla avantaj sağlar (genellikle bileşenleri kullanmanızın nedeni budur). Çözümünüz, bileşen tabanlı bir çözümün faydalarından hiçbirini sağlamaz ve bazı tuhaflıklar getirir (çoklu kalıtım vb.). Veri konumu yok, ayrı bileşen güncellemesi yok. Bileşenlerde çalışma zamanı değişikliği yok.
geçersiz

her şeyden önce soru, her bileşen örneğinin yalnızca bir varlıkla ilgili olduğunu yapılandırır ve bileşenleri yalnızca bir bool isActivetemel commponent sınıfına ekleyerek bileşenleri etkinleştirebilir veya devre dışı bırakabilirsiniz. Enteties tanımlarken kullanılabilir bileşenlerin tanıtımı için hala ihtiyaç var, ancak bunu bir sorun olarak görmüyorum ve hala ayrı bileşen güncellemelerine sahipsiniz (şöyle bir şey hatırlayın dynamic_cast<componnet*>(entity)->update().
Ali1S232 17:07

ve veri paylaşabilen bir bileşene sahip olmak istediğinde hala bir sorun olacağını kabul ediyorum, ancak ne istediğini düşünürsek, bunun için bir sorun olmayacağını ve yine bu sorun için bazı hileler olduğunu açıklamak istiyorum istiyorum.
Ali1S232

Bu şekilde uygulamanın mümkün olduğunu kabul etsem de, bunun iyi bir fikir olduğunu düşünmüyorum. Tüm olası bileşenleri miras alan bir über sınıfınız yoksa, tasarımcılarınız nesneleri kendileri oluşturamazsınız. Ve sadece bir bileşende güncelleme çağırabilseniz de, iyi bir bellek içi düzenine sahip olmayacak, aynı modeldeki tüm bileşen örnekleri bellekte yakın tutulabilir ve herhangi bir önbellek özlülemesi olmadan tekrarlanabilir. Performans nedenleriyle oyunlarda genellikle kapalı olan RTTI'ya da güveniyorsunuz. İyi bir sıralama nesnesi düzeni çoğunlukla düzeltir.
geçersiz
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.