游戏组件,游戏管理器和对象属性


15

我正在努力使基于组件的实体设计成为现实。

我的第一步是创建可以添加到对象中的各种组件。对于每种组件类型,我都有一个管理器,该管理器将调用每个组件的更新功能,并根据需要传入诸如键盘状态之类的信息。

我要做的下一件事是删除对象,并让每个组件都带有一个ID。因此,对象是由具有相同ID的组件定义的。

现在,我在想我不需要所有组件的管理器,例如,我有一个SizeComponent,它只是一个Size属性)。结果,SizeComponent它没有更新方法,并且管理器的更新方法不执行任何操作。

我的第一个想法是要有一个ObjectProperty组件可以查询的类,而不是将它们作为组件的属性。因此,一个对象有许多ObjectPropertyObjectComponent。组件将具有查询对象属性的更新逻辑。管理者将管理调用组件的更新方法。

在我看来,这似乎是工程过度,但我认为我无法摆脱这些组件,因为我需要让管理人员知道哪些对象需要哪种组件逻辑才能运行(否则,我只会删除该组件)并将其更新逻辑推入管理器)。

  1. 难道这(有ObjectPropertyObjectComponentComponentManager班)在工程?
  2. 有什么好的选择?

1
您可以通过尝试学习组件模型来获得正确的想法,但是您需要更好地了解它需要做什么-唯一的方法是[主要]在不使用游戏的情况下完成游戏。我认为制作一个对象SizeComponent是过大的-您可以假设大多数对象都具有一定的大小-使用渲染,AI和物理等原理来使用组件模型;Size始终具有相同的行为-因此您可以共享该代码。
乔纳森·迪金森


@ JonathanDickinson,@ Den:我想,那我的问题是我应该在哪里存储公共属性。例如,一个对象作为位置,由a RenderingComponent和a使用PhysicsComponent。我是否在考虑将财产放在何处的决定?我应该只将其插入其中一个,然后让另一个查询具有所需属性的组件的对象吗?
乔治·达基特

我之前的评论以及其背后的思考过程是促使我为组件可以查询的属性(或一组相关属性)设置单独的类的原因。
乔治·达基特

1
我真的很喜欢这个主意-可能值得尝试;但是拥有一个描述每个属性的对象确实很昂贵。您可以尝试PhysicalStateInstance(每个对象一个)和GravityPhysicsShared(每个游戏一个);但是我很想说这正在进入建筑师兴高采烈的境界,不要让自己陷入困境(正是我对第一个组件系统所做的事情)。吻。
乔纳森·迪金森

Answers:


6

您第一个问题的简单答案是:是,您正在过度设计设计。“我能分解多远?” 当执行下一步并删除中心对象(通常称为Entity)时,这个问题非常普遍。

当您将对象分解到如此细致的程度以至于无法自行确定大小时,设计就显得过分了。数据值本身不是组成部分。它是一种内置的数据类型,通常可以准确地称为您开始调用它们的属性。属性不是组件,但是组件确实包含属性。

因此,在组件系统中进行开发时,我尝试并遵循以下几条准则:

  • 没有勺子。
    • 这是您摆脱中心对象已经采取的步骤。这消除了关于什么东西进入Entity对象以及什么东西进入组件的整个争论,因为现在您所拥有的就是这些组件。
  • 组件不是结构
    • 如果将某些内容分解为仅包含数据的位置,则它不再是组件,而只是数据结构。
    • 组件应包含以特定方式完成特定任务所需的所有功能。
    • IRenderable界面提供了通用解决方案,以可视方式显示游戏中的任何内容。CRenderableSprite和CRenderableModel是该接口的组件实现,提供分别在2D和3D中渲染的细节。
    • IUseable是玩家可以进行交互的界面。CUseableItem可以是发射主动枪或喝选定药水的组件,而CUseableTrigger可以是玩家跳进炮塔或投掷操纵杆以降低吊桥的地方。

因此,由于组件的指导原则不是结构化,因此SizeComponent被细分得太多了。它仅包含数据,定义大小的内容可能有所不同。例如,在渲染组件中,它可以是1d标量或2 / 3d向量。在物理组件中,它可能是对象的边界体积。在库存项目中,可能是2D网格占用了多少空间。

尝试在理论和实践之间划清界限。

希望这可以帮助。


别忘了在某些平台上,从接口调用函数比从父类调用函数要长(因为您的回答中包括对接口和类的提及)
ADB

要记住的好点,但是我试图保持语言不可知性,仅在一般设计术语中使用它们。
詹姆斯

“如果将某物分解为仅包含数据的位置,那么它就不再是组件,而仅仅是数据结构。” -为什么呢?“组件”是一个通用词,它也可以表示数据结构。
保罗·曼塔

@PaulManta是的,这是一个通用术语,但此问题和答案的重点是确定界限。正如你所引用的,我的回答只是我建议凭经验来做到这一点。一如既往,我主张永远不要让理论或设计考量成为推动发展的因素,而这正是促进发展的意思。
詹姆斯

