组件化游戏引擎设计

322

我一直在研究游戏引擎设计(特别是专注于2D游戏引擎,但也适用于3D游戏),对如何实现它很感兴趣。我听说现在许多引擎都采用组件化设计而不是传统的深层次对象层级。

您是否知道有哪些好的链接提供这些设计通常如何实现的信息?我看过evolve your hierarchy,但我真的找不到更多详细信息的链接(大多数似乎只是说“使用组件而不是层级”,但我发现在两种模型之间转换思维需要花费一些精力)。

任何关于此的好链接或信息都将不胜感激,即使是书籍,但首选链接和详细答案。

8个回答

316

2013年1月7日更新如果您想看到一个良好混合组件式游戏引擎和(在我看来)优越的反应式编程方法,请查看V-Play引擎。它非常好地集成了QT的QML属性绑定功能。

我们在大学进行了一些关于游戏中CBSE的研究,我在这些年里收集了一些材料:

游戏中的CBSE文献:

  • 游戏引擎架构
  • 游戏编程宝典4:管理游戏实体的系统
  • 游戏编程宝典5:基于组件的对象管理
  • 游戏编程宝典5:通用组件库
  • 游戏编程宝典6:游戏对象组件系统
  • 面向对象的游戏开发
  • Java游戏引擎核心架构与实现(德语)

C#中一个非常好且干净的组件式游戏引擎示例是Elephant游戏框架

如果您真的想知道组件是什么,请阅读:基于组件的软件工程!他们将组件定义为:

软件组件是符合组件模型的软件元素,可以根据组合标准独立部署和组合而无需修改。

组件模型定义了特定的交互和组合标准。组件模型实现是支持符合该模型的组件执行所需的专用可执行软件元素集。

软件组件基础设施是一组相互作用的软件组件,旨在确保使用这些组件和接口构建的软件系统或子系统将满足明确定义的性能规格。

在使用CBSE进行游戏开发2年后,我的看法是面向对象编程已经走到了死胡同。当您的组件变得越来越小,并且更像是打包了许多无用开销的函数时,请记住我的警告。请改用函数式反应式编程。还可以看看我最新的博客文章(写这篇文章的时候,我就是因为它才看到了这个问题 :)):为什么我从组件式游戏引擎架构转向FRP

游戏中的CBSE论文:

按相关性排序的游戏CBSE网站链接:


2
我很难找到关于游戏引擎中FRP的资源。你能提供一些代码或链接吗? - svenstaro
1
FRP通常是一个小领域,特别是在游戏中。它已经存在多年,但仍然相当前沿。如果您搜索与Haskell语言相关的函数响应式编程,您会发现大部分研究都集中在这方面。关键项目包括Fruit、Fran和Yampa。Yampa Arcade是一篇描述使用Yampa反应库进行游戏开发的论文。尽管可能有一些使用新的.NET反应式技术的Silverlight项目,但我还没有听说过任何实际的实现。 - CodexArcanum
12
一个非常好且干净的基于组件的C#游戏引擎示例是“Elephant”游戏框架。然而,它从未完成,并没有解决任何实际问题,比如组件/实体之间的通信。 - Den
13
我是“大象东西”的作者,在任何人决定使用那个老东西之前,我想指出我发布了一种替代品叫做ComponentKit。虽然它没有提供与游戏相关的任何内容,但至少可以作为一个有用的参考,展示这样一个系统如何实现。 - jhauberg
5
我理解它的意思是“软件组件就像一头软件大象”...太累了。 - bobobobo
显示剩余4条评论

9

关于这个主题似乎缺乏信息。我最近实施了这个系统,发现一份非常好的GDC幻灯片对通常被忽略的细节进行了很好的解释。该文档在此处:游戏对象组件架构的理论与实践

除了那个幻灯片外,还有一些好资源各种博客。PurplePwny进行了良好的讨论并链接到其他一些资源。Ugly Baby Studios围绕组件如何相互交互的想法进行了一些讨论。祝你好运!


