再谈组合式实体的架构设计(2)

第一部分,请点这里

上次谈到了CBES中,实体类的组成,以及实体类中实体,组件间的通信方式。这次继续这个话题,再来谈谈CBES中的性能问题和设计思路。

在CBES的实体类中,属性部分是用key-value对来描述的,这样可以方便的扩展,并且较为快速的读取和写入,但这样的灵活方式,是有一定代价的,一个是比起直接访问成员变量来说,效率并不会很高,另一个是对于经常访问的属性,这样的访问方式并不是很方便,也绕过了编译器检查,比如,key的拼写错误,getProperty(“behaviorID”)和getProperty(“behaviourID”),这样的访问方式,会导致这些“手误”只有在运行时才会被发现错误。

所以我们可以进一步改进实体类的定义,在实践中,我们发现,像位置,旋转,缩放等属性是会被经常访问的,需要把这些属性提炼出来。一个方案是把访问接口定义成员函数,适当封装一下,并把这些属性的键值设定为保留键值,如下图

Cbes class 6

这样我们可以保证属性访问的一致性(还是使用key-value),并且增加了接口易用性,如果属性的数据结构经过高度优化,读写速度够快,或者有架构“强迫症”的(注:极度追求代码结构优雅的:p),这样的方案很不错。但如果访问效率还是不能令人满意,可以用另一种方案,那就是直接把这些常用的属性定义成实体的成员变量,而脱离key-value的约束,这样的话,我们相当于人为的把实体的属性分了一个类,用成员变量定义的属性,可以称为固有属性(Built-in Property),用key-value定义的属性,可以称为自定义属性(User-defined Property)。如下图

Cbes class 7

这样的方案,一个显而易见的好处就是,提高了对常用属性的访问效率(有时候这很重要,特别是如果场景中有成百上千个实体的时候),对于属性的分类的另一个额外的好处是,我们可以用继承的方式,根据需要扩展实体的固有属性!也许有人要问,CBES不就是为了不用继承吗?但在实际应用中,我发现如果在CBES中适当的用继承,反而更为便利和合理。举个例子,我们游戏中有一种实体类叫SynchronizedEntity,它表示我们所有需要和网络同步的实体,假设整个游戏的80%的实体都是这种类型,在这个实体里,可能需要许多特定的属性(比如网络ID,服务器位置等等),这些属性会经常被访问和使用,所以,如果用继承将这些属性定义成固有属性,那对于这种类型的实体来说,将更为高效。

因此,一个改进的CBES架构就是,将Entity类的属性分为固有属性和自定义属性,在引擎层的Entity类中提供基本的固有属性(位置,旋转,缩放等等),在实际的游戏层,可以根据需要用继承的方式来扩展固有属性,定义一个(或多个)游戏层的Entity类,这就是我在现在的项目中,对于CEBS的设计和使用建议。结构图如下

 Cbes class 8 

 当然,这样的设计并不是十全十美的,它一个明显的问题,就是组件类不再是完全通用的了,因为它的owner由于继承的存在,不再是同一种Entity类型,所以,当该组件需要访问某一种Entity的固有属性时,它相当于限定了这个组件的使用范围,比如用上面的SynchronizedEntity举例,我们做了一个“逼近服务器位置”的运动组件,在这个组件中,需要读取SynchronizedEntity的“服务器位置”这个固有属性,然而这个固有属性在引擎的Entity类中是没有的,所以,这个组件就只能和SynchronizedEntity绑定了。但在实际使用中,这样的限制并没有带来很大的问题,因为当游戏中80%的实体都是这个的类型时,对于这个游戏的组件类来说,基本就是“通用”组件了。

说完了Entity设计,下面来谈谈组件的设计

前面的讨论中,我们相当于把Entity分为了两层,一层是引擎层,提供Entity的基类,一个是游戏层,定义游戏的Entity类。其实,组件的设计也可以遵循这样的原则,分为引擎层,和游戏层,并且组件功能的扩展就完全是靠继承来实现的了。引擎层的组件提供一些基本的Entity的行为功能,比如渲染,动画,AI,物理等等(参考Unity的设计,会很有帮助),有一些是基类需要游戏层去具体实现,比如,我们在游戏中定一个了一个鼠标处理的组件,当实体需要接受鼠标消息的时候,就可以绑定这个组件,但在引擎层,这个组件只有几个空的虚函数,需要游戏层通过继承去实现click,rollover,rollout等方法。

