实体系统是MMOG开发的未来——第二部分

原文地址:http://t-machine.org/index.php/2007/11/11/entity-systems-are-the-future-of-mmog-development-part-2/


第2部分 - 什么是实体系统

第一部分在这里


可悲的是,到底什么才能被称为一个实体系统(Entity System ES),就此还存在一些争议。对于一些人来说,这和组件系统(Component System CS)甚至和面对组件编程(Component-Oriented Programming COP)是一样的。对于另一些人来说,它则是有着显著区别的事物。为了让事情变得简单,我将只讨论实体系统,恕我直言,实体系统是COP的一个特殊子集。个人认为,组件系统可能是面对组件编程的一个更精确的称呼,然而COP是一个令人迷惑且更不恰当的命名,我觉得你如果将这些东西称为组件系统,那么你完全就没有抓住重点。最好的称为应该是切面系统(Aspect Systems),但AOP(Aspect Oriented Programming)已经把这个名字里的Aspect占为己有了。


实体系统是你程序里的一个简单的组成部分,它使用一种特别的方式将你程序里的逻辑和数据分散到源代码中。


在大多数情况下,它确实是使用面对组件编程的模式而不是面向对象的。但有细微的区别之处,我们随后进行探讨。


我们之所以提到实体“系统”而不是实体“编程”,是因为ES通常会集成在一个大型的OOP的程序中。ES被用来解决那些OOP不擅长的问题中的一部分。


因此,它通常是面对对象编程模式的一个替代方案。两者是互斥的:你必须从两者中选择一个(尽管任何复杂的程序会有多个抽象层,你可以将OOP和COP/ES混合配对到不同的抽象层里去,这样一来,分配到的层次也就成为了它们两者中的一个(或者,你也可以用其他方式来构建你的程序))


常见误解 #1 —— 实体系统是OOP的一部分

常见误解 #2 —— Entity == Class, Component == Object


OOP有类和对象,ES则有实体和组件 —— 尽管在表面上它们都有一些相同的特性,但实体不等同于类,组件也不等同于对象,注意到这一点非常重要。如果你用OOP的方式去思考实体/组件,那么你永远也无法理解ES,如果你试着用它来编程,那么你也只会搞砸。


ES所使用的不同编程方式(paradiagm)总的来说意味着它永远也不能直接等同于诸如类,或是对象之类的东西 —— 如果这种等价存在的话,那么它就和OOP的方法一样了,或者就是一个变种而已。不,它们在根本上就是不同的,也是不兼容的。


什么是实体(Entity)?

实体系统之所以这么命名是因为实体(Entities)是构建你的系统的基本概念砖石,每个实体都代表着游戏中不同的具体对象。你的游戏世界中每个明显的“事物”都是一个实体。实体没有任何数据和方法。


就运行时实例的数目而言,他们就像OOP概念中的对象一样(如果你有100辆同类坦克,那么就有100个实体,而不是一个)。


但是,就行为(behaviour)而言,他们就像类(实体间接定义游戏中对象的行为——我们后面马上说到)。


所以,实体仅靠自身的话基本上就跟完全无用差不多——他们是粗粒度的,它们的作用也就是将每个粗粒度的游戏对象标记为不同的物体而已。这个时候组件(Component)就该登场了。


什么是组件(Component)?

每一个游戏中的物体都有多个面(facets)或切面(aspects),解释了这个物体是什么,和它如何跟世界交互。举个例子,一辆自行车是:

  • 金属制造
  • 能够被人类使用
  • 交通工具
  • 能够被交易
  • 流行的生日礼物
  • 人造的

在这一点上请注意:OOP没有多面(multiple aspects)的概念;OOP会将所有的这些特性硬编码成一团,然后将行为的集合和数据声明为一个对象的单面(single-aspected)类。

C++的多重继承和Java的接口(Interface)能让你获得ES很小的一部分特性,但他们很快就会在OOP那种无法修复的基本方法的限制(strain)下崩塌。


表示一个物体的实体表现出物体具有的不同切面的方法,就是为物体的每一个切面分配一个组件。组件会做一件非常重要的事情:

  • 为实体拥有特定切面做标记

组件可能还会做其他的一些事情,取决于你对ES的具体设计和实现。但无论如何,对于一个ES而言,这是最重要的事情了,因为这是所有方法调用和执行的基础。


什么是系统或子系统(System or subsystem)?