第一张幻灯片很棒,引人深思! - Ricket
1
@Noah:GDC ppt链接已经失效了,你还有其他地方存储这个ppt吗? :-) - gentimouton
目前还没有,但下班后我会查找一下,看看是否在其他地方放置了备份副本。 - Noah Callaway
嘿,我下載了這份PPT(鏈接有效),然後意識到我5年前已經參加過這個演講了,謝謝你的提醒。一般來說,在你的組件中不要放太多行為,否則會導致程式碼混亂和最終的瘋狂。應該優先考慮使用“愚笨”的組件來保存數據,並將行為放入實體處理器中。 - yoyo

7

虽然不是有关游戏引擎设计的完整教程,但我发现这个页面对于使用组件架构制作游戏具有一些好的细节和示例。


5

2
第一个链接已经移动到这里:http://www.unseen-academy.de/snippet_component_system.html。 - Andrew

4
我在上个学期为游戏开发课程研究和实现了这个东西。希望这段示例代码能够指引你如何处理这个问题。
class Entity {
public:
    Entity(const unsigned int id, const std::string& enttype);
    ~Entity();

    //Component Interface
    const Component* GetComponent(const std::string& family) const;
    void SetComponent(Component* newComp);
    void RemoveComponent(const std::string& family);
    void ClearComponents();

    //Property Interface
    bool HasProperty(const std::string& propName) const;
    template<class T> T& GetPropertyDataPtr(const std::string& propName);
    template<class T> const T& GetPropertyDataPtr(const std::string& propName) const;

    //Entity Interface
    const unsigned int GetID() const;
    void Update(float dt);

private:
    void RemoveProperty(const std::string& propName);
    void ClearProperties();
    template<class T> void AddProperty(const std::string& propName);
    template<class T> Property<T>* GetProperty(const std::string& propName);
    template<class T> const Property<T>* GetProperty(const std::string& propName) const;

    unsigned int m_Id;
    std::map<const string, IProperty*> m_Properties;
    std::map<const string, Component*> m_Components;
};

组件指定行为并对属性进行操作。属性通过引用在所有组件之间共享,并且可以免费更新。这意味着没有传递消息的大量开销。如有任何问题,我将尽力给出最佳答案。


1
那么你使用属性来让组件彼此交流吗?这种方法不会破坏封装性吗?基本上,你是将属性用作一堆全局变量。 - Emiliano
除了happy_emi的评论之外,您刚刚通过交换“大开销的字符串查找和糟糕的缓存一致性”来进行了消息传递,这意味着您使用属性带来了大量的开销。您实现的组件部分看起来很好,但是属性部分没有意义——要么将它们作为实际字段放在实体上,以便组件可以设置,要么保持组件间的引用关系。 - user79758
属性仅在组件创建时查找并存储为指针。获取实体上的“共享”数据只需一次成本。该数据在“全局”意义上仅表示所有组件都可以访问其所需的实体上的任何数据。我不是在谈论纯字符串查找,而是额外调用的代码。请记住,您的游戏中可能有大量实体。每个实体在每个游戏循环中更新其位置的消息传递是很多无用开销,而您只需让一个组件设置数据即可。 - Macuzza
也许举个例子会更好理解。假设你的实体有一个路径组件和一个渲染组件,两者都需要 Vec3 位置。顺序是任意的,但假设先创建了 Render 组件。Render 请求实体的 Vec3 位置属性,在实体上创建该属性,并将指针给予 Render。现在 Pathing 被创建,它请求相同的 Vec3 位置,实体返回刚刚创建的属性(实际上是属性内部的原始数据)的指针。此时,当 Pathing 更新位置时,Render 可以绘制而无需请求新的位置数据。 - Macuzza

4

1
在这个上下文中,组件对我来说听起来像是引擎的隔离运行时部分,可能与其他组件同时执行。如果这是动机,那么您可能需要查看actor model和使用它的系统。

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接