分层组件存储的结构

时间:2009-04-15 02:34:15

标签: c++ stl

我已经在脑子里打了几天这个问题并且没有得出任何令人满意的结论所以我想我会问SO工作人员他们的意见。对于我正在处理的游戏,我使用了herehere所述的组件对象模型。它实际上相当不错,但我目前的存储解决方案是限制性的(我只能通过他们的类名或任意“家庭”名称来请求组件)。我想要的是能够请求给定类型并迭代该类型的所有组件或从其派生的任何类型。

在考虑这个问题时,我首先实现了一个简单的RTTI方案,该方案通过派生类型按顺序存储基类类型。这意味着,例如,一个精灵的RTTI将是:component :: renderable :: sprite。这允许我轻松地比较类型,看看类型A是否是从类型B派生而只是通过比较B的所有元素:即,component :: renderable :: sprite是从component :: renderable但不是component :: timer派生的。简单,有效,已经实施。

我现在想要的是一种以表示该层次结构的方式存储组件的方法。首先想到的是使用类型作为节点的树,如下所示:

       component
       /       \
  timer         renderable
  /              /      \
shotTimer   sprite      particle

在每个节点,我会存储该类型的所有组件的列表。这样,请求“component :: renderable”节点将允许我访问所有可渲染组件,而不管派生类型如何。问题是我希望能够使用迭代器访问这些组件,以便我可以这样做:

for_each(renderable.begin(), renderable.end(), renderFunc);

并从可渲染向下迭代整个树。我使用一个非常难看的map / vector / tree节点结构和一个跟踪我去过的节点堆栈的自定义前向迭代器。不过,我一直在实施,我觉得必须有一个更好,更清晰的方式......我只是想不出一个:(

所以问题是:我不必要地过度复杂化了吗?是否有一些明显的简化我缺少,或者我应该使用的预先存在的结构?或者这只是遗传上一个复杂的问题,我可能已经做得很好了?

感谢您的任何输入!

3 个答案:

答案 0 :(得分:1)

您应该考虑需要执行以下操作的频率:

  • 遍历树
  • 从树中添加/删除元素
  • 您需要跟踪多少个对象

哪个更频繁将有助于确定最佳解决方案

也许不是创建一个复杂的树,只需要一个包含所有类型的列表,并为它派生的每个类型添加一个指向该对象的指针。像这样:

map<string,set<componenet *>>  myTypeList

然后是一个类型为component :: renderable :: sprite

的对象
myTypeList["component"].insert(&object);
myTypeList["renderable"].insert(&object);
myTypeList["sprite"].insert(&object);

通过在多个列表中注册每个obejct,可以轻松地对给定类型和子类型的所有对象执行某些操作

for_each(myTypeList["renderable"].begin(),myTypeList["renderable"].end(),renderFunc);

请注意,std :: set和我的std :: map构造可能不是最佳选择,具体取决于您将如何使用它。

或者也许是一种混合方法,只在树中存储类层次

map<string, set<string> > myTypeList;
map<string, set<component *> myObjectList;

myTypeList["component"].insert("component");
myTypeList["component"].insert("renderable");
myTypeList["component"].insert("sprite");
myTypeList["renderable"].insert("renderable");
myTypeList["renderable"].insert("sprite");
myTypeList["sprite"].insert("sprite");

// this isn't quite right, but you get the idea
struct doForList {
    UnaryFunction f;
    doForList(UnaryFunction f): func(f) {};
    operator ()(string typename) { 
       for_each(myTypeList[typename].begin();myTypeList[typename].end(), func);
   }
}

for_each(myTypeList["renderable"].begin(),myTypeList["renderable"].end(), doForList(myFunc))

答案 1 :(得分:0)

答案取决于你需要它们的顺序。你几乎可以选择预购,后序和顺序。因此,在宽度优先和深度优先搜索方面有明显的类比,一般来说,你会遇到麻烦。

现在,如果你将问题限制在一个问题上,那么有许多老式的算法可以将任意数据的树存储为数组。我们在FORTRAN时代经常使用它们。其中一个关键技巧是将A的孩子,比如A2和A3,存储在索引(A)* 2,索引(A)* 2 + 1。问题是,如果树稀疏,则会浪费空间,树的大小会受到数组大小的限制。但是,如果我没记住这一点,你可以通过简单的DO循环获得广度优先的元素。

看看Knuth第3卷,那里有一些东西。

答案 2 :(得分:0)

如果你想查看现有实现的代码,牛仔编程页面中引用的Game Programming Gems 5文章附带了我们用于组件系统的代码的某种简化版本(我做了相当大的一部分设计和实施该文章中描述的系统。)

我需要返回并重新检查代码,我现在无法做到这一点,我们并没有按照您展示的方式表示层次结构中的内容。虽然组件在代码中存在于类层次结构中,但运行时表示是一个平面列表。组件刚刚声明了它们实现的接口列表。用户可以查询接口或具体类型。

因此,在您的示例中,Sprite和Particle将声明它们实现了RENDERABLE接口,如果我们想对所有可渲染内容执行某些操作,我们只需遍历活动组件列表并检查每个可渲染组件。面对它并不是非常有效,但在实践中它很好。它不是一个问题的主要原因是它实际上并不是一个非常常见的操作。例如,renderables之类的东西在创建时将自己添加到渲染场景中,因此全局场景管理器维护了自己的可渲染对象列表,并且从不需要为它们查询组件系统。与phyics和碰撞组件类似,等等。