设计基于组件的游戏


16

我正在写一个射击游戏(例如1942年,经典的2D图形),我想使用基于组件的方法。到目前为止,我考虑了以下设计:

  1. 每个游戏元素(飞艇,弹丸,加电,敌人)都是一个实体

  2. 每个实体都是一组可以在运行时添加或删除的组件。例如位置,雪碧,健康,IA,损坏,边界框等。

这个想法是飞艇,弹丸,敌人,威能不是游戏类。实体仅由其拥有的组件定义(并且可以随时间变化)。因此,玩家飞艇从Sprite,Position,Health和Input组件开始。上电有Sprite,Position,BoundingBox。等等。

主循环管理游戏“物理”,即组件之间如何交互:

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()); 

...

组件在主C ++应用程序中进行了硬编码。实体可以在XML文件中定义(lua或python文件中的IA部分)。

主循环对实体并不太在意:它仅管理组件。该软件设计应允许:

  1. 给定一个组件,获取它所属的实体

  2. 给定一个实体,获取类型为“ type”的组件

  3. 对于所有实体,做点什么

  4. 对于所有实体的组件,执行某些操作(例如:序列化)

我在考虑以下问题:

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;

通过这种设计,我可以获得#1,#2,#3(感谢boost :: fusion :: map算法)和#4。同样,一切都是O(1)(好的,不完全是,但是仍然非常快)。

还有一个更“常见”的方法:

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
...
};

另一个方法是摆脱Entity类:每种Component类型都位于其自己的列表中。因此,有一个Sprite列表,一个Health列表,一个Damage列表等。我知道由于实体ID,它们属于同一逻辑实体。这比较简单,但速度较慢:IA组件基本上需要访问所有其他实体的组件,这将需要在每个步骤中搜索其他组件的列表。

您认为哪种方法更好?boost :: fusion贴图适合以这种方式使用吗?


2
为什么要投票?这个问题怎么了?
艾米利亚诺

Answers:


6

我发现基于组件的设计和面向数据的设计是齐头并进的。您说拥有同质的组件列表并消除一流的实体对象(而不是在组件本身上选择实体ID)会“变慢”,但这并不存在,因为您实际上没有剖析任何实施两种方法得出该结论。事实上,我几乎可以保证,由于面向数据的设计具有多种优势-简化并行化,缓存利用率,模块化等,因此使组件均匀化并避免传统的繁重虚拟化将更快

我并不是说这种方法适用于所有情况,但是组件系统基本上是数据集合,需要在每个帧上对其执行相同的转换,只是尖叫着面向数据。有时组件需要与不同类型的其他组件进行通信,但这两种方式都是必不可少的。但是,它不应该驱动设计,因为即使在极端情况下(例如消息队列和Future)并行处理所有组件的情况下,也有解决此问题的方法。

毫无疑问,Google围绕与​​基于组件的系统相关的面向数据的设计而来,因为这个话题出现了很多,并且存在很多讨论和轶事数据。


“面向数据”是什么意思?
艾米利亚诺

Google上有很多信息,但是弹出的一篇不错的文章应该提供高层次的概述,然后再进行与组件系统相关的讨论:gamefromwithin.com/data-directional-designgamedev。 net / topic /…
Skyler York

我不同意关于DOD的所有事情,因为我认为它本身并不能完成,我的意思是只有DOD可以建议一个很好的方法来存储数据,但是对于调用函数和过程,您需要使用过程或OOP方法,我的意思是问题在于如何结合使用这两种方法以在性能和编码易用性方面获得最大的收益。在结构中,我建议当所有实体不共享某些组件但会使用DOD轻松解决时会出现性能问题,您只需为不同类型的实体创建不同的数组即可。
Ali1S232

这不能直接回答我的问题,但是非常有用。我回想起大学时代的一些有关数据流的事情。到目前为止,这是最好的答案,并且“胜出”。
艾米利亚诺

-1

如果我要编写这样的代码,我宁愿使用此方法(如果对您很重要,我也不会使用任何增强方法),因为它可以完成您想要的所有事情,但是问题是当有太多的实体时那些不共享某些组件的组件,找到那些拥有组件的组件会消耗一些时间。除此之外,我没有其他问题可以解决:

// 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;
}

在此方法中,每个组件都是实体的基础,因此给定组件的指针也是实体!您要求的第二件事是直接访问某些实体的组件,例如。当我需要访问我使用的其中一个实体中的损坏时dynamic_cast<damage*>(entity)->value,因此如果entity包含损坏组件,它将返回该值。如果您不确定entity组件是否损坏,则可以轻松检查if (dynamic_cast<damage*> (entity))返回值,dynamic_cast如果强制类型转换无效,则返回值始终为NULL;如果有效,则使用相同的指针但具有所请求的类型。这样做的东西都在entities其中有一些component你可以像下面

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

如果还有其他问题,我将很乐意回答。


为什么我得到了否决票?我的解决方案出了什么问题?
Ali1S232 2011年

3
您的解决方案实际上不是基于组件的解决方案,因为组件没有与游戏类分离。您的实例全部依赖于IS A关系(继承),而不是HAS A关系(组成)。做到这一点的方式(实体解决了几个组件)给您带来了很多优于继承模型的优点(这通常就是为什么使用组件)。您的解决方案没有提供基于组件的解决方案的任何优势,并且引入了一些怪癖(多重继承等)。没有数据局部性,没有单独的组件更新。没有组件的运行时修改。
无效

首先,这个问题要求结构,即每个组件实例仅与一个实体相关,并且可以通过仅bool isActive在基本组件类中添加来激活和停用组件。仍有需要引进使用的组件,当你正在定义enteties,但我不认为这是一个问题,而且还是你有seprate componnent更新(记得财产以后像dynamic_cast<componnet*>(entity)->update()
Ali1S232

我同意当他想拥有可以共享数据的组件时仍然会出现问题,但是考虑到他的要求,我想这不会有问题,同样,对于该问题也有一些技巧,如果您希望我能解释。
Ali1S232

虽然我同意可以以这种方式实现它,但我认为这不是一个好主意。除非您拥有一个继承所有可能组件的über类,否则设计人员无法自行组成对象。而且,尽管您只能在一个组件上调用update,但是它不会具有良好的内存布局,在组合模型中,可以将所有相同类型的组件实例保持在内存中并进行迭代,而不会丢失任何缓存。您还依赖RTTI,由于性能原因,通常在游戏中将其关闭。排序良好的对象布局可以解决大多数问题。
无效
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.