实体组件系统中的逻辑

我正在制作一个使用Entity / Component架构的游戏,基本上是将Artemis的框架移植到c ++中,当我尝试制作PlayerControllerComponent时,问题就出现了,我原来的想法是这样的。

class PlayerControllerComponent: Component { public: virtual void update() = 0; }; class FpsPlayerControllerComponent: PlayerControllerComponent { public: void update() { //handle input } }; 

并且有一个更新PlayerControllerComponents的系统,但是我发现artemis框架并不像我认为的那样查看子类。 所以我所有的问题是我应该让框架知道子类,或者我应该添加一个新的组件像用于逻辑的对象。

组件可以有function。 你想要远离的东西是有很多的function。 function应该是简单的事情。 例如,我的库存组件具有将商品添加到库存的function:

 public boolean addItem(ItemAttribute item) { if (items.size() < maxItems) { items.add(item); return true; } return false; } 

它不仅仅是一个设置函数,而且没有任何复杂的东西。 我将把function创建留给个人部署框架。 不要强迫他们更新function,让他们决定要实施什么。

请确保您查看Artemis网站上的示例游戏 。 这些应该可以帮助你看到如何使用组件。

另外,请特别注意看看我给阿蒂米斯的这个答案 。

所以用你的例子。 玩家控制组件根本不需要太多的数据。 也没有function。 然而,只要将它附加到也有位置的实体,就可以使PlayerControlSystem选取该组件并处理传入的键盘input。 有时候你可以把组件看作系统的标志。 该组件实际上只在那里,以便系统将该实体添加到其处理组中。

组件应该是系统可以访问的对象。 当一个系统访问一个组件时,它应该能够访问它的数据以及它所具有的任何简单的function。

每种控制types应该有不同的系统,每种控制types应该有不同的系统。 如果你想把它压缩成一个单一的系统或者一个单一的组件,但是它最终会像Sidar的回答中所描述的那样在一个switch语句中结束。

在我的阿蒂米斯港口,它看起来像这样:

我的组件:

  class PlayerControllerComponent : public artemis::Component{ enum PlayerControl{ TPS,FSP,TD }; public: PlayerControl pc; PlayerControllerComponent(PlayerControl pc){ this->pc = pc; } }; 

系统会是这样的:

 class PlayerControlSystem: public artemis::EntityProcessingSystem { private: artemis::ComponentMapper<PlayerControllerComponent > pcm; public: PlayerControlSystem() { setComponentTypes<PlayerControllerComponent>(); }; virtual void begin() { //before logic } virtual void end() { //after logic } virtual void initialize() { pcm.init(*world); }; virtual void processEntity(artemis::Entity &e) { PlayerControllerComponent & ent = *pcm.get(e); switch(ent.pc) { case TPS: //call internal function for TPS break; case FPS: //call internal function for FPS break; case TD: //call internal function for TD break; }; }; }; 

我不得不承认,我仍然试图完全包围阿耳忒弥斯。 但是,由于组件的bitset是如此特定的系统与组件更多的1:1。 所以这就是我要做的。

您的input组件不需要任何逻辑。 它需要一个控制计划,因为实体之间会有所不同,但仅此而已。 其余的应该由系统来处理。 它和渲染和物理是一样的,只是检测哪个实体具有该组件并对其执行操作。 将input处理逻辑放在组件中是没有用的,因为在开始创建黑客之前,只能做到这一点。

试图以创建inheritance链为代价来避免一个switch语句是一个糟糕的决定,因为你一无所获。 您需要相同的逻辑来处理input。 switch语句是为这样的情况创建的,它不会使你的应用程序不那么健壮。 用switch语句添加新types的控件会更容易; 只需添加一个案例和一个函数来处理它。 否则,你会更多的依靠运行时types检查来膨胀你的inheritance链,再次获得任何回报。

我的建议是使用抽象的纯虚拟类作为接口或模板策略。 您的系统只能接受从指定接口inheritance或实现模板策略的组件。 通过这种方式,您的系统将以这种方式意识到您的组件具有更新function的事实。

你可以做这样的事情:

 class Test { std::vector<Interface *> items; public: void AddComponent(T& param) { items.push_back(&param); }; void Update() { for (unsigned i = 0; i < items.size(); i++) items[i]->Update(); } }; 

要么

 template <class T> class Test { std::vector<T *> items; public: void AddComponent(T& param) { items.push_back(&param); }; void Update() { for (unsigned i = 0; i < items.size(); i++) items[i]->Update(); } }; 

第二个不需要使用接口,但是如果T没有实现公共的Update函数,编译器将不会编译代码。

我将创建FpsPlayerControllerComponent作为组件的子类,并创建一个处理包含此组件的实体的系统。

如果你发现你需要另一个PlayerController,比如说TpsPlayerController,那么只需要创建一个新的Component子类和一个新的系统,这样你的代码就不需要开关ifs了

系统通常比这更通用,但我认为控制器系统可以这样实现,因为你可能不需要同时使用FpsPlayerController和TpsPlayerController。

这些系统中的逻辑? 几乎没有。 :)在和其他GD.SE用户聊了一会儿这个问题之后,我们得出了一个结论:我们中的许多人并不完全相信这些系统是每个人都是专家。

编程中组件的原始概念是沿着这些线的某个地方:

 class MyRendererComponent { function MyFunction(){..}; data MyData; }; class MyOtherComponent { function MyOtherFunction(){..}; data MyOtherData; } class MyEntity { MyRendererComponent C; MyOtherComponent C2; ... }; 

通过这种方法,组件的行为可以是实体希望它们performance的任何方式。 把逻辑放到这里就像把它放到实体中一样。 在这种方法中可能会出现一些冗余的粘合代码,但所有事情都考虑到了,这实际上certificate了比其他任何系统都需要更less的工作。

所有其他的方法主要是为了大量数据驱动引擎(使游戏编辑中的实体组合),这是大多数人实际上不需要的(大多数甚至没有编辑者)。 甚至GTA3也不需要它。 如果我说你在开发中没有更大的游戏,我很可能是对的。