ES在如何分离代码和数据的时空上走得比OOP更远。用OOP的方式,你会把所有的东西都塞到对象里去。用ES的方式,你会将代码和数据分离到两个不同的东西里去:第一个是“实体+组件”,第二个是“系统”。


这是ES诸多价值的来源。OOP在任何程序中实现那些——有大量游离数据(floating data),和,只需要作用于程序很小一部分数据的实例上的大量游离方法,的部分——非常在行。

OOP在实现程序中那些需要对 “所有东西” 进行操作,或是会被“任意地方”调用的 “全局”(global)部分上表现很糟糕。ES通过使用系统明确地(explicitly)处理所有的全局事务,而这不在实体/组件的范畴内。


每个系统都连续的运行(仿佛每个系统都有属于自己的线程)并对拥有与该系统切面相同组件的实体进行全局操作。


在系统和可用(AVAILABLE)的切面(又称为组件类型)之间存在着一一对应的关系。一个系统本质上是提供了指定类型组件的方法实现,但和OOP相比,它是自后向前来实现的。OOP的方式是让组件拥有0个或多个方法,然后组件外部会在某个时候调用它们。ES的风格是让每个组件都不实现任何方法,而是让连续执行的系统每次根据不同的组件来调用系统内部的方法。


游戏中一个典型的系统可能是:渲染系统、动画系统、输入系统等等


渲染系统每16毫秒唤醒一次,渲染所有拥有可渲染(Renderable)组件的实体,随后回到睡眠状态


