Unity ECS框架Entities源码解析

时间:2024-03-24 11:03:00

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

先上个简约版的类图:

Unity2018 ECS框架Entities源码解析

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

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

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

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

public struct SampleGroup
{
	public ComponentDataArray<A> Acs;
	public SubtractiveComponent<B> Bcs;
	public int Length;
}      

甚至你还能指定某组件的值为x的集合:

详细例子见:component特殊指定说明

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

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

C:\Users\Administrator\AppData\Local\Unity\cache\packages\packages.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->

public static void Initialize(string worldName, bool editorWorld)
{
     var world = new World(worldName);
     World.Active = world;
     IEnumerable<Type> allTypes;
     foreach (var ass in AppDomain.CurrentDomain.GetAssemblies())
     {
         allTypes = ass.GetTypes();
         CreateBehaviourManagersForMatchingTypes(editorWorld, allTypes, world);
     }
     ScriptBehaviourUpdateOrder.UpdatePlayerLoop(world);
}

static void CreateBehaviourManagersForMatchingTypes(bool editorWorld, IEnumerable<Type> allTypes, World world)
{
     var systemTypes = allTypes.Where(t => t.IsSubclassOf(typeof(ComponentSystemBase)));
     foreach (var type in systemTypes)
     {
         world.GetOrCreateManager(type);
     }
}

为突出重点,删掉了不少代码,详细还是去看原文件的好。
上面的代码主要就是new一个World类并用AppDomain.CurrentDomain.GetAssemblies()遍历所有类,把继承自ComponentSystemBase的类都用World.Active.GetOrCreateManager(type)加进去。 ->

ScriptBehaviourUpdateOrder.UpdatePlayerLoop(World.Active)->

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

下一章我们再看看ECS里组件的内存布局以及System是怎么注入ComponentData的