1
@James这是一个有趣的讨论。:) chat.stackexchange.com/rooms/2175如果您的实现是组件对其他组件感兴趣的太多了,那么我最大的烦恼。我希望以后继续进行讨论。
保罗·曼塔2012年

14

您已经接受了答案,但是这是我在CBS遇到的问题。我发现泛型Component类有一些局限性,因此我采用了Radical Entertainment在GDC 2009上描述的设计,该设计建议将组件分成AttributesBehaviors。(“ 游戏对象组件体系结构的理论和实践 ”,Marcin Chady)

我在两页的文档中解释了我的设计决策。我将发布链接,因为将其粘贴到这里太长了。它目前仅涵盖逻辑组件(还不包括渲染和物理组件),但是它应该使您了解我尝试做的事情:

►http ://www.pdf-archive.com/2012/01/08/entity-component-system/preview/page/1

这是该文档的摘录:

简而言之,属性和行为

Attributes管理一类数据,并且它们具有的任何逻辑范围都受到限制。例如,Health可以确保其当前值永远不会大于其最大值,并且当当前值降至某个临界水平以下时,它甚至可以通知其他组件,但是它不包含任何更复杂的逻辑。Attributes不依赖其他Attributes或其他Behaviors

Behaviors控制实体如何对游戏事件做出反应,做出决定并Attributes根据需要更改的值。Behaviors是依赖于一些Attributes,但他们不能直接互相作用-他们才反应过来怎么Attributes’值由其他改变Behaviors和他们发送的事件。


编辑:这是一个关系图,显示了组件之间如何通信:

属性与行为之间的通信图

实现细节:实体级别 EventManager仅在使用创建它。该Entity班只是存储的指针EventManager被初始化只有一些组件请求它。


编辑:在另一个问题上,我对此给出了类似的答案。你可以在这里找到它了,或许,该系统的更好的解释:
/gamedev//a/23759/6188


2

这实际上取决于所需的属性以及所需的位置。您将拥有的内存量以及将使用的处理能力/类型。我已经看到并尝试执行以下操作:

  • 多个组件使用但仅被一个组件修改的属性存储在该组件中。形状是AI系统,物理系统以及渲染系统都需要访问基本形状的游戏中的一个很好的例子,它是沉重的属性,如果可能的话,它应该只保留在一个地方。
  • 位置等属性有时需要复制。例如,如果您并行运行多个系统,则希望避免跨系统偷看,而宁愿同步位置(从主组件复制或通过增量同步,或者根据需要通过碰撞传递)。
  • 源自控件或AI“意图”的属性可以存储在专用系统中,因为它们可以应用于其他系统而不会从外部看到。
  • 简单的属性可能会变得复杂。如果您需要共享大量数据(位置,方向,帧增量,当前总增量运动,当前帧和上一帧的增量运动,旋转...),有时您的位置将需要专用的系统。在这种情况下,您将必须使用系统并从专用组件访问最新数据,并且可能必须通过累加器(增量)进行更改。
  • 有时,您的属性可以存储在原始数组(double *)中,并且组件将仅具有指向包含不同属性的数组的指针。最明显的例子是当您需要大量并行计算(CUDA,OpenCL)时。因此,拥有一个可以正确管理指针的系统可能很少。

这些原则有其局限性。当然,您将不得不将几何图形推送到渲染器,但是您可能不想从那里检索它。在其中发生变形的情况下,主几何体将存储在物理引擎中,并与渲染器同步(不时取决于对象的距离)。因此,无论如何您都将复制它。

没有完美的系统。某些游戏使用更简单的系统会更好,而其他游戏则需要跨系统进行更复杂的同步。

首先,请确保可以从组件中以简单的方式访问所有属性,以便一旦开始对系统进行微调,就可以透明地更改属性的存储方式。

复制某些属性不会感到羞耻。如果一些组件需要保存本地副本,则复制和同步有时比访问“外部”值更有效。

另外,同步不必一定在每一帧都发生。某些组件的同步频率可能低于其他组件。渲染组件通常是一个很好的例子。那些不与播放器进行交互的对象可以像不远的对象那样较少地同步。摄像机场外的摄像机可以更不频繁地同步。


关于尺寸组件,它可能会捆绑在您的位置组件中:

  • 并非所有具有大小的实体都具有物理成分,例如面积,因此将其与物理绑定并不是您的最大利益。
  • 没有职位,大小可能无关紧要
  • 所有具有位置的对象都可能具有大小(可用于脚本,物理,AI,渲染...)。
  • 大小可能不会在每个周期更新,但位置可能会更新。

除了大小,您甚至可以使用大小修饰符,它可能更方便。

至于将所有属性存储在通用属性存储系统中……我不确定您的使用方向是否正确……专注于游戏核心的属性,并创建将尽可能多的相关属性捆绑在一起的组件。只要您正确地抽象了对这些属性的访问(例如,通过需要它们的组件中的getter),您就应该能够在以后移动,复制和同步它们,而不会破坏太多的逻辑。