组件的设计一般遵循这样的原则,先实现游戏功能,然后通过提炼,看看能不能做成游戏内通用的组件,然后再看是否这个组件的功能具有普遍性,再决定是否放入引擎层的组件库中。我不建议一开始就去做通用的组件类,主要是,在项目中用CBES的时候,都是为了实现游戏的功能,是做Gameplay的,而不是为了写库,所以没有必要为了通用而“兴师动众”,只有当大量的实践和检验后,才有可能把用过的组件提炼成通用的组件类,放入引擎层,当然,这是个人建议,仅供参考:)。

好,基本上,CBES就聊完了,最后总结一下

  • 将实体分为属性部分(Property)和行为部分(Behavior)
  • 行为部分用组件(Component)来实现
  • 属性部分分为固有属性(Built-in Property)和自定义属性(User-defined Property),固有属性用成员变量来实现,自定义属性用key-value对来实现,可以通过继承来扩展固有属性
  • 可以用数据驱动的方式来配置属性和组件
  • 实体和组件的通信靠属性作为共享数据
  • 组件和组件的通信靠消息,不暴露组件的内部变量
  • 实体和实体的通信靠消息
  • 使用层次化(引擎层和游戏层)的方式来设计实体和组件

就像我一开始说的,不是很难,但会很实用,希望对大家有所帮助,最后附一个Stackoverflow上对于CBES的讨论,里面有一些文章很不错,感兴趣的可以去看看,如果E文不是很熟的,我在想是不是可以挑几篇帮大家翻译一下,有空可以做做。

http://stackoverflow.com/questions/1901251/component-based-game-engine-design/3495647#3495647

————————————————————————
作者:Finney
Blog:AI分享站(http://www.aisharing.com/)
Email:finneytang@gmail.com
本文欢迎转载和引用,请保留本说明并注明出处
———————————————————————— 

(已被阅读2,741次)

6 评论

  1. 看了博主的这两篇文章给了我一个启示:用map来作为实体的属性容器。然后我给实体加了一个有限状态机(相当于一个消息处理机)。这样实体对外的接口就统一了,耦合性大大降低。唯一不好的地方就是我用的是C++,变量不是动态类型,属性容器里的属性必须继承自同一个基类,使用起来得先转换为子类,有点不易用,而且得大量使用类的转换,不安全了。

  2. 看了楼主的文章后有去查了些英文资料,觉得楼主的说法和资料有些不同,我列一些我查的资料楼主有兴趣的话可以看看,如果说得不客气一点的话,楼主错误理解了实体架构。如果楼主觉得这些资料对你有帮助的话希望能给我回个邮件,我是个刚开始学习游戏开发,希望能得到一些指定,谢谢!
    1.what is an entity system framework for game development? http://www.richardlord.net/blog/what-is-an-entity-framework
    2.Understanding Component-Entity-Systems http://www.gamedev.net/page/resources/_/technical/game-programming/understanding-component-entity-systems-r3013
    3. Entity Systems are the future of MMOG development http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/

  3. 通过你给的http://stackoverflow.com/questions/1901251/component-based-game-engine-design/3495647#3495647链接找到了很多有用的东西,博主能否写一篇关于u3d编辑器在组件化设计相关的文章.

    1. 因为u3d我还没有在项目中实际用过,文章中的理解也是基于阅读u3d的文档,所以,具体u3d的设计,可能我需要实际用一下,才会有一些想法。不过,我觉得,总体的思路就是要把数据,行为,表现,通信设计好,要有专门的数据部分,行为(逻辑)组件只负责行为(逻辑),还有表现组件就负责呈现结果,各个组件分工明确就不太会乱

    2. 有个问题想问下博主,就是组件的细分粒度和如何组件之间的耦合度。比如,我一个npc角色,我将ai决策移动(逻辑决策层)作为组件,移动模块(执行层)也作为组件。那其实这两个组件的耦合程度是比较高的,彼此需要通讯比如移动到具体地点和已经到达的事件回调。 这样类似的情况应该怎么去分析呢?

    3. 可以通过共享数据来通信,比如在unity里可以专门datacomponent来存放共享数据,在我的结构里面,可以用entity身上的data模块,一般这种共享数据需要精确定义,比如我在做决策层和行为层的时候专门定义了一个request的数据结构来处理,细分粒度的话,建议按照大功能来分,不用太细

发表评论

邮箱地址不会被公开。

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

Copyright © 2011-2020 AI分享站    登录