我一直在研究游戏引擎设计(特别关注 2d 游戏引擎,但也适用于 3d 游戏),并且对如何进行的一些信息感兴趣。我听说现在许多引擎正在转向基于组件的设计,而不是传统的深层对象层次结构。
您是否知道有关此类设计通常如何实施的信息的任何良好链接?我已经看到了你的层次结构的进化,但我真的找不到更多的详细信息(他们中的大多数似乎只是说“使用组件而不是层次结构”,但我发现改变我的想法需要一些努力在两个模型之间)。
任何关于这方面的好的链接或信息都将不胜感激,甚至是书籍,尽管这里的链接和详细答案将是首选。
architecture game-engine entity-system entity-component-system
对于我正在制作的2D游戏(对于Android),我使用的是基于组件的系统,其中GameObject包含多个GameComponent对象.GameComponents可以是输入组件,渲染组件,子弹发射组件等.目前,GameComponents具有对拥有它们的对象的引用并且可以对其进行修改,但GameObject本身只有一个组件列表,并且它不关心组件是什么,只要它们在对象更新时可以更新.
有时组件有一些GameObject需要知道的信息.例如,对于碰撞检测,GameObject将自身注册到碰撞检测子系统,以在其与另一个对象碰撞时被通知.碰撞检测子系统需要知道对象的边界框.我将x和y直接存储在对象中(因为它被几个组件使用),但宽度和高度仅为保存对象位图的渲染组件所知.我想在GameObject中有一个方法getBoundingBox或getWidth来获取该信息.或者一般来说,我想从组件向对象发送一些信息.但是,在我目前的设计中,GameObject不知道它在列表中具有哪些特定组件.
我可以想出几种方法来解决这个问题:
我可以让游戏对象拥有一些重要组件的特定字段,而不是拥有一个完全通用的组件列表.例如,它可以有一个名为renderingComponent的成员变量; 每当我需要获得我刚才使用的对象的宽度时renderingComponent.getWidth()
.这个解决方案仍然允许组件的通用列表,但它以不同的方式处理它们中的一些,并且我担心由于需要查询更多组件,我最终会有几个特殊字段.有些对象甚至没有渲染组件.
将所需信息作为GameObject的成员,但允许组件更新它.因此,对象的宽度和高度默认为0或-1,但渲染组件可以在其更新循环中将它们设置为正确的值.这感觉就像一个黑客,我可能最终会向GameObject类推送很多东西以方便使用,即使并非所有对象都需要它们.
组件实现一个接口,指示可以查询的信息类型.例如,渲染组件将实现HasSize接口,该接口包括getWidth和getHeight等方法.当GameObject需要宽度时,它会遍历其组件,检查它们是否实现了HasSize接口(instanceof
在Java中使用关键字,或is
在C#中).这似乎是一个更通用的解决方案,一个缺点是搜索组件可能需要一些时间(但是,大多数对象只有3或4个组件).
这个问题与具体问题无关.它常常出现在我的设计中,我想知道处理它的最佳方法是什么.性能有点重要,因为这是一个游戏,但每个对象的组件数量通常很小(最大值为8).
简短的版本
在基于组件的游戏系统中,在保持设计通用性的同时,将信息从组件传递到对象的最佳方法是什么?
作为一个小练习,我试图编写一个非常小的,简单的游戏引擎,只处理实体(移动,基本AI等)
因此,我试图考虑游戏如何处理所有实体的更新,我有点困惑(可能是因为我以错误的方式处理它)
所以我决定在这里发布这个问题,向你展示我目前的思考方式,看看是否有人可以向我建议一个更好的方法.
目前,我有一个CEngine类,它指向它需要的其他类(例如CWindow类,CEntityManager类等)
我有一个游戏循环,伪代码会像这样(在CEngine类内)
while(isRunning) {
Window->clear_screen();
EntityManager->draw();
Window->flip_screen();
// Cap FPS
}
Run Code Online (Sandbox Code Playgroud)
我的CEntityManager类看起来像这样:
enum {
PLAYER,
ENEMY,
ALLY
};
class CEntityManager {
public:
void create_entity(int entityType); // PLAYER, ENEMY, ALLY etc.
void delete_entity(int entityID);
private:
std::vector<CEntity*> entityVector;
std::vector<CEntity*> entityVectorIter;
};
Run Code Online (Sandbox Code Playgroud)
我的CEntity课程看起来像这样:
class CEntity() {
public:
virtual void draw() = 0;
void set_id(int nextEntityID);
int get_id();
int get_type();
private:
static nextEntityID;
int entityID;
int entityType;
};
Run Code Online (Sandbox Code Playgroud)
在那之后,我会为敌人创建类,并给它一个精灵表,它自己的功能等.
例如:
class CEnemy : public CEntity {
public:
void draw(); // Implement …
Run Code Online (Sandbox Code Playgroud) 我知道有函数编程语言(LISP,Haskell等)和OOP编程(Java,C#,Ruby,Python等等),但有没有围绕实体组件编程的概念?
当存在共享或依赖的组件时,我一直试图围绕 ECS 的工作方式进行思考。我已经阅读了大量关于 ECS 的文章,但似乎无法找到明确的答案。
假设以下场景:
我有一个实体,它有一个 ModelComponent(或 MeshComponent)、一个 PositionComponent 和一个 ParticlesComponent(或 EmitterComponent)。
ModelRenderSystem 需要 ModelComponent 和 PositionComponent。
ParticleRenderSystem 需要 ParticlesComponent 和 PositionComponent。
在 ModelRenderSystem 中,为了缓存效率/局部性,我想遍历所有位于紧凑数组中的 ModelComponents 并渲染它们,但是对于每个模型,我需要提取 PositionComponent。我什至还没有开始考虑如何处理每个模型的纹理、着色器等(这肯定会破坏缓存)。
与 ParticleRenderSystem 类似的问题。我需要 ParticlesComponent 和 PositionComponent,我希望能够以高效/友好的缓存方式运行所有 ParticlesComponent。
我考虑让 ModelComponent 和 ParticlesComponent 每个都有自己的位置,但是每次模型位置改变时它们都需要同步(想象一下对角色的粒子效果)。这会添加另一个需要跟踪和同步组件或值的实体或组件(并且可能会否定任何缓存效率)。
其他人如何处理这些依赖问题?
我找不到一个CPU缓存友好的框架实现,这意味着每个游戏循环周期中系统遍历的数据存储在连续的内存中.
让我们看看,系统遍历满足其条件的特定实体,即实体应包含要由X系统处理的A,B,C组件.这意味着我需要一个包含所有实体和组件的连续内存(不是引用,只要引用不是缓存友好的,并且你将有很多缓存未命中),以便尽可能快地从RAM中获取它们在X系统的处理过程中.但是在处理X系统之后,Y系统开始在满足其条件的一组实体上运行,例如包含A和B的所有实体.这意味着我们处理与X系统相同的一组实体以及其他一些实体.有A和B.这意味着我们有两个连续的记忆,它们有重复的数据.首先,由于已知原因,数据复制非常糟糕.而且,这反过来意味着我们需要一个同步,只要你需要从一个向量中找到一些实体并使用另一个向量中包含的新数据进行更新,这同样不再是CPU缓存.
这只是我的一个想法.对于实体组件系统框架数据模型还有其他更现实的想法,但在每个模型中我都可以发现存在同样的问题:在每个游戏循环周期中,由于不连续的数据,您无法阻止大量缓存未命中.
任何人都可以建议一个实现,文章,这个主题的例子,这可以帮助我理解应该使用什么数据模型来获得缓存友好的设计,因为这是游戏性能中最关键的事情之一.
我正在探索Go和Entity-Component-Systems.我理解ECS是如何工作的,我试图复制似乎是ECS的首要文件,即http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/
为了提高性能,本文档建议使用每种组件类型的静态数组.也就是说,不是组件接口数组(指针数组).Go中的问题是循环导入.
我有一个包ecs,它包含实体,组件和系统类型/接口的定义以及EntityManager.另一个包,ecs/components,包含各种组件.显然,ecs/components包依赖于ecs.但是,要在EntityManager中声明特定组件的数组,ecs将依赖于ecs/components,因此创建循环导入.
有没有办法避免这种情况?我知道通常高级系统不应该依赖于较低级别的系统.我还想指出,使用指针数组可能对我的目的而言足够快,但我对可能的解决方法感兴趣(供将来参考)
谢谢您的帮助!