查看原文
其他

干货来袭丨这篇文章帮你快速了解组件-实体-系统

2016-08-03 崔嘉艺 Gad-腾讯游戏开发者平台

在传统方法中,实现游戏实体是使用面向对象编程(object-oriented programming)的方法。每个实体是一个对象,它非常直观的允许基于类的实例化系统并让实体可以通过多态(polymorphism)来进行扩展。但是这会导致庞大固化的类继承体系。随着实体数目的增长,越来越难以在整个类继承体系中放置一个新的实体,特别是在该实体需要大量不同类型的功能的时候。在下图中,你可以看到一个简单的类继承体系。一个静态敌人(static enemy)类与这个类继承树并不匹配。
为了解决这个问题,游戏开发者开始通过组合(composition)而不是继承(inheritance)来构建实体。一个实体就是组件的简单组合(技术术语是composition)。
这种方法相比较上面提到的面向对象架构有很多的优点:可以很容易地添加新的复杂的实体。
可以很容易地在数据中定义新实体。效率更高。你可能会注意到这里的组件都是纯数据组件-并没有自己的方法。这个问题将在后面的内容进行详细的解释。


1
组件
一个组件可以用C语言中的结构(struct)来进行类比。组件没有自己的方法并且只能用于存储数据,不能对组件本身采取什么行动(也就是对组件调用方法,让组件来执行某些行为)。在一个典型的组件实现中,每个不同类型的组件将会继承自一个抽象的组件类(abstract Component class),这个抽象的组件类会提供在运行时获取组件类型以及包含的实体的方法。每个组件描述了一个实体的某些方面和它的参数。只是单独拿出一个组件出来看是毫无意义的,但是如果把组件和实体以及系统一起使用时,它们将变得极其强大。空的组件对于标记实体也是非常有用的。
一些组件的例子
位置组件,用 (x, y)来描述。速度组件,用 (x, y)来描述。物理组件,用(物理体)来描述。精灵组件,用(图像, 动画)来描述。生命属性组件,(用生命值) 来描述。角色组件,用(名字, 等级) 来描述。玩家组件,内容为 (空)。
2
实体
实体是某种已经存在你的游戏世界中的物体。再强调一次,实体不仅仅只是一系列组件。因为他们是如此的简单,大部分的实现里面不会把实体定义为一个具体的数据。相反,一个实体会有一个独特的ID,所有组成这个实体的组件都会在组件的内容记录这个ID。实体其实是将用ID标记的组件隐式的聚合(implicit aggregation)起来。如果你愿意的话,可以允许实体中的组件可以动态的添加或者删除。这将使得你可以动态的“变动(mutate)”实体。举个例子来说,你可能会有一个法术可以让这个法术的目标冻结一段时间。要实现这个功能,你可以简单的移除速度组件。(我觉得这个例子举得是有点问题的,如果速度组件移除了,那么在更新物体位置的时候相对应的函数计算部分根本就没有办法取到速度组件,进而没有办法取到速度的值,这样确实是没有办法进行位置的更新,但是这要依赖于代码的具体实现,如果代码没有做足够的保护,将会导致崩溃,至少会导致对应函数的退出。一般来说,组件的动态添加是为了实现某个特殊的赋予功能,比如我有一个法术可以让法术的目标变形,那么我就将变形组件添加进去,这样实体才能够变形。等变形法术结束的时候,我就可以将变形组件从实体中移除出去。一般实现中动态删除往往是针对动态添加的组件,将一些预设的组件移除去要么让代码不稳定,要么增加编写的时候的复杂度)。

一些实体的例子
岩石实体, (有位置组件、精灵组件)。木箱(Crate)实体,(有位置组件、精灵组件、生命属性组件)。标记(Sign)实体, (有位置组件、精灵组件、文本组件)。球(Ball)实体, (有位置组件、速度组件、物理组件、精灵组件)。敌人实体 ,(有位置组件、速度组件、精灵组件、角色组件、输入组件、人工智能组件)。玩家实体, (有位置组件、速度组件、精灵组件、角色组件、输入组件、玩家组件)。 
3
系统
你可能已经注意到了我在前面的文章部分没有以任何形式提到游戏逻辑。因为这都是系统的任务,将由系统来处理所有的游戏逻辑。系统是在一组相关的组件上进行操作,一般来说所谓相关的组件是指属于同一个实体的组件。举个例子来说,角色移动系统可能会操作位置组件、速度组件、碰撞组件和输入组件。在逻辑顺序中每个系统将在每帧更新一次。举个例子来说,要让一个角色跳起来的话,首先要检测输入数据的keyJump成员。如果对输入数据的keyJump成员检测返回的结果为真的话,那么系统将去查看包含在碰撞数据的对应信息并检测当前角色是否站在地面上。如果检测的结果返回为真的话,它将设置速度组件的成员变量y来让角色真正的跳起来。
因为只有在整个组件集合全部出现的时候,系统才可以对组件进行操作,所以组件隐式的定义了一个实体可能会具有的行为。举个例子来说,如果一个实体只有位置组件但是没有速度组件,那么这个实体将会一直静止。因为运动系统需要使用位置组件和速度组件的信息,但是这个实体没有速度组件,所以运动系统没有办法对实体的位置来进行操作。给这个实体添加一个速度组件将使得运动系统可以对这个实体进行作用,进而让整个实体运动起来并受到重力的影响。这种行为可以被理解为“组件标签”(如同之前解释过的那样),可以在不同的上下文中重用组件。举个例子来说,一个输入组件定义了一些通用的标签比如跳跃、移动和射击。添加一个空的玩家组件(Player component)将向玩家控制系统(PlayerControl system)标记实体,这样的话,输入数据将根据控制器的输入进行填充。

一些系统的例子
移动系统(使用了位置组件、速度组件) – 会使用速度来更新位置。重力系统(使用了速度组件) – 由于重力引起了加速度进而会改变运动速度。渲染系统(使用了位置组件、精灵组件) – 会对精灵进行渲染。玩家控制系统(使用了输入组件、玩家组件) – 根据控制器的输入来设置由玩家控制的实体的输入信息。机器人控制系统(使用了输入组件、人工智能组件) – 根据人工智能代理的信息来设置被人工智能控制的实体的输入信息。 
4
总结
总结一下,有了新的组件-实体-系统做对比,基于OOP(面向对象编程)的对象继承体系可以被忘记了。实体就是指你的游戏物体,它将隐式的被定义为一组组件的集合。这些组件都是纯数据(也就是没有方法)并将由系统里面的函数来进行操作。
我希望我帮助你了解了组件-实体-系统这个设计体系是如何工作的,并能说服你它们比传统的面向对象编程更好。如果你对这篇文章有任何的疑问的话,请在文章的后面评论或者给我留言。后续文章已经发布了,它主要是提供了一个用C语言实现的组件、实体、系统的样例并且解决了一些设计问题。文章是《实现组件-实体-系统》。

近期热文

Unity开发秘诀丨使用 Unity开发安卓游戏时,如何追踪性能问题

【技术干货】深入浅出Unity内存泄漏

腾讯游戏开发者平台长按,识别二维码,加关注
经验分享丨项目实践项目孵化丨渠道发行做有梦想的游戏人

-GAME AND DREAM-





您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存