ecs模式 java注解_[技术干货]Unity2018 ECS框架Entities源码解析!(1-4)

原标题:[技术干货]Unity2018 ECS框架Entities源码解析!(1-4)

(一)框架的启动与循环

本文主题:ECS框架的启动和循环,System的OnUpdate函数在哪里被调用,运行时disable一个System

先上个简约版的类图:

ec73348478cb0db5989af4b2a84216b5.png

World:保存着一个EntityManager和一堆ComponentSystem,默认情况下游戏启动时会为我们创建一个World并通过反射把所有的ComponentSystem类加进去。你要定义多个World也可以,像守望先锋那样有个普通游戏时的World,被打死后的回放就用另外的World处理。

Entity:只有id和version的小结构体

IComponentData:空接口而已,表明是个ECS的组件

ComponentSystem:继承该类后,用Inject特性标明关注的IComponentData,重写OnUpdate方法就可以访问到所有Entity上的IComponentData了,当然你可以设置更加复杂的关系,比如需要有A组件且没有B组件:

publicstructSampleGroup

{

publicComponentDataArray Acs;

publicSubtractiveComponent Bcs;

publicintLength;

}

甚至你还能指定某组件的值为x的集合:详细例子见:component特殊指定说明

ComponentGroup:在ComponentSystem里会针对大部分类型的Inject对象(比如上面的SampleGroup结构体)生成一个ComponentGroup,其包含了system所关注的组件信息。

关于ECS的源码,不需要反编译,从Unity菜单Window->Package Window里下载了Entities后就可以从以下目录找到:

C:UsersAdministratorAppDataLocalUnitycachepackagespackages.unity.com

为了调试方便,可以复制到项目的Packages目录里并把"com.unity.entities": "0.0.12-preview.19",修改为"com.unity.entities": "file:./com.unity.entities",这样就可以直接修改entities代码在Unity上调试了。

我们大致可以猜到会有个地方调用整个系统的初始化函数的,然后new一个默认的World把所有我们定义的System类注册进去,接着在主循环里每帧调用所有System的OnUpdate,所以我对着World类的构造函数右键-》查看所有引用就找到线索了。

具体细节:

AutomaticWorldBootstrap.Initialize(一般模式)或GameObjectEntity.OnEnable(Editor启动)->

DefaultWorldInitialization.Initialize->

publicstaticvoidInitialize(stringworldName, booleditorWorld)

{

varworld = newWorld(worldName);

World.Active = world;

IEnumerable allTypes;

foreach(varass inAppDomain.CurrentDomain.GetAssemblies)

{

allTypes = ass.GetTypes;

CreateBehaviourManagersForMatchingTypes(editorWorld, allTypes, world);

}

BehaviourUpdateOrder.UpdatePlayerLoop(world);

}

staticvoidCreateBehaviourManagersForMatchingTypes(booleditorWorld, IEnumerable allTypes, World world)

{

varsystemTypes = allTypes.Where(t => t.IsSubclassOf(typeof(ComponentSystemBase)));

foreach(vartype insystemTypes)

{

world.GetOrCreateManager(type);

}

}

为突出重点,删掉了不少代码,详细还是去看原文件的好。

上面的代码主要就是new一个World类并用AppDomain.CurrentDomain.GetAssemblies遍历所有类,把继承自ComponentSystemBase的类都用World.Active.GetOrCreateManager(type)加进去。->BehaviourUpdateOrder.UpdatePlayerLoop(World.Active)->

在这里给所有的System根据UpdateAfter、UpdateBefore和UpdateInGroup三种特性、是否支持多线程和读写权限等约束给System们排好序(这个排序逻辑也够写另外一章了),然后就按这个顺序调用所有BehaviourManager的Update方法,在调用我们定义System的OnUpdate方法前,ComponentSystem:UpdateInjectedComponentGroups就为我们“注入”该System需要用到的ComponentData,然后就可以愉快地玩耍了。

由于默认情况下World都会把我们所有的System给加进去,那么如果我们想运行时控制某个系统不执行应该怎么弄呢?我们只好从ComponentSystem和其父类的代码去找了,ComponentSystem调用Update前做了哪些操作:

internalsealedoverridevoidInternalUpdate()

{

if(Enabled && ShouldRunSystem)

{

if(!m_PreviouslyEnabled)

{

m_PreviouslyEnabled = true;

OnStartRunning;

}

BeforeOnUpdate;

try

{

OnUpdate;

}

finally

{

AfterOnUpdate;

}

}

elseif(m_PreviouslyEnabled)

{

m_PreviouslyEnabled = false;

OnStopRunning;

}

}

第三行我们就看到Enabled字段了,在上面的类图里我们知道ComponentSystem也是继承自BehaviourManager的,所以我们可以通过World获取该System并disable掉它:

World.Active.GetOrCreateManager .Enabled = false;

(二)组件与Chunk的内存布局

为了性能考虑,所有Entity里的组件数据都会存放在被称为Chunk的连续内存空间里,因为cpu访问某内存时会顺便加载附近的一段数据,所以数据的连续存放有利于提高缓存命中。

当然,并不是所有的A组件的值都放在同一个Chunk里的,Chunk是按EntityArchetype分配的。EntityArchetype核心是Archetype类,其实就是一个组件的数组,拥有相同组件集合的Entity指向相同的Archetype,Archetype对应一个Chunk数组,Chunk是一个固定大小的缓存空间,当一个Chunk空间用光了就创建多一个。

当你对一个特定的entity调用EntityManager:AddComponent时传入A组件就会获取或创建一个仅有A的Archetype(拥有相同组件集合的Archetype只会存在一份,由ArchetypeManager.m_TypeLookup管理),再次调用AddComponent传入B组件时就会创建一个{A,B}组件集合的Archetype,并把之前A组件的数据复制到新的Archetype指向的Chunk上,所以官方也推荐先创建好EntityArchetype来CreateEntity,或者一次性传入所有需要的组件。

创建Archetype时会预计算好各个组件占用的字节大小和内存偏移等信息,然后分配一段固定大小的内存空间给Chunk。在取某Entity某组件数据时就找到该Chunk,根据自己的IndexInChunk和组件在Chunk里的偏移就可以访问到了。

我们先看看创建一个Entity时做的操作:

首先我们知道Entity只是一个Index,关于它的信息都交由EntityDataManager管理,m_Entities数组就是干这个用的(注意Entity的值(Index和Version两int而已)还会和它的组件值保存在同个Chunk上,这样用EntityArray时才能在连续内存高速访问),Entity的Index就是在此数组的索引,m_Entities数组里的元素类型是EntityData结构体,其中Archetype字段指定了Entity的Archetype(即拥有的组件类型集合),ChunkData字段保存着该Entity的组件具体值在哪个Chunk上以及Entity在Chunk的序号,可用于

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值