欢迎参与讨论,转载请注明出处。
前言
在阅读本文之前,你需要了解一下何为ECS框架,今年年初本人也对此进行了相关研究。到了实际开发时发现确实有此需求,遂应用之。本文便记录其中心得。
ECS的意义
在讨论实现细节之前,首先要弄明白一个关键问题:为何要用ECS? 对于我而言,使用ECS的意义在于使用传统OOP方式构造一个高度复杂的对象集时异常困难。在我看来,高度复杂的对象集即「多衍生物、衍生物之间多多少少拥有些共性」的存在。游戏中于地图上活跃的对象便是如此,拥有多种形式(物体、特效、子弹、NPC、怪物……),而这些衍生物之间多多少少会拥有一些共性(怪物和NPC都要寻路),如何组织安排好这些功能是很麻烦的一件事。在以往的开发生涯中,这部分我重构过很多遍,尝试过各种形式(将通用的功能做成子对象之类的),最终发现:ECS便是解决此问题的绝佳利器。
实现要素
上图便是本项目ECS框架的结构了,大致介绍一二:
- Entity(实体): Entity是对象的主体,Component的容器,在数据结构的形式上就是个哈希表。
- Component(组件): Component是数据的容器,与Data对接,提取相关数据。Component的形式多样,如Transform、Aspect、Input等。Component只有构造函数。
- Data(数据): 来自配置文件,其中定义了各Component的配置所需。可由Manager将Data作为参数创建Entity。
- Group(群组): Group以Component作为条件筛选出合适的Entity集合,如此便可使符合条件的Entity运作相应的业务。
- System(系统): 业务运作的主体,以Group进行筛选出合适的Entity以执行相应的业务。分为
Enter, Init, Exit, Update, LateUpdate, Draw
六个业务函数。System的形式多样,如Drawing、Life、Battle等。 - Lib(库): 存放通用业务函数之处,原则上以具体所需Component为参数,而非Entity,如
Hitstop(attacker, identity, time)
。如此是为明确函数调用条件,以及可以使Component分别来自不同Entity,实现一些特殊需求。Lib的形式多样,如AI、Battle、Effect等。 - Manager(中枢): 负责Group与Entity的管理,如
AddComponent, DelComponent, NewGroup, NewEntity
等。 - Executor(执行): 整套系统的执行者,负责导入System,定义System的执行顺序以及提供System的执行场所。
使用演示
以上便是ECS框架的组成元素了,接下来展示一下使用场景:
可以看到,这是属性相关的System,它提供了每秒回复HP与MP的业务。拥有Battle与Attributes组件的Entity方可执行,并且了ATTRIBUTE这个Lib的函数。采用这种形式只需要将业务分割为一个个System,以不同的Component组成游戏对象即可达到极高的灵活度。对于高度复杂的对象集而言可谓绝佳的解决方案。
子对象问题
在开发的过程中,总会遇到诸如状态、技能、BUFF之类需要以子对象形式存在的情况。为此应当如何实现是ECS框架绕不开的一个问题。我曾尝试为他们也纳入至ECS框架中,但是这样会使得System的数量膨胀,而且并没有带来什么明显的好处(它们的独立性很高)。也曾试过为它们弄二级ECS框架,但感觉很刻意死板。最终领悟到了一点:ECS框架对我而言的意义,只是降低构建对象的复杂度,若是对象本身的复杂度并不高,采用OOP的方式完全可以接受。
后记
ECS框架我只用在了地图对象,其余部分(如UI)等都是采用传统的面向对象形式,因为他们的构成复杂度并不高,切忌犯了“为用而用”的错误。当然对于Unity那边而言,ECS的意义在于达到高性能(内存连续、非GC、高Cache命中率、多线程),这时候为了高性能是不得不用了。当然Unity的ECS框架我所涉猎并不多,有待后日挖掘。