我在此组件体系结构方面走的正确吗?


9

我最近决定修改游戏架构,以摆脱深层的类层次结构,并用可配置的组件替换它们。我要替换的第一个层次结构是Item层次结构,我想了解一些有关我是否处在正确轨道上的建议。

以前,我的层次结构如下所示:

Item -> Equipment -> Weapon
                  -> Armor
                  -> Accessory
     -> SyntehsisItem
     -> BattleUseItem -> HealingItem
                      -> ThrowingItem -> ThrowsAsAttackItem

不用说它开始变得凌乱,而对于需要多种类型的物品来说,这并不是简单的解决方案(例如,某些设备用于物品合成,某些设备可抛弃,等等)。

然后,我尝试将功能重构并将其放置到基础项目类中。但是后来我注意到该物料具有大量未使用/多余的数据。现在,我尝试为我的其他游戏类创建至少一个类似于建筑的组件,至少要为我的物品做一个。

这是我目前正在考虑的组件设置:

我有一个基础项目类,该类具有用于各种组件的插槽(例如,设备组件插槽,修复组件插槽等,以及任意组件的映射),因此如下所示:

class Item
{
    //Basic item properties (name, ID, etc.) excluded
    EquipmentComponent* equipmentComponent;
    HealingComponent* healingComponent;
    SynthesisComponent* synthesisComponent;
    ThrowComponent* throwComponent;
    boost::unordered_map<std::string, std::pair<bool, ItemComponent*> > AdditionalComponents;
} 

所有项目组件都将从基本ItemComponent类继承,并且每种Component类型都负责告诉引擎如何实现该功能。也就是说,HealingComponent告诉战斗机械师如何将物品当作治疗物品使用,而ThrowComponent告诉战斗引擎如何将物品视为可扔物品。

该映射用于存储不是核心项目组件的任意组件。我将其与布尔值配对以指示项目容器应管理ItemComponent还是由外部源进行管理。

我的想法是,我先定义游戏引擎使用的核心组件,而我的商品工厂将分配商品实际拥有的组件,否则它们为null。该映射将包含通常由脚本文件添加/使用的任意组件。

我的问题是,这是一个好的设计吗?如果没有,该如何改善?我考虑过将所有组件分组到地图中,但是对于核心项组件而言,似乎不需要使用字符串索引

Answers:


8

这似乎是非常合理的第一步。

您选择了通用性(“附加组件”映射)和查找性能(硬编码成员)的组合,这可能是一些预先优化的问题-您关于基于字符串的一般效率低下的观点查找是经过精心设计的,但是您可以通过选择以更快的哈希值索引组件的方式来缓解这种情况。一种方法可能是为每个组件类型赋予唯一的类型ID(本质上,您正在实现轻量级的自定义RTTI)并基于该ID进行索引。

无论如何,我都会提醒您公开Item对象的公共API,该API允许您以统一的方式要求任何组件(硬编码组件和附加组件)。这将使更改硬编码/非硬编码组件的基础表示或平衡变得更加容易,而不必重构所有项目组件客户端。

您可能还考虑为每个硬编码组件提供“虚拟”无操作版本,并确保始终分配它们—然后,您可以使用引用成员而不是指针,并且您将不需要检查NULL指针与硬编码的组件类之一进行交互之前。您仍然会承担与该组件的成员进行交互的动态调度的开销,但是即使使用指针成员也会发生这种情况。这更多的是代码清洁性问题,因为对性能的影响在任何情况下都可以忽略不计。

我不认为拥有两种不同的生命周期作用域不是一个好主意(换句话说,我认为附加组件图中的布尔值不是一个好主意)。它使系统复杂化,并意味着销毁和资源释放不会具有确定性。如果您选择一种寿命管理策略或另一种寿命管理策略,则组件的API将会更加清晰-要么由实体来管理组件寿命,要么由实现组件的子系统来实现(我更喜欢后者,因为它与舷外组件更好地配对方法,我将在下面讨论)。

我对您的方法的最大缺点是,您将所有组件都聚集在“实体”对象中,实际上这并不总是最好的设计。从我的相关答案到另一个基于组件的问题:

在游戏对象中使用大型组件图和update()调用的方法是次优的(对于那些首先构建此类系统的人来说,这是一个常见的陷阱)。它使更新期间的高速缓存一致性非常差,并且不允许您利用并发性和趋势,即一次大批量数据或行为趋于SIMD风格的过程。通常最好使用一种设计,其中游戏对象不更新其组件,而是负责组件本身的子系统一次更新所有组件。

本质上,您通过将组件存储在item实体中来采取相同的方法(再次,这是完全可以接受的第一步)。您可能会发现,您对性能所关注的组件的大部分访问权限只是为了更新它们,如果您选择对组件组织使用更外部的方法,则组件将保持在缓存一致性中。通过最了解其需求的子系统构建高效(针对其域)的数据结构,您可以获得更好,更可并行化的更新性能。

但是我仅指出这是要考虑的未来方向,您当然不希望过度设计这个东西。您可以通过不断的重构进行逐步过渡,或者您可能会发现当前的实现完全满足您的需求,而无需对其进行迭代。


1
+1表示建议摆脱Item对象。尽管将需要更多的前期工作,但最终将产生一个更好的组件系统。
詹姆斯

我还有其他问题,不确定是否应该启动新的topiuc,因此请首先在这里尝试:对于我的item类,没有任何方法可以调用Evert Frame(甚至关闭)。对于我的图形子系统,我将听取您的建议,并保留所有对象在系统下进行更新。我的另一个问题是如何处理组件检查?例如,我想查看是否可以将某项用作X,所以自然地,我会检查该项是否具有执行X所需的组件。这是正确的方法吗?再次感谢您的回答
user127817 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.