首先我们要理解一个框架的思想,必不可少的就是阅读其中的代码。
故而这节主要是阅读框架的启动流程,从Scene
找到启动游戏的入口,也就是Global
下挂载的MonoBehaviour
脚本,打开Init
,边阅读边写注释:
代码以及注释
//Init.cs的代码
using System;
using CommandLine;
using UnityEngine;
namespace ET
{
/// <summary>
/// 整个框架入口 继承MonoBehaviour
/// </summary>
public class Init: MonoBehaviour
{
private void Start()
{
this.StartAsync().Coroutine();
}
private async ETTask StartAsync()
{
//防止切换场景后销毁
DontDestroyOnLoad(gameObject);
//监听未处理的异常,然后打印
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
{
Log.Error(e.ExceptionObject.ToString());
};
// 服务器启动服务的命令行参数
string[] args = "".Split(" ");
//解析配置
Parser.Default.ParseArguments<Options>(args)
.WithNotParsed(error => throw new Exception($"命令行格式错误! {error}"))
.WithParsed((o)=>World.Instance.AddSingleton(o));
//开始游戏配置的文件夹
Options.Instance.StartConfig = $"StartConfig/Localhost";
//添加Logger单例
World.Instance.AddSingleton<Logger>().Log = new UnityLogger();
ETTask.ExceptionHandler += Log.Error;
//添加时间信息类单例
World.Instance.AddSingleton<TimeInfo>();
//添加纤程管理类单例
World.Instance.AddSingleton<FiberManager>();
//异步等待添加资源加载组件单例,并且执行YooAsset资源包初始化(ET框架没有资源热更,自行编写)
await World.Instance.AddSingleton<ResourcesComponent>().CreatePackageAsync("DefaultPackage", true);
//添加代码加载器单例
CodeLoader codeLoader = World.Instance.AddSingleton<CodeLoader>();
//执行代码热更文件的加载
await codeLoader.DownloadAsync();
//执行代码热更
codeLoader.Start();
}
private void Update()
{
//更新时间信息
TimeInfo.Instance.Update();
//更新纤程管理器
FiberManager.Instance.Update();
}
private void LateUpdate()
{
//更新纤程管理器
FiberManager.Instance.LateUpdate();
}
private void OnApplicationQuit()
{
//关闭,移除全部单例类
World.Instance.Dispose();
}
}
}
//ResourcesComponent.cs的代码
public async ETTask CreatePackageAsync(string packageName, bool isDefault = false)
{
//加载YooAsset配置好的包
ResourcePackage package = YooAssets.CreatePackage(packageName);
if (isDefault)
{
YooAssets.SetDefaultPackage(package);
}
//读取全局配置文件,包括代码执行类型(客户端/服务端/双端)、打包类型(Develop/Release)、App类型(状态同步/帧同步)、运行模式
GlobalConfig globalConfig = Resources.Load<GlobalConfig>("GlobalConfig");
//运行模式
EPlayMode ePlayMode = globalConfig.EPlayMode;
//资源初始化
switch (ePlayMode)
{
//编辑器下的模拟模式
case EPlayMode.EditorSimulateMode:
{
EditorSimulateModeParameters createParameters = new();
createParameters.SimulateManifestFilePath = EditorSimulateModeHelper.SimulateBuild("ScriptableBuildPipeline", packageName);
await package.InitializeAsync(createParameters).Task;
break;
}
//离线运行模式
case EPlayMode.OfflinePlayMode:
{
OfflinePlayModeParameters createParameters = new();
await package.InitializeAsync(createParameters).Task;
break;
}
//联网运行模式
case EPlayMode.HostPlayMode:
{
string defaultHostServer = GetHostServerURL();
string fallbackHostServer = GetHostServerURL();
HostPlayModeParameters createParameters = new();
createParameters.BuildinQueryServices = new GameQueryServices();
createParameters.RemoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
await package.InitializeAsync(createParameters).Task;
break;
}
default:
throw new ArgumentOutOfRangeException();
}
}
//CodeLoader.cs的代码
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using HybridCLR;
using UnityEngine;
namespace ET
{
/// <summary>
/// 代码加载器
/// </summary>
public class CodeLoader: Singleton<CodeLoader>, ISingletonAwake
{
/// <summary>
/// model程序集
/// </summary>
private Assembly modelAssembly;
/// <summary>
/// modelView程序集
/// </summary>
private Assembly modelViewAssembly;
/// <summary>
/// Dll
/// </summary>
private Dictionary<string, TextAsset> dlls;
/// <summary>
/// 静态编译Dll
/// </summary>
private Dictionary<string, TextAsset> aotDlls;
private bool enableDll;
public void Awake()
{
//赋值是否启用Dll
this.enableDll = Resources.Load<GlobalConfig>("GlobalConfig").EnableDll;
}
/// <summary>
/// 加载Dll
/// </summary>
public async ETTask DownloadAsync()
{
//非编辑器下才需要加载Dll
if (!Define.IsEditor)
{
//这里的API调用,路径参数只需要文件夹里的任一文件即可,具体底层会获取当前资源包文件夹里的全部文件
this.dlls = await ResourcesComponent.Instance.LoadAllAssetsAsync<TextAsset>($"Assets/Bundles/Code/Unity.Model.dll.bytes");
this.aotDlls = await ResourcesComponent.Instance.LoadAllAssetsAsync<TextAsset>($"Assets/Bundles/AotDlls/mscorlib.dll.bytes");
}
}
/// <summary>
/// 非Mono生命周期,在DownloadAsync后执行
/// </summary>
public void Start()
{
//如果不是编辑器,直接读取已经加载好的Dll
if (!Define.IsEditor)
{
byte[] modelAssBytes = this.dlls["Unity.Model.dll"].bytes;
byte[] modelPdbBytes = this.dlls["Unity.Model.pdb"].bytes;
byte[] modelViewAssBytes = this.dlls["Unity.ModelView.dll"].bytes;
byte[] modelViewPdbBytes = this.dlls["Unity.ModelView.pdb"].bytes;
// 如果需要测试,可替换成下面注释的代码直接加载Assets/Bundles/Code/Unity.Model.dll.bytes,但真正打包时必须使用上面的代码
//modelAssBytes = File.ReadAllBytes(Path.Combine(Define.CodeDir, "Unity.Model.dll.bytes"));
//modelPdbBytes = File.ReadAllBytes(Path.Combine(Define.CodeDir, "Unity.Model.pdb.bytes"));
//modelViewAssBytes = File.ReadAllBytes(Path.Combine(Define.CodeDir, "Unity.ModelView.dll.bytes"));
//modelViewPdbBytes = File.ReadAllBytes(Path.Combine(Define.CodeDir, "Unity.ModelView.pdb.bytes"));
//HybridCLR的IL2CPP的处理方式
if (Define.EnableIL2CPP)
{
foreach (var kv in this.aotDlls)
{
TextAsset textAsset = kv.Value;
//补充元数据,使用超集的模式(在允许使用裁剪后的Dll的情况下,还允许使用原始Dll进行补充)
RuntimeApi.LoadMetadataForAOTAssembly(textAsset.bytes, HomologousImageMode.SuperSet);
}
}
//加载model的程序集
this.modelAssembly = Assembly.Load(modelAssBytes, modelPdbBytes);
//加载modelView的程序集
this.modelViewAssembly = Assembly.Load(modelViewAssBytes, modelViewPdbBytes);
}
else
{
//编辑器模式下,如果启动Dll的模式
if (this.enableDll)
{
//直接从文件读取后加载,无需补充元数据
byte[] modelAssBytes = File.ReadAllBytes(Path.Combine(Define.CodeDir, "Unity.Model.dll.bytes"));
byte[] modelPdbBytes = File.ReadAllBytes(Path.Combine(Define.CodeDir, "Unity.Model.pdb.bytes"));
byte[] modelViewAssBytes = File.ReadAllBytes(Path.Combine(Define.CodeDir, "Unity.ModelView.dll.bytes"));
byte[] modelViewPdbBytes = File.ReadAllBytes(Path.Combine(Define.CodeDir, "Unity.ModelView.pdb.bytes"));
this.modelAssembly = Assembly.Load(modelAssBytes, modelPdbBytes);
this.modelViewAssembly = Assembly.Load(modelViewAssBytes, modelViewPdbBytes);
}
else
{
//反射加载
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly ass in assemblies)
{
string name = ass.GetName().Name;
if (name == "Unity.Model")
{
this.modelAssembly = ass;
}
else if (name == "Unity.ModelView")
{
this.modelViewAssembly = ass;
}
if (this.modelAssembly != null && this.modelViewAssembly != null)
{
break;
}
}
}
}
//加载热更代码(元组?),与本方法类似
(Assembly hotfixAssembly, Assembly hotfixViewAssembly) = this.LoadHotfix();
//添加CodeTypes的单例并Awake
World.Instance.AddSingleton<CodeTypes, Assembly[]>(new[]
{
//之前加载完毕的程序集
typeof (World).Assembly, typeof (Init).Assembly, this.modelAssembly, this.modelViewAssembly, hotfixAssembly,hotfixViewAssembly
});
//热更代码结束,进入正式逻辑,反射执行ET.Entry类的Start方法
IStaticMethod start = new StaticMethod(this.modelAssembly, "ET.Entry", "Start");
start.Run();
}
/// <summary>
/// 加载热更程序集
/// </summary>
private (Assembly, Assembly) LoadHotfix()
{
byte[] hotfixAssBytes;
byte[] hotfixPdbBytes;
byte[] hotfixViewAssBytes;
byte[] hotfixViewPdbBytes;
Assembly hotfixAssembly = null;
Assembly hotfixViewAssembly = null;
if (!Define.IsEditor)
{
hotfixAssBytes = this.dlls["Unity.Hotfix.dll"].bytes;
hotfixPdbBytes = this.dlls["Unity.Hotfix.pdb"].bytes;
hotfixViewAssBytes = this.dlls["Unity.HotfixView.dll"].bytes;
hotfixViewPdbBytes = this.dlls["Unity.HotfixView.pdb"].bytes;
// 如果需要测试,可替换成下面注释的代码直接加载Assets/Bundles/Code/Hotfix.dll.bytes,但真正打包时必须使用上面的代码
//hotfixAssBytes = File.ReadAllBytes(Path.Combine(Define.CodeDir, "Unity.Hotfix.dll.bytes"));
//hotfixPdbBytes = File.ReadAllBytes(Path.Combine(Define.CodeDir, "Unity.Hotfix.pdb.bytes"));
//hotfixViewAssBytes = File.ReadAllBytes(Path.Combine(Define.CodeDir, "Unity.HotfixView.dll.bytes"));
//hotfixViewPdbBytes = File.ReadAllBytes(Path.Combine(Define.CodeDir, "Unity.HotfixView.pdb.bytes"));
hotfixAssembly = Assembly.Load(hotfixAssBytes, hotfixPdbBytes);
hotfixViewAssembly = Assembly.Load(hotfixViewAssBytes, hotfixViewPdbBytes);
}
else
{
if (this.enableDll)
{
hotfixAssBytes = File.ReadAllBytes(Path.Combine(Define.CodeDir, "Unity.Hotfix.dll.bytes"));
hotfixPdbBytes = File.ReadAllBytes(Path.Combine(Define.CodeDir, "Unity.Hotfix.pdb.bytes"));
hotfixViewAssBytes = File.ReadAllBytes(Path.Combine(Define.CodeDir, "Unity.HotfixView.dll.bytes"));
hotfixViewPdbBytes = File.ReadAllBytes(Path.Combine(Define.CodeDir, "Unity.HotfixView.pdb.bytes"));
hotfixAssembly = Assembly.Load(hotfixAssBytes, hotfixPdbBytes);
hotfixViewAssembly = Assembly.Load(hotfixViewAssBytes, hotfixViewPdbBytes);
}
else
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly ass in assemblies)
{
string name = ass.GetName().Name;
if (name == "Unity.Hotfix")
{
hotfixAssembly = ass;
}
else if (name == "Unity.HotfixView")
{
hotfixViewAssembly = ass;
}
if (hotfixAssembly != null && hotfixViewAssembly != null)
{
break;
}
}
}
}
return (hotfixAssembly, hotfixViewAssembly);
}
public void Reload()
{
(Assembly hotfixAssembly, Assembly hotfixViewAssembly) = this.LoadHotfix();
CodeTypes codeTypes = World.Instance.AddSingleton<CodeTypes, Assembly[]>(new[]
{
typeof (World).Assembly, typeof (Init).Assembly, this.modelAssembly, this.modelViewAssembly, hotfixAssembly,
hotfixViewAssembly
});
codeTypes.CreateCode();
Log.Info($"reload dll finish!");
}
}
}
//CodeTypes.cs的代码
using System.Collections.Generic;
using System.Reflection;
using System;
namespace ET
{
/// <summary>
/// 代码类型集合
/// </summary>
public class CodeTypes: Singleton<CodeTypes>, ISingletonAwake<Assembly[]>
{
private readonly Dictionary<string, Type> allTypes = new();
private readonly UnOrderMultiMapSet<Type, Type> types = new();
public void Awake(Assembly[] assemblies)
{
//获取Dll的全部类型
Dictionary<string, Type> addTypes = AssemblyHelper.GetAssemblyTypes(assemblies);
foreach ((string fullName, Type type) in addTypes)
{
//赋值
this.allTypes[fullName] = type;
//抽象类型
if (type.IsAbstract)
{
continue;
}
// 记录所有的有BaseAttribute标记的的类型(例如[EventSystem]这种类型)
object[] objects = type.GetCustomAttributes(typeof(BaseAttribute), true);
foreach (object o in objects)
{
this.types.Add(o.GetType(), type);
}
}
}
public HashSet<Type> GetTypes(Type systemAttributeType)
{
if (!this.types.ContainsKey(systemAttributeType))
{
return new HashSet<Type>();
}
return this.types[systemAttributeType];
}
public Dictionary<string, Type> GetTypes()
{
return allTypes;
}
public Type GetType(string typeName)
{
return this.allTypes[typeName];
}
public void CreateCode()
{
var hashSet = this.GetTypes(typeof (CodeAttribute));
foreach (Type type in hashSet)
{
object obj = Activator.CreateInstance(type);
((ISingletonAwake)obj).Awake();
World.Instance.AddSingleton((ASingleton)obj);
}
}
}
}
//Entry.cs的代码
using MemoryPack;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Serializers;
namespace ET
{
public struct EntryEvent1
{
}
public struct EntryEvent2
{
}
public struct EntryEvent3
{
}
public static class Entry
{
public static void Init()
{
}
public static void Start()
{
StartAsync().Coroutine();
}
private static async ETTask StartAsync()
{
//根据平台不同初始化时间精度
WinPeriod.Init();
// 注册Mongo type
MongoRegister.Init();
// 注册Entity序列化器
EntitySerializeRegister.Init();
//创建无需热重载的通用组件
World.Instance.AddSingleton<IdGenerater>();
World.Instance.AddSingleton<OpcodeType>();
World.Instance.AddSingleton<ObjectPool>();
World.Instance.AddSingleton<MessageQueue>();
World.Instance.AddSingleton<NetServices>();
World.Instance.AddSingleton<NavmeshComponent>();
World.Instance.AddSingleton<LogMsg>();
//创建需要reload的各种code单例(各种消息分发组件/EventSystem)
CodeTypes.Instance.CreateCode();
//添加配置加载单例,加载全部标记[Config]标签头的类
await World.Instance.AddSingleton<ConfigLoader>().LoadAsync();
//因为上面初始化了EventSystem,所以这里创建纤程后会分发到对应的事件管线
//检查SceneType.Main的引用后,发现该事件的Invoke为AInvokeHandler类
await FiberManager.Instance.Create(SchedulerType.Main, ConstFiberId.Main, 0, SceneType.Main, "");
}
}
}
最后,在创建完纤程后抛出的事件里,会创建一个根实体Scene
在Fiber
下面,而后面又会给这个根实体Scene
挂载很多的组件实体,也就形成了如下的树状结构图。
注:
Scene
并非只允许挂载在Fiber
下,而是任意实体下都可。