“游戏对象”-和基于组件的设计


25

在过去的3-4年中,我一直在从事一些业余爱好项目。只是简单的2d和3d游戏。但是最近我开始了一个更大的项目。在过去的几个月中,Soo一直在尝试设计一个游戏对象类,该类可以作为我所有游戏对象的基础。因此,在进行了多次尝试和模子测试之后,我转向了Google,该公司迅速为我指出了一些GDC PDF和PowerPoint。现在,我试图掌握基于组件的游戏对象。

我知道引擎会创建一个游戏对象,然后附加不同的组件来处理诸如健康,物理,网络以及您所做的任何事情。但是我不明白的是,组件X如何知道Y是否更改了对象的状态。就像PhysicsComponent如何知道玩家是否还活着一样,因为健康状况由HealthComponent控制。HealthComponent如何播放“玩家死亡动画”?

我的印象是(在HealthComponent中)是这样的:

if(Health < 0) {
   AnimationComponent.PlayAnimation("played-died-animation")
}

但是话又说回来,HealthComponent如何知道附加的游戏对象是否附加了AnimationComponent?我在这里看到的唯一解决方案是

  1. 检查是否连接了AnimationComponent(在组件代码内部或在引擎侧)

  2. 有组件需要其他组件,但这似乎与整个组件设计相抗衡。

  3. 像HealthWithAnimationComponent,HealthNoAnimationComponent这样写,如此反复,似乎又与整个组件设计思想相抵触。


1
喜欢这个问题。我应该在同一个月前问过,但是从来没有解决过。我面临的另一个问题是游戏对象具有相同组件的多个实例(例如,多个动画)。如果答案能够解决这一问题,那就太好了。我最终将消息用于通知,并在Game对象的所有组件之间共享了变量(因此,它们无需发送消息即可获取变量的值)。
亚行

1
根据游戏类型的不同,您可能不会拥有带有健康组件而没有动画组件的游戏对象。所有这些游戏对象可能都是单位之类的代表。因此,您可以丢弃运行状况组件,并创建具有现场运行状况的UnitComponent,并知道该单元需要的所有组件。组件的这种粒度实际上并没有任何帮助-在每个域(渲染,音频,物理,游戏逻辑)中拥有一个组件更为现实。
Kikaimaru

Answers:


11

在所有示例中,都存在一个严重的问题。健康组件需要了解可能需要响应实体死亡的每种组件类型。因此,您的情况都不适合。您的实体具有健康组件。它具有动画组件。两者都不依赖或不了解对方。他们通过消息传递系统进行通信。

当运行状况组件检测到该实体“死亡”时,它将发送“我已死亡”消息。动画组件负责通过播放适当的动画来响应此消息。

运行状况组件不会将消息直接发送到动画组件。也许将其广播到该实体中的每个组件,甚至是整个系统。也许动画组件需要让消息传递系统知道它对“我死了”消息感兴趣。有许多方法可以实现消息传递系统。无论您实现它是什么,关键是运行状况组件和动画组件永远不需要知道或关心它们是否存在,并且添加新组件将永远不需要修改现有组件来向它们发送适当的消息。


Okey,这很有道理。但是,谁会声明“状态”,如“死”或“门户已损坏”等?组件或引擎呢?因为在永远不会附加health组件的事物上添加状态“死角”似乎对我来说是浪费。我想我将深入研究并开始测试一些代码,看看有什么用。
hayer 2012年

迈克尔和帕特里克·休斯在上面有正确的答案。组件仅仅是数据;因此,不是健康组件真正检测到实体何时死亡并发送消息,而是某些特定于游戏的逻辑。如何抽象取决于您。实际死亡状态永远不需要存储在任何地方。如果对象的运行状况<0,则该对象已死亡,运行状况组件可以封装该位数据检查逻辑而不会破坏“无行为!”。如果仅考虑将修改组件状态的行为视为行为,则限制。
Blecki 2012年

只是好奇,您将如何处理MovementComponent?当检测到输入时,需要增加PositionComponent中的速度。该消息是什么样的?
Tips48 2014年

8

该方式的Artemis解决这个问题是不是做中零件的加工。组件仅包含所需的数据。系统读取多种组件类型,并进行必要的处理。

因此,就您而言,您可能有一个RenderSystem,它可以读取HealthComponent(和其他组件)并按适当的动画播放队列。通过这种方式将数据与功能分开,可以更轻松地保持对依赖关系的正确管理。


最终这是解决问题的一种好方法:组件代表属性,而系统将完全不同的属性结合在一起,并使用它们来完成工作。这是对传统OOP思维的巨大转变,使某些人的头部受伤=)
Patrick Hughes

