对于我正在制作的2D游戏(对于Android),我正在使用一个基于组件的系统,其中gameObject可以容纳多个gameComponent对象。 gamecomponents可以是输入组件,渲染组件,子弹发射组件等。当前,GameComponents对拥有它们并可以修改的对象有一个引用,但是GameObject本身只有一个组件列表,并且在对象更新时可以更新的组件只要它们是什么。

有时,一个组件会有一些信息可以知道的信息。例如,为了碰撞检测,游戏对象与碰撞检测子系统与另一个对象碰撞时要通知。碰撞检测子系统需要知道对象的边界框。我直接将X和Y直接存储在对象中(因为它是由几个组件使用的),但是宽度和高度仅对容纳对象的位图的渲染组件已知。我想在获取该信息的游戏对象中拥有一个方法bundingbox或getwidth。或通常,我想将一些信息从组件发送到对象。但是,在我当前的设计中,GameObject不知道列表中具有哪些特定组件。

我可以想到几种解决这个问题的方法:

  1. 我可以让GameObject具有某些重要组件的特定字段,而不是拥有完全通用的组件列表。例如,它可以具有称为RenderingComponent的成员变量;每当我需要获取对象的宽度时,我就会使用 renderingComponent.getWidth(). 。该解决方案仍然允许构成一般的组件列表,但它以不同的方式对待其中的一些,恐怕我最终会有几个非凡的字段,因为需要查询更多的组件。有些对象甚至没有渲染组件。

  2. 将所需的信息作为GameObject的成员,但允许组件进行更新。因此,默认情况下,对象具有0或-1的宽度和高度,但是渲染组件可以将它们设置为更新循环中的正确值。这感觉就像是一个黑客,我最终可能会把很多东西推到GameObject类中,即使并非所有对象都需要它们,也可能会为方便起见。

  3. 使组件实现了一个接口,该接口指示他们可以查询哪种类型的信息。例如,渲染组件将实现Hassize接口,该接口包括Getwidth和Getheight等方法。当GameObject需要宽度时,它会在其组件上循环检查是否实现了Hassize接口(使用 instanceof Java中的关键字,或 is 在C#中)。这似乎是一个更通用的解决方案,一个缺点是搜索组件可能需要一些时间(但是,大多数对象只有3或4个组件)。

这个问题与特定问题无关。它经常出现在我的设计中,我想知道最好的处理方法是什么。性能很重要,因为这是一个游戏,但是每个对象的组件数量通常很少(最大值为8)。

简短版本

在基于组件的游戏系统中,在保持设计通用的同时,将信息从组件传递到对象的最佳方法是什么?

有帮助吗?

解决方案

我们在Gamedev.net上每周三到四次(GameObject通常称为“实体”),我们在这个问题上有所不同,到目前为止,最佳方法尚无共识。已经证明了几种不同的方法是可行的,因此我不必担心太多。

但是,通常问题在组件之间进行通信。人们很少担心将信息从组件获得到实体 - 如果一个实体知道其需要什么信息,那么大概它准确地知道它需要访问哪种类型的组件以及需要哪种属性或方法来调用该组件才能获得数据。如果您需要反应性而不是活动性,请注册回调或使用组件设置观察者模式,以让实体知道组件中的某些东西何时更改,并在此时读取值。

完全通用的组件在很大程度上是没有用的:它们需要提供某种已知界面,否则几乎没有任何意义。否则,您也可能只有大量的无型价值数组,然后就可以完成。在Java,Python,C#和其他稍微高的语言中,您可以使用反射为您提供一种更通用的方法来使用特定的子类别,而无需对组件本身编码类型和接口信息。

至于交流:

有些人假设实体将始终包含一组已知的组件类型(每个实例是几个可能的子类之一),因此只能直接引用对另一个组件的直接引用,并通过其公共接口读取/写入/写入。

有些人正在使用发布/订阅,信号/插槽等来在组件之间创建任意连接。这似乎更加灵活,但最终您仍然需要了解这些隐性依赖性的知识。 (如果在编译时已知,为什么不使用先前的方法呢?)

或者,您可以将所有共享数据放在实体本身中,并将其用作共享通信区域(与 黑板系统 在AI中)每个组件都可以读写。这通常需要一些鲁棒性,面对某些属性,当您期望它们时没有存在。尽管我怀疑这对一个小的嵌入式系统是一个非常关注的问题,但这也不适合并行性...?

最后,有些人有根本不存在该实体的系统。该组件生存在其子系统内,实体的唯一概念是某些组件中的ID值 - 如果渲染组件(渲染系统中),并且播放器组件(在播放器系统中)具有相同的ID,则可以假设前者处理后者的绘画。但是,没有任何单个对象可以汇总这些组件中的任何一个。

其他提示

就像其他人所说的那样,这里并不总是正确的答案。不同的游戏将向不同的解决方案提供帮助。如果您正在建立一个具有许多不同类型的实体的大型复杂游戏,那么组件之间具有某种抽象的消息传递的更脱钩的通用体系结构可能值得您获得可维护性。对于具有类似实体的更简单的游戏,将所有状态都推入GameObject可能是最有意义的。

对于您需要在某个地方存储边界框的特定情况,并且仅关心碰撞组件,我会:

  1. 将其存储在碰撞组件本身中。
  2. 使碰撞检测代码直接与组件一起使用。

因此,与其通过一系列GameObject进行碰撞引擎迭代以解决互动,而要通过一系列CollisionComponents直接进行迭代。一旦发生碰撞,就可以将其推到其父gameObject。

这给您带来了一些好处:

  1. 将特定于碰撞的状态放在游戏对象中。
  2. 节省您对没有碰撞组件的游戏对象进行迭代。 (如果您有很多非相互作用的对象,例如视觉效果和装饰,则可以节省很多周期。)
  3. 助长您燃烧在物体及其组件之间行走的周期。如果您迭代对象,请执行 getCollisionComponent() 在每个方面,指针键合可能会导致缓存失误。对于每个对象,每个帧都可以燃烧很多CPU。

如果您有兴趣,我对此模式有更多 这里, ,尽管看起来您已经了解了本章中的大部分内容。

使用活动巴士”(请注意,您可能无法按原样使用代码,但应该为您提供基本想法)。

基本上,创建一个中央资源,每个对象都可以注册为侦听器,并说“如果X发生,我想知道”。当游戏中发生某些事情时,负责的对象可以简单地将事件X发送到事件总线,所有有趣的各方都会注意到。

编辑]有关更详细的讨论,请参阅 消息传递 (谢谢 snk_kid 指出这一点)。

一种方法是初始化组件容器。每个组件可以提供服务,还可能需要其他组件的服务。根据您的编程语言和环境,您必须提出一种提供此信息的方法。

以最简单的形式,您在组件之间具有一对一的连接,但您还需要一对多的连接。例如 CollectionDetector 将有实施组件的列表 IBoundingBox.

在初始化过程中,容器将连接组件之间的连接,在运行时,将没有额外的成本。

这与您接近解决方案3),预计组件之间的连接仅连接一次,并且在游戏循环的每次迭代中未检查一次。

.NET的管理可扩展框架 是解决这个问题的一个很好的解决方案。我意识到您打算在Android上发展,但是您仍然可能会从这个框架中获得一些灵感。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top