Fir*_*aad 21 oop entity-system component-based
对于我正在制作的2D游戏(对于Android),我使用的是基于组件的系统,其中GameObject包含多个GameComponent对象.GameComponents可以是输入组件,渲染组件,子弹发射组件等.目前,GameComponents具有对拥有它们的对象的引用并且可以对其进行修改,但GameObject本身只有一个组件列表,并且它不关心组件是什么,只要它们在对象更新时可以更新.
有时组件有一些GameObject需要知道的信息.例如,对于碰撞检测,GameObject将自身注册到碰撞检测子系统,以在其与另一个对象碰撞时被通知.碰撞检测子系统需要知道对象的边界框.我将x和y直接存储在对象中(因为它被几个组件使用),但宽度和高度仅为保存对象位图的渲染组件所知.我想在GameObject中有一个方法getBoundingBox或getWidth来获取该信息.或者一般来说,我想从组件向对象发送一些信息.但是,在我目前的设计中,GameObject不知道它在列表中具有哪些特定组件.
我可以想出几种方法来解决这个问题:
我可以让游戏对象拥有一些重要组件的特定字段,而不是拥有一个完全通用的组件列表.例如,它可以有一个名为renderingComponent的成员变量; 每当我需要获得我刚才使用的对象的宽度时renderingComponent.getWidth()
.这个解决方案仍然允许组件的通用列表,但它以不同的方式处理它们中的一些,并且我担心由于需要查询更多组件,我最终会有几个特殊字段.有些对象甚至没有渲染组件.
将所需信息作为GameObject的成员,但允许组件更新它.因此,对象的宽度和高度默认为0或-1,但渲染组件可以在其更新循环中将它们设置为正确的值.这感觉就像一个黑客,我可能最终会向GameObject类推送很多东西以方便使用,即使并非所有对象都需要它们.
组件实现一个接口,指示可以查询的信息类型.例如,渲染组件将实现HasSize接口,该接口包括getWidth和getHeight等方法.当GameObject需要宽度时,它会遍历其组件,检查它们是否实现了HasSize接口(instanceof
在Java中使用关键字,或is
在C#中).这似乎是一个更通用的解决方案,一个缺点是搜索组件可能需要一些时间(但是,大多数对象只有3或4个组件).
这个问题与具体问题无关.它常常出现在我的设计中,我想知道处理它的最佳方法是什么.性能有点重要,因为这是一个游戏,但每个对象的组件数量通常很小(最大值为8).
简短的版本
在基于组件的游戏系统中,在保持设计通用性的同时,将信息从组件传递到对象的最佳方法是什么?
Kyl*_*tan 18
我们在GameDev.net(游戏对象通常被称为'实体')上每周三到四次得到这个问题的变化,到目前为止,对最佳方法没有达成共识.已经证明几种不同的方法是可行的,但我不会太担心它.
但是,通常问题是组件之间的通信.人们很少担心从组件获取信息到实体 - 如果实体知道它需要什么信息,那么可能它确切地知道它需要访问哪种类型的组件以及它需要在该组件上调用哪个属性或方法来获取数据.如果你需要被动而不是主动,那么注册回调或者用组件设置观察者模式,让实体知道组件中的某些东西何时发生了变化,并在那时读取值.
完全通用的组件在很大程度上是无用的:它们需要提供某种已知的接口,否则它们就不存在了.否则你也可能只有一个非关键值的大型关联数组,并完成它.在Java,Python,C#和其他比C++稍高级的语言中,您可以使用反射为您提供使用特定子类的更通用方法,而无需将类型和接口信息编码到组件本身中.
至于沟通:
有些人假设一个实体总是包含一组已知的组件类型(其中每个实例都是几个可能的子类之一),因此可以直接引用另一个组件并通过其公共接口读/写.
有些人正在使用发布/订阅,信号/插槽等来创建组件之间的任意连接.这看起来有点灵活,但最终你还需要了解这些隐式依赖的知识.(如果在编译时知道这一点,为什么不使用以前的方法呢?)
或者,您可以将所有共享数据放在实体本身中,并将其用作每个组件可以读取和写入的共享通信区域(与AI中的黑板系统有些相关).这通常需要面对某些属性时的强大性,而这些属性在您预期时不存在.它也不适用于并行性,尽管我怀疑这是一个小型嵌入式系统的大问题......?
最后,有些人拥有实体根本不存在的系统.组件存在于其子系统中,实体的唯一概念是某些组件中的ID值 - 如果Rendering组件(在Rendering系统中)和Player组件(在Players系统中)具有相同的ID,那么您可以假设前者处理后者的绘制.但是没有任何单个对象聚合这些组件.
mun*_*ent 12
就像其他人所说的那样,这里没有永远正确的答案.不同的游戏将适合不同的解决方案.如果您正在构建一个包含许多不同类型实体的大型复杂游戏,那么在组件之间使用某种抽象消息传递的更加分离的通用体系结构可能值得您获得可维护性.对于具有类似实体的更简单的游戏,将所有状态推送到GameObject中可能是最有意义的.
对于您需要在某处存储边界框并且只有碰撞组件关心它的特定场景,我会:
因此,不是让碰撞引擎迭代游戏对象的集合来解决交互,而是让它直接迭代通过CollisionComponents的集合.一旦发生碰撞,将由组件将其推送到其父GameObject.
这给你带来了几个好处:
getCollisionComponent()
对每个对象进行迭代,那么指针跟随可能导致高速缓存未命中.为每个对象的每个帧执行此操作可能会耗费大量CPU.如果你感兴趣我在这里有更多关于这个模式,虽然看起来你已经理解了该章中的大部分内容.