本文主题:ECS框架的启动和循环,System的OnUpdate函数在哪里被调用
先上个简约版的类图:
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的