2
BTW +1或-1 me,因为自11月9日以来我的当前代表是666 ...令人毛骨悚然。
郊狼

1

如果可以将组件任意添加到实体,那么您需要一种查询实体中是否存在给定组件并获取对它的引用的方法。因此,您可以遍历从ObjectComponent派生的对象列表,直到找到所需的对象,然后将其返回。但是您将返回正确类型的对象。

在C ++或C#中,这通常意味着您将在诸如 T GetComponent<T>()。一旦有了该引用,就可以确切知道其拥有的成员数据,因此直接访问它即可。

在诸如Lua或Python之类的东西中,您不一定具有该对象的显式类型,并且可能也不在乎。但是同样,您可以访问成员变量并处理尝试访问不存在的任何异常。

明确地查询对象属性听起来像是在重复编译该语言可以为您完成的工作,无论是在编译时针对静态类型的语言,还是在运行时针对动态类型的语言。


我了解从实体(使用泛型等)获取强类型组件的问题,我的问题更多是关于这些属性应该去哪里,尤其是在多个组件使用一个属性并且不能说拥有单个组件的情况下。请参阅我对这个问题的第三和第四条评论。
乔治·达基特

只需选择一个合适的现有组件即可,如果不适合则将该属性分解为一个新组件。例如,Unity有一个“变换”组件,它只是位置,如果需要改变对象的位置,则可以通过该组件来完成。
Kylotan

1

“我认为,那我的问题是我应该在哪里存储公共属性。例如,一个对象用作位置,供RenderingComponent和PhysicsComponent使用。我是否在考虑将属性放置在哪里的决定?我应该坚持吗?两者之一,然后让另一个查询一个具有所需属性的组件的对象?”

事实是RenderingComponent 使用位置,但是PhysicsComponent 提供了位置。您只需要一种方法来告诉每个用户组件要使用哪个提供程序。理想情况下,以不可知论的方式,否则将存在依赖性。

“ ...我的问题更多是关于这些属性应该去哪里,特别是在多个组件使用一个属性并且不能说拥有单个组件的地方。请参阅我对该问题的第三和第四条评论。”

没有共同的规则。取决于特定的属性(请参见上文)。

使用难看但基于组件的架构制作游戏,然后对其进行重构。


我认为我不太明白该怎么PhysicsComponent办。我将其视为在物理环境中管理对象的模拟,这使我感到困惑:并非所有需要渲染的事物都需要进行模拟,因此添加PhysicsComponent时似乎不对,RenderingComponent因为它包含一个位置该RenderingComponent用途。我可以很容易地看到自己最终是由相互连接的组件构成的网络,这意味着所有/大部分都需要添加到每个实体中。
乔治·达基特

我实际上也有类似情况:)。我有一个PhysicsBodyComponent和一个SimpleSpatialComponent。它们都提供位置,角度和大小。但是第一个参与物理模拟并具有其他相关属性,第二个仅保存该空间数据。如果您拥有自己的phys引擎,您甚至可以从后者继承前者。

“我很容易看到自己最终是由相互连接的组件组成的网络,这意味着所有/大部分都需要添加到每个实体中。” 那是因为您没有实际的游戏原型。我们在这里讨论一些基本组件。难怪它们会在任何地方使用。

1

你的直觉是告诉你,具有ThingPropertyThingComponentThingManager每一个Thing类型的组件一个小矫枉过正。我认为是对的。

但是,您需要某种方式来跟踪相关组件的相关信息,例如哪些系统使用它们,它们属于什么实体等等。

TransformProperty会是一个很普通的人。但是谁来负责渲染系统呢?物理系统?音响系统?为什么Transform组件甚至需要更新自身?

解决方案是从属性中删除除getter,setter和initializers外的任何类型的代码。组件是数据,游戏中的系统使用它来执行各种任务,例如渲染,AI,声音回放,移动等。

阅读有关Artemis的信息:http : //piemaster.net/2011/07/entity-component-artemis/

查看其代码,您将看到它基于将其依赖项声明为的系统ComponentTypes。您编写每个System类,并在构造函数/ init方法中声明系统依赖的类型。

在设置级别或其他级别的过程中,您将创建实体并向其中添加组件。之后,您告诉该实体向Artemis报告,然后Artemis根据该实体的构成来确定哪些系统希望了解该实体。

然后,在循环的更新阶段,您System的现在有了要更新的实体的列表。现在你可以拥有的组件的粒度,所以你可以设计疯了系统,构建实体出的ModelComponentTransformComponentFliesLikeSupermanComponent,和SocketInfoComponent,并做一些怪异像化妆飞碟客户端之间的苍蝇连接到多人游戏。好的,也许不是那样,但是其思想是使事情保持解耦和灵活。

Artemis并不完美,该站点上的示例有些基础,但是代码和数据的分离很强大。如果做得对,这对缓存也有好处。Artemis可能在这方面并没有做到这一点,但值得学习。

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.