Unity2018 ECS框架Entities源码解析(一)框架的启动与循环

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

先上个简约版的类图:

Unity2018 ECS框架Entities源码解析(一)框架的启动与循环_第1张图片

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 Acs;
	public SubtractiveComponent 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 allTypes;
     foreach (var ass in AppDomain.CurrentDomain.GetAssemblies())
     {
         allTypes = ass.GetTypes();
         CreateBehaviourManagersForMatchingTypes(editorWorld, allTypes, world);
     }
     ScriptBehaviourUpdateOrder.UpdatePlayerLoop(world);
}

static void CreateBehaviourManagersForMatchingTypes(bool editorWorld, IEnumerable 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)->

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

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

internal sealed override void InternalUpdate()
{
     if (Enabled && ShouldRunSystem())
     {
           if (!m_PreviouslyEnabled)
           {
                m_PreviouslyEnabled = true;
                OnStartRunning();
           }
           BeforeOnUpdate();
           try
           {
                OnUpdate();
           }
           finally
           {
                AfterOnUpdate();
           }
      }
      else if (m_PreviouslyEnabled)
      {
            m_PreviouslyEnabled = false;
            OnStopRunning();
      }
}

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

World.Active.GetOrCreateManager .Enabled = false;

下一章我们再看看ECS里组件的内存布局

你可能感兴趣的:(Unity,游戏开发,ECS,C#,UnityECS框架源码分析)