Okey,现在我真的迷失了。“相反,在ES中,如果战场上有100个单位,每个单位由一个Entity表示,那么每个单位可以调用的方法的副本为零-因为实体不包含方法,组件也不包含方法,而是每个方面都有一个外部系统,并且该外部系统包含可以在任何拥有将其标记为与此组件兼容的Component的实体上调用的所有方法。系统。” 那么,GunComponent中的数据存储在哪里?如回合等。如果所有实体共享同一组件。
hayer 2012年

1
据我了解,所有实体都不共享同一组件,每个实体可以具有N个组件实例。然后,系统在游戏中查询所有包含其关心的组件实例的实体的列表,然后对该实体进行任何处理
Jake Woods

这只是解决问题。系统如何知道要使用哪些组件?一个系统也可能需要其他系统(例如StateMachine系统可能要调用动画)。但是,它确实解决了WHO拥有数据的问题。实际上,一个更简单的实现是在游戏对象中有一个字典,每个系统都在其中创建他的变量。
亚行

确实可以解决问题,但可以解决的地方更持久。系统具有其相关组件的硬连线。系统可以通过组件彼此通信(StateMachine可以设置Animation读入的组件值来知道该怎么做(否则它可能会触发一个事件)。字典的方法听起来像是Properties Pattern也可以工作。组件是相关的属性组合在一起,他们可以静态地检查没有离奇失误,因为你在一个地方添加了“Dammage”,而是试图找回它在另一个使用“损害”。
迈克尔

6

在您的代码中,您可以通过多种方式(我曾经使用过它们,可能还存在其他方式)来知道对象是否更改了状态:

  1. 发信息。
  2. 直接从组件读取数据。

1)检查是否连接了AnimationComponent(在组件代码内部或在引擎侧)

为此,我使用了:1. GameObject的HasComponent函数,或者2.附加组件时,可以检查某些构造函数中的依赖项,或者3.如果我确定对象具有此组件,我就使用它。

2)让组件需要其他组件,但这似乎与整个组件设计相抗衡。

在我读过的一些文章中,理想系统中的组件并不相互依赖,但在现实生活中并非如此。

3)像HealthWithAnimationComponent,HealthNoAnimationComponent这样写,再等等,这似乎又与整个组件设计思想相抵触。

编写此类组件是一个坏主意。在我的应用中,我创建了最独立的“健康”组件。现在,我正在考虑一些观察者模式,该模式可以通知订阅者某些特定事件(例如“命中”,“恢复”等)。因此,AnimationComponent必须自己决定何时播放动画。

但是,当我阅读有关CBES的文章时,它给我留下了深刻的印象,所以当我使用CBES并发现它的新可能性时,我现在感到非常高兴。


1
好吧,google.no /… @幻灯片16
hayer

@bobenko,请提供有关CBES的文章的链接。我对此也很感兴趣;)
Edward83,2012年

1
并且lambdor.net/?p=171 @底部,这是我的问题的摘要。如何根据相对复杂的非基本组件定义不同的功能?最基本的成分是什么?基本组件在哪些方面不同于纯函数?现有组件如何自动与新组件中的新消息进行通信?忽略组件不知道的消息有什么意义?输入过程输出模型到底发生了什么?
hayer 2012年

1
在CBES stackoverflow.com/a/3495647/903195上,这是一个很好的答案,我研究的大多数文章都来自该答案。我从cowboyprogramming.com/2007/01/05/evolve-your-heirachy开始并受到启发,然后在《 Gems 5》(我记得)中有一篇很好的例子。
Yevhen 2012年

但是关于功能响应式编程的另一个概念呢?对我来说,这个问题仍是未解决的,但可能对您来说是一个很好的研究方向。
Yevhen 2012年

3

就像迈克尔,帕特里克·休斯和布莱奇说的那样。避免简单地解决问题的方法是首先放弃导致问题的意识形态。

它的OOD更少,更像函数式编程。当我开始尝试基于组件的设计时,我发现了这个问题。我在Google上搜索了更多,发现“功能性反应式编程”是解决方案。

现在,我的组件不过是描述其当前状态的变量和字段的集合。然后,我有一堆“系统”类,用于更新与它们相关的所有组件。通过以明确的顺序运行系统来实现无功部分。这确保了下一个要进行处理和更新的系统,以及要读取和更新的任何组件和实体,始终能够处理最新数据。

但是,尽管如此,您仍然可以说问题再次发生了。因为如果不是很简单,您的系统需要运行哪个命令呢?如果存在周期性关系,而只是盯着一堆if-else和switch语句只是时间问题,该怎么办?它是消息的隐式形式,不是吗?乍一看,我认为这是一个很小的风险。通常,事物是按顺序处理的。类似于:玩家输入->实体位置->碰撞检测->游戏逻辑->渲染->重新开始。在这种情况下,每个系统都有一个系统,为每个系统提供一个update()方法,然后在游戏循环中依次运行它们。

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.