【问题标题】:Structure for hierarchal Component storage分层组件存储结构
【发布时间】:2009-04-15 21:14:12
【问题描述】:

我这几天一直在脑子里琢磨这个问题,但没有得出任何令人满意的结论,所以我想我会向 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);

并让它从可渲染向下迭代整个树。我使用一个非常丑陋的地图/向量/树节点结构和一个自定义前向迭代器来跟踪我所在位置的节点堆栈,这几乎可以完成这项工作。不过,在实施的过程中,我觉得必须有一种更好、更清晰的方法……我只是想不出一个:(

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

感谢您的任何意见!

【问题讨论】:

    标签: c++ stl


    【解决方案1】:

    您应该考虑需要多久执行一次以下操作:

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

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

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

    map<string,set<componenet *>>  myTypeList
    

    然后对于类型为 component::renderable::sprite 的对象

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

    通过在多个列表中注册每个对象,然后对给定类型和子类型的所有对象执行某些操作变得容易

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

    【讨论】:

    • 我需要遍历的不仅仅是添加/删除,而且我计划一次处理几百到几千个组件,具体取决于具体情况。我已经考虑过你说的方法,还没有排除,但我不是存储要求的忠实粉丝。不过可能是最好的选择。
    • 你看过 boost::graph 吗?
    • 其实我有。也强烈考虑过它,但我的一些快速测试显示,当以我需要的方式使用时,一些非平凡的性能命中。可能只是我用错了。我得再看看。
    • 也许你的解决方案和我的混合解决方案会起作用。我会把它添加到我的答案中
    • 嗯...现在这很有趣!我认为我不能以那种形式使用它,但它给了我一些想法!我得玩一会儿。
    【解决方案2】:

    答案取决于您需要它们的顺序。您几乎可以选择预购、后购和中购。因此,在广度优先和深度优先搜索方面有明显的相似之处,通常你很难击败它们。

    现在,如果您稍微限制一下问题,则有许多老式算法可以将任意数据树存储为数组。在 FORTRAN 时代,我们经常使用它们。其中一个的关键技巧是将 A 的孩子(比如 A2 和 A3)存储在 index(A)*2,index(A)*2+1 处。问题是如果你的树是稀疏的,你会浪费空间,而且你的树的大小受数组大小的限制。但是,如果我没记错的话,您可以通过简单的 DO 循环以广度优先顺序获取元素。

    看看 Knuth Volume 3,里面有很多这样的东西。

    【讨论】:

      【解决方案3】:

      如果您想查看现有实现的代码,Cowboy Programming 页面中引用的 Game Programming Gems 5 文章附带了我们用于组件系统的代码的精简版本(我做了相当一部分该文章中描述的系统的设计和实现)。

      我需要返回并重新检查代码,我现在不能这样做,我们没有按照您显示的方式在层次结构中表示事物。尽管组件在代码中存在于类层次结构中,但运行时表示是一个平面列表。组件只是声明了它们实现的接口列表。用户可以查询接口或具体类型。

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

      【讨论】:

        猜你喜欢
        • 2011-09-20
        • 1970-01-01
        • 2014-06-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-05-03
        • 2018-08-25
        • 2011-01-19
        相关资源
        最近更新 更多