动画系统不断的查找任何可能触发一个新动画或改变一个已有动画的事件(比如说玩家变换方向的时候),同时更新每一个受影响的实体(当然,只更新那些具有动画能力(Animatable)的组件,或称其为动画(Animations)


输入系统会轮询游戏手柄,鼠标等输入设备,同时改变任何有可响应输入组件(Inputable Component)且被标记为正在被玩家控制的实体。


在传统的OOP中,如果你在战场上有100个单位,每一个都用一个对象去代表,那么理论上你有100份每个单位可以被调用的方法的拷贝。在实际应用中,多数OOP语言会在幕后使用运行时和编译器优化来让这些方法得到共用,同时避免内存浪费——虽然脚本语言通常不做任何共用,使得每个实例的方法都可以被单独改变。


相比之下,在ES中,如果你在战场上有100个单位,每一个都用一个对象去代表,那么你有0份每个单位可以被调用的方法的拷贝——因为实体不包含方法。组件也没有方法。取而代之的是,对应每一个切面,你都有一个外部系统,这个外部系统包含了所有能够在实体上调用的方法,只要这些实体拥有和该系统兼容的组件即可。

那不是实体系统!

好吧,至少与我共事过的“大多数”人考虑的东西都不是实体系统。但哪怕只在一个项目内,我也发现过关于ES的多种理解,有一些看法是相互兼容的,剩下的则不是。下面是我遇到的一些人关于ES的一些想法。


实体系统的另类定义:


1. 比起OOP,ES提供了一种不一样的 类-继承(class-inheritance) 模型,特别是关于“继承”的那一部分。对于你声明的拥有切面的实体-类(entity-class),实际的类是在运行时通过将不同切面拼凑到一起来得以访问的。


2. 与1相同,但将ES的定义严重的误认为是方法和域,即认为ES提供了一个将函数/方法的继承混合和配对的有趣的方式。因此,只要方法还依附在它所作用的域上时,和/或者一个“事先需要”(pre-requisite)的系统实现了诸如“当你具有X切面的时候会自动让你也具有Y切面”这样的特性的时候,那么这就能很顺利的工作。请注意:这是在实现“模块化对象(Modular objects)”:“切面”完全就是OOP基本理论中的对象(objects)。


3. 是1和2的变种,切面的数据在编译期被预编译为大量的OOP类,这样运行时你只是在执行“普通”的类,不需要支付由于动态查找带来的消耗:“我有一个拥有X切面的实体E;有人调用了函数Z,请等我先创建一个函数表来查查看现在到底该怎么办…”


4. 一种将标准OOP类明确地编译为运行时数据的方法(注:将它们与OOP特性分离开来),这样你可以将它们视为流数据(stream data)而不是成块(chunked)的二进制和数据。这种方式在那些代码缓存(code cache)可用内存很小,读取额外的代码非常非常慢,但却有着海量序列数据吞吐能力的CPU的机器上非常有效。比起典型的PC做法而言,PlayStation尤其喜欢这种方式。注意,在这种情况下,游戏程序员和设计师不会注意到实体系统的存在——它只是一个运行时/编译时的性能优化手段,而不是一个设计好的功能。


5. 一种过度围绕观察者模式(Observer pattern)设计的系统,但是这样一来,使用的是标准的OOP技术。我之所以提到这个是因为它是ES概念(错误的)使用方式中常见的一种,但是缺失了诸多优势。更糟糕的是,这是我第一次发现ES概念时候所处的境地。所以,就我个人而言,我不认为这些是ES的特性。这些不是ES,只不过是一句:“天啊!我需要ES!”,但是许多人坚持认为(和声称)它们是真的ES。


请注意:恕我直言,这是关于实体系统的使用方法里,“你真的不该这样做”中的一类。为什么?因为你最终只是创造了无数有着独立观察者方法附着的OOP类而已。作为一个通用的方案是不错的,它能提供一些东西而同时保留完整的OOP,但它也同时失去了ES本可提供的,特别是在灵活性和复合继承领域的东西。

谁关注ES,为什么?(他们是怎么发明ES的?)

1. 那些曾经编写图像/渲染引擎并尝试将它与剩余的游戏部分分离开来的程序员,。他们了解到可以在只使用游戏中OOP对象一部分属性(几个切面——不超过2到3个)的情况下编写整个图像引擎。

他们需要用到:

可渲染属性(每一帧都要绘制到屏幕上么?[会有通用的绘制方法])

可更新属性(是否每一次或每隔几次游戏循环(game-loop-tick)就有可能会改变?[动画是一个极佳的例子——它们的内部状态会独立于其被渲染了几次而改变])


2. 那些寻找能够使切面本身(per se)(原文:例如可渲染属性相对于可更新属性)能够应用于粗粒度多线程编程概念的多线程程序员:你可以为每一个切面分配一个单独的线程,而且是保证线程安全的,因为切面从定义上就是互相独立的。因为一个典型的ES会有一打或更多的切面,你能够在高达12颗独立核心CPU上获得非常不错的性能,免费。


同时,就更深入和进一步提升性能的角度来说,他们在寻找可以组成进行多线程运算原子层次的单个最大粒度(就字面意义而言:那些“不可再分”)的东西。默认情况下只有手写的硬编码(manually-hard-coded)原子操作才算数,而且OOP对象当然都充满了多线程的各个粒度过粗的部分,但也许实体中的组件小得足以成为原子性的单位。


3. 那些希望能够“万物皆允(everything become anything)”的游戏设计师:

摄像机能够射击人么?

子弹能够接受来自玩家的输入么?

不行?


呃…你有没有玩过虚幻竞技场?如果上面的问题你没有回答“是”的话,你需要找人给你看看一把使用在副开火模式的救赎者(Redeemer)(提示:你可以通过安装在弹头里的摄像机提供的第一人称视角在火箭飞行过程中手动控制其飞行路线)。

ES——万物皆允,万事皆允,而无需重新编码。这是给游戏设计师提供的优雅——所有的,由程序员强制提出的限制,你懂的,那些能让他们编写有可靠性能和无错代码的限制,突然就解除了。


4. 那些被观察者模式恶心到了的程序员。

如果你搞不懂这一条的意思,而你经常使用观察者模式,问问你自己:你有没有在使用观察者模式的时候遇到过“只有一个基类远远不够”的问题?例如,一个实现了一堆观察者的基类可以运行得很好,但随着系统/游戏/应用的扩展,你发现那个基类需要更多的变化(variety),你需要多种不同的版本,你还需要对那些不同版本的派生做“所有可能的组合”,因为那些基类无法/不可能漂亮的自己堆(stack)起来。

今天的观点

在编程中良好地使用实体系统与使用关系数型据库(Relational Database)编程非常类似。将ES称为“面对关系编程”不是没有道理的。


接下来的东西需要另外一篇帖子,但你可能会想思考一下:) 就上述声明在什么情况下是真的(提示:想想你为每个系统编写的代码看起来会是什么样的,它们到底做了什么),以及如果声明是真的话,会带来什么影响。


请记住,还没有人能够使用关系来快速地进行OOP;RDBMS和OOP之间的转换的高昂成本——在编码耗时和运行时上——仍然是开发MMO中最大的开发问题,没有之一。


欢迎转载,转载请注明出处


Creative Commons License
This work is licensed under a  Creative Commons Attribution-NonCommercial-ShareAlike 3.0 China Mainland License.
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值