为了我的即将开始的个人游戏项目,我使用基于组件的实体系统。
你可以通过搜索“基于组件的游戏开发”来了解更多信息。一篇著名的文章是Cowboy编程博客上的《演化你的层次结构》。
在我的系统中,实体只是无符号长整型的ID,有点像关系数据库中的ID。所有与实体相关的数据和逻辑都写入组件中。我有一些系统将实体ID与其相应的组件链接起来。就像这样:
typedef unsigned long EntityId;
class Component {
Component(EntityId id) : owner(id) {}
EntityId owner;
};
template <typename C> class System {
std::map<EntityId, C * > components;
};
对于每种功能,我都会编写一个特殊的组件。不同实体的组件并不相同。
例如,您可以拥有一个静态的岩石对象,它具有WorldPositionComponent和ShapeComponent,以及一个移动的敌人,它除了这些组件之外还有VelocityComponent。
以下是一个示例:
class WorldPositionComponent : public Component {
float x, y, z;
WorldPositionComponent(EntityId id) : Component(id) {}
};
class RenderComponent : public Component {
WorldPositionComponent * position;
3DModel * model;
RenderComponent(EntityId id, System<WorldPositionComponent> & wpSys)
: Component(id), position(wpSys.components[owner]) {}
void render() {
model->draw(position);
}
};
class Game {
System<WorldPositionComponent> wpSys;
System<RenderComponent> rSys;
void init() {
EntityId visibleObject = 1;
wpSys.components[visibleObject] = new WorldPositionComponent(visibleObject);
rSys.components[visibleObject] = new RenderComponent(visibleObject, wpSys);
EntityId invisibleObject = 2;
wpSys.components[invisibleObject] = new WorldPositionComponent(invisibleObject);
}
void gameLoop() {
std::map<EntityId, RenderComponent *>::iterator it;
for (it = rSys.components.iterator(); it != rSys.components.end(); ++it) {
(*it).second->render();
}
}
};
这里有两个组件,WorldPosition和Render。Game类持有这两个系统。Render组件可以访问对象的位置。如果实体没有WorldPosition组件,则可以选择默认值或忽略该实体。
Game::gameLoop()方法仅呈现visibleObject。不会浪费处理非渲染组件的资源。
您还可以将我的Game类分为两个或三个,以将显示和输入系统与逻辑分离。类似于Model,View和Controller。
我认为使用组件来定义游戏逻辑,并且只为需要的功能创建实体非常整洁 - 不再存在空的render()或无用的碰撞检测检查。