@leviyuan
2017-08-05T10:02:56.000000Z
字数 10110
阅读 573
supermobs lua ecs unity
e Entity 实体
类似于Unity的GameObject,仅仅只是一个东西,没有任何直接用处的一个东西
可以理解为载体、容器
c Component 组件
组件是放在在Entity里面的东西,可以拥有各种属性
与Unity中的Component类似,但只可以有属性,不能有方法
s System 系统
ecs世界里真正执行逻辑的部分,一般不可以存储任何属性
ecs提供了一套便捷的API让每一个system都可以快速获取到要处理的数据
各种不同的system分工合作,达到最终目的
pool 池
pool是entity的容器,某些情况下需要多个ecs世界并行,pool是最好的分隔方案
matcher 匹配器
matcher是system取数据的依据,作用对象是entity
matcher之于ecs,相当于sql之于数据库
group 组
符合相同matcher的一组entity称为group
与group相关有一些事件可以使用,例如:有新的entity加入指定group
在group之内,可以预设排序、索引等高级查询功能
有一个流水线,要做一种罐头
创建一个pool,名字叫罐头制作流水线
罐头由瓶子、盖子、水、糖浆、水果组成
我们最终要创建出的entity包含瓶子、盖子、水、糖浆、水果这五种component
流水线上有5道工序
需要5个system来实现这个过程
第一道工序,在合理的情况下(下游没有累积过多、总需求量没超),向流水线上放空瓶子
第一个system,在合适的时候,创建空entity,并添加瓶子组件
第二、三、四道工序,分别对应水、糖浆、水果,检查瓶子里没有自己要加的东西时,添加进去
第二、三、四个system,在entity上没有自己对应组件时,添加给entity
第五道工序,检查其他的都已经齐全,把盖子盖上
第五个system,当其他四个component都已经添加时,为entity添加盖子component
流水线把产品制作分成不同模块,方便分别改进优化
流水线为每个工人提供工作环境,单个工人不需要在意整体流程
产品出现问题时,可以在流水线上查找产品定位问题到单一工人
承担相同工作的工人可以在流水线上随意替换
ecs把流水线的好处带给coding
使用ecs最大的难点是要转变设计思路,它与面向对象的程序设计思路相差甚远
下面从一个实际的需求开始讲述如何使用ecs的思路来设计解决方案
需求:服务器列表功能
1、服务器有 编号、名称、分组、排序、状态属性需要显示
2、需要自动选择玩家上次登录的服务器
3、玩家可以手动刷新服务器状态
4、服务器列表中要显示玩家在该服务器拥有的角色基本信息
这一步需要设计出功能需要数据的最精简结构
这部分数据将是功能的核心数据,其他的数据都可以通过计算或者数据查询得到
在这个示例需求中,最核心的数据就是
服务器固定信息:{编号、名称、分组、序号}
服务器动态信息:{编号、状态}
玩家信息:{上次登录的服务器编号}
角色信息:{编号、名称、等级、所属服务器}
这样一个数据结构
PS:设计基础数据时要保证不同时变化的数据需要分开,示例中的服务器固定信息和动态信息
分开的好处是方便做数据匹配,可以让系统设计更加容易
在完整的数据结构里,将会数据的 整合、分组 设计
然后得出模块的Component设计
整合
整合的过程与面向对象设计思路类似,把相关数据合在一起形成对象形式,给Entity赋予实际含义
服务器固定信息、服务器动态信息都是服务器的数据,实际的逻辑是把这两个数据在一起使用
玩家上次登录的服务器实际上是服务器的一个标签
在需要的功能里,每个角色要跟随服务器来展现
所以我们有如下的设计:
ServerEntity { // 一个服务器 ServerInfoComponent { // 服务器的基本信息 id、name、group、order } ServerStateComponent {// 服务器状态信息 state } isLastLogin // 是否是上一次登录的服务器 ServerRoleListComponent {// 包含的角色id列表 list } } RoleInfoEntity { // 一个角色 RoleInfoComponent { // 角色信息 id、name、level、serverid } }
分组
把同类的Entity通过Component标识分组,然后加上索引或排序规则以便进行实际逻辑处理
整合过程与面向对象思路很像,但这里绝不是简单的按照“对象类型”分组
举一个简单的例子:
人、汽车、空气 都可以移动
人、汽车可以被看见,空气不行
用面向对象思路来归类这三个东西已然GG
在ecs的思路里,设计两个分组即可,分别是:可以移动的东西、可以被看见的东西
这个分组概念与GO语言的interface更接近,可以参考来理解
索引:参考sql数据的索引设计概念来理解,为分组内的数据设计快速查询方式(而不是遍历)
排序:按照实际需求做排序规则,以便具体逻辑直接使用
Group(ServerInfoComponent) { // 服务器数据组,ServerInfoComponent是分组标识 index : ServerInfoComponent.id // 通过id索引一个服务器 order : ServerInfoComponent.order // 通过order排序服务器 } Group(RoleInfoComponent) { // 角色数据组,RoleInfoComponent是分组标识 index : RoleInfoComponent.id // 通过id索引一个角色 index : RoleInfoComponent.serverid // 通过id索引一个角色 }
这一步将会得到一些System的设计结论,从以下两个方向来思考
基础数据获得
完整数据结构组织
汇总一下前面的结论,所有的Component如下:
RoleInfoComponent
所有的System如下(做了适当整合):
system:setreactiveexecute(func(self, entities, count))
设置系统为触发式,每次被轮询,如果有收集到触发的entity,就回调接口,并传入所有触发了的entity
因性能缘故,entities并不是一般的luatable,只能使用如下方式遍历:
for i = 1, count dolocal entity = entities[i]-- do some thing to entityend
ps:如果一个entity触发了条件,但是在执行前被删除,或者不满足触发执行的确认条件,entities中将不包含此entity;这种情况下,count值可能为0
system:addtrigger(triggerEvt)
为触发式system添加触发侦听条件
matcher代表指定的group,evtType有三种值
简单的matcher有缩写方式来指定triggerEvt
ecs.matcher.[componentname].[added|removed|addedorremoved]
ecs.matcher.[componentname].any.[added|removed|addedorremoved]
ecs.matcher.[componentname].none.[added|removed|addedorremoved]
ecs.component.regist("A", "a")ecs.component.regist("B", "b")local p = ecs.pool.create("test")local group = p:getgroup(ecs.matcher.A)-- 用B组件的属性来做索引计算,但是group的声明中并没有涉及到B-- ecs内部更新时,不会认为B组件的变化与这个group有任何关系-- 所以索引值完全不正确,没法办法去使用group:addindex("b", function(entity)return entity.hasB and entity.B.b or 0end)-- 实际的逻辑中,如果用A+B作为group的特征描述也ok,那么可以这样声明groupgroup = p:getgroup(ecs.matcher.A, eca.matcher.B)-- 如果只有A可以作为特征描述,B不与特征有直接关系,那么可以这样做-- 本质与getgroup(ecs.matcher.A)相同,但是向ecs系统说明了这个group与B有关group = p:getgroup(ecs.matcher.A, eca.matcher.A.any, eca.matcher.B.any)
geoup:addorder(ordername, filter(entity), compare, ...)
为group中的entity添加一个名为ordername排序
filter:过滤函数,nil表示不过滤,全部参与排序;函数的返回值表示是否参与排序
compare:比较函数,排序的内部实际上就是orderdict,compare等价orderdict的参数
{...}:排序计算使用的index,性能限制,只能使用索引值进行排序
group:ipairs(ordername)
按照指定的排序方式,遍历entity
for i, entity in group:ipairs("ordername") doprint(i, entity)end
{-- using UnityEngine;"A.B.*","C.*",-- using C = UnityEngine.Camera;c = "D.EE",f = "D.XXXX"}
没有声明shorttable的情况下,ecs系统会在全局搜索以'.componentname'结尾的组件,如果只有一个,那就当做是这个组件来处理,如果不止一个,将会抛出error,"componet name XXXXX is not explicited"
以示例的shorttable来说明组件搜索过程:
1、查找“c = "D.EE"”这样的格式,如果匹配,那么就是等号右边就是组件的fullname
2、依次把componentname代入"A.B.*"这样格式,查找是否存在这样fullname的组件,如果有,查找结束
3、无视shorttable,去全局查找,有可能出现"componet name XXXXX is not explicited"的报错
EntityEditor
pool-GameObject的children是Entity-GameObject,有
标识

对于每一项component

SystemsEditor
在Hierarchy中,DonotDestroyOnLoad下,有
标注的是System-GameObject
