ET版本5.0(6.0还未尝试,等6.0正式发布了再融合)
XAsset版本6.1.1(6.0+版本都可参考)
在进行代码修改前,请弄明白XAsset示例代码中加载资源与版本更新代码的运行机制,否则修改过程中遇到问题可能无法理解
准备阶段
下载XAsset的源码或Dll,将运行时和编辑器代码分别放入Editor文件夹与ThirdParty文件夹中。
删除ET原有代码
- 删除
Assets/Editor/BuildEditor
目录下BuildEditor脚本BuildHelper脚本 - 删除
Assets/Model/Module/AssetsBundle
目录下AssetsBundleLoaderAsync与AssetsLaderAsync,VersionConfig脚本
增加代码
-
在
Assets/Model/Module/AssetsBundle
目录下新增脚本Assetusing System; using UnityEngine; namespace ETModel { public class Asset : Entity { private VEngine.Asset asset; public new GameObject GameObject { get { return (GameObject)asset.asset; } } /// <summary> /// 同步加载 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="path"></param> /// <returns></returns> public Asset LoadAsset(string path) { return LoadAsset<GameObject>(path); } /// <summary> /// 同步加载 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="path"></param> /// <returns></returns> public Asset LoadAsset<T>(string path) where T : UnityEngine.Object { this.asset = VEngine.Asset.Load(path, typeof(T)); return this; } /// <summary> /// 异步加载 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="path"></param> /// <returns></returns> public async ETTask<Asset> LoadAssetAsync(string path) { return await LoadAssetAsync<GameObject>(path); } /// <summary> /// 异步加载 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="path"></param> /// <returns></returns> public async ETTask<Asset> LoadAssetAsync<T>(string path) where T : UnityEngine.Object { ETTaskCompletionSource tcs = new ETTaskCompletionSource(); var vAsset = VEngine.Asset.LoadAsync(path, typeof(T)); //如果已经完成,则直接返回 if (vAsset.isDone) { tcs.SetResult(); await tcs.Task; } vAsset.completed += OutAsset => { tcs.SetResult(); }; await tcs.Task; this.asset = vAsset; return this; } /// <summary> /// 异步实例化对象 /// </summary> /// <param name="path"></param> /// <returns></returns> public static async ETTask<GameObject> InstantiateObjectAsync(string path) { ETTaskCompletionSource<GameObject> tcs = new ETTaskCompletionSource<GameObject>(); var instantiate = VEngine.InstantiateObject.InstantiateAsync(path); if (instantiate.isDone) { tcs.SetResult(instantiate.result); return instantiate.result; } instantiate.completed += OutInstantiate => { tcs.SetResult(((VEngine.InstantiateObject)OutInstantiate).result); }; return await tcs.Task; } /// <summary> /// 异步加载场景 /// </summary> /// <param name="assetPath"></param> /// <param name="completed"></param> /// <param name="additive"></param> /// <returns></returns> public static async ETTask<VEngine.Scene> LoadSceneAsync(string assetPath, Action<VEngine.Scene> completed = null, bool additive = false) { ETTaskCompletionSource<VEngine.Scene> tcs = new ETTaskCompletionSource<VEngine.Scene>(); var scene = VEngine.Scene.LoadAsync(assetPath, completed, additive); if (scene.isDone) { tcs.SetResult(scene); return scene; } scene.completed += outScene => { tcs.SetResult(outScene); }; return await tcs.Task; } public override void Dispose() { if (IsDisposed) return; base.Dispose(); if (asset != null) { asset.Release(); asset = null; } } } }
修改代码
-
修改
Assets/Model/Module/AssetsBundle
目录下的BundleDownloaderComponent脚本,用于版本更新using System; using VEngine; namespace ETModel { [ObjectSystem] public class BundleDownloaderComponentUpdateSystem : UpdateSystem<BundleDownloaderComponent> { public override void Update(BundleDownloaderComponent self) { self.Update(); } } public class BundleDownloaderComponent : Component { public UpdateVersions update { get; private set; } public GetDownloadSize check { get; private set; } public DownloadVersions download { get; private set; } public Action<string, string, float> action; ETTaskCompletionSource tcs = null; /// <summary> /// 创建的打包清单 /// </summary> public ManifestInfo[] manifests = { new ManifestInfo { autoUpdate = true, name = "arts" } }; string totalSize = "0 B"; string downloadedSize = "0 B"; float progress = 0; /// <summary> /// 需要检查更新的分组 /// </summary> public string[] groupNames = new string[] { "Preload", "Bundled" }; /// <summary> /// 启动更新框架 /// </summary> /// <returns></returns> public async ETTask StartAsync() { // 下载网址 Versions.DownloadURL = Game.Scene.GetComponent<GlobalConfigComponent>().GlobalProto.AssetBundleServerUrl; ETTaskCompletionSource tcs = new ETTaskCompletionSource(); // 异步初始化 InitializeVersions operation = Versions.InitializeAsync(manifests); if (operation.isDone) { tcs.SetResult(); } else { operation.completed += OutOperation => { tcs.SetResult(); }; await tcs.Task; } if (operation.status == OperationStatus.Failed) { Log.Error("Asset初始化运行时失败,出错: {0}", operation.error); } } /// <summary> /// 等待更新任务完成 /// </summary> /// <returns></returns> public async ETTask AwakeComplete() { this.tcs = new ETTaskCompletionSource(); await this.tcs.Task; } /// <summary> /// 更新版本文件,并统计需要更新的资源 /// </summary> /// <returns></returns> public async ETTask<bool> CheckUpdateVersions() { await UpdateVersions(); await CheckVersions(); bool _bool = check.result.Count > 0; if (!_bool) tcs.SetResult(); return _bool; } /// <summary> /// 更新版本文件 /// </summary> /// <returns></returns> async ETTask UpdateVersions() { update = Versions.UpdateAsync(); ETTaskCompletionSource tcs = new ETTaskCompletionSource(); //如果已经完成,则直接返回 if (update.isDone) { tcs.SetResult(); return; } update.completed += outUpdate => tcs.SetResult(); await tcs.Task; } /// <summary> /// 查找需要更新的分组资源 /// </summary> /// <returns></returns> async ETTask CheckVersions() { check = Versions.GetDownloadSizeAsync(groupNames); ETTaskCompletionSource tcs = new ETTaskCompletionSource(); //如果已经完成,则直接返回 if (check.isDone) { tcs.SetResult(); //Logger.W("{0}个文件等待更新", check.result.Count); return; } check.completed += outCheck => { tcs.SetResult(); Logger.W("{0}个文件等待下载更新", check.result.Count); }; await tcs.Task; //Logger.W("{0}个文件等待更新", check.result.Count); } /// <summary> /// 下载资源 /// </summary> /// <returns></returns> public async ETTask DownloaderBundle() { Log.Warning("开始下载资源"); if (check.result.Count > 0) { var totalSize = Utility.FormatBytes(check.totalSize); Log.Warning($"版本更新:{check.result}({totalSize})"); download = Versions.DownloadAsync(check.result.ToArray()); ETTaskCompletionSource tcs = new ETTaskCompletionSource(); download.completed += outCheck => tcs.SetResult(); await tcs.Task; } Log.Warning("{0}个文件更新完成", check.result.Count); tcs.SetResult(); } internal void Update() { if (action != null && check != null) { if (download != null) { totalSize = BundleHelper.FormatBytes(download.totalSize); downloadedSize = BundleHelper.FormatBytes(download.downloadedBytes); progress = download.progress; } else { totalSize = BundleHelper.FormatBytes(check.totalSize); } action.Invoke(totalSize, downloadedSize, progress); } } } }
-
修改
Assets/Model/Helper
目录下BundleHelper脚本using System; using VEngine; namespace ETModel { public static class BundleHelper { public static async ETTask DownLoadBundle() { try { using (BundleDownloaderComponent bundleDownloaderComponent = Game.Scene.AddComponent<BundleDownloaderComponent>()) { await bundleDownloaderComponent.StartAsync(); Game.EventSystem.Run(EventIdType.UILoad_Update_Start); await bundleDownloaderComponent.AwakeComplete(); bundleDownloaderComponent.Dispose(); } } catch (Exception e) { Log.Error(e); } } } }
-
分别修改
Assets/Model/Module/UI
与Assets/Hotfix/Module/UI
目录下的UI脚本,下面只粘贴Model的修改后代码using System.Collections.Generic; using ETModel; using UnityEngine; namespace ETModel { [ObjectSystem] public class UiAwakeSystem_1 : AwakeSystem<UI, string, Asset> { public override void Awake(UI self, string name, Asset asset) { self.Awake(name, asset); } } [ObjectSystem] public class UiAwakeSystem_2 : AwakeSystem<UI, string, GameObject> { public override void Awake(UI self, string name, GameObject gameObject) { self.Awake(name, gameObject); } } [HideInHierarchy] public sealed class UI: Entity { public string Name { get; private set; } public Dictionary<string, UI> children = new Dictionary<string, UI>(); private Asset asset; public void Awake(string name, Asset asset) { this.children.Clear(); this.asset = asset; var gameObject = UnityEngine.Object.Instantiate(asset.GameObject); gameObject.AddComponent<ComponentView>().Component = this; gameObject.layer = LayerMask.NameToLayer(LayerNames.UI); this.Name = name; this.GameObject = gameObject; } public void Awake(string name, GameObject gameObject) { this.children.Clear(); gameObject.AddComponent<ComponentView>().Component = this; gameObject.layer = LayerMask.NameToLayer(LayerNames.UI); this.Name = name; this.GameObject = gameObject; } public override void Dispose() { if (this.IsDisposed) { return; } base.Dispose(); foreach (UI ui in this.children.Values) { ui.Dispose(); } UnityEngine.Object.Destroy(GameObject); if (this.asset != null) { asset.Dispose(); asset = null; } children.Clear(); } public void SetAsFirstSibling() { this.GameObject.transform.SetAsFirstSibling(); } public void Add(UI ui) { this.children.Add(ui.Name, ui); ui.Parent = this; } public void Remove(string name) { UI ui; if (!this.children.TryGetValue(name, out ui)) { return; } this.children.Remove(name); ui.Dispose(); } public UI Get(string name) { UI child; if (this.children.TryGetValue(name, out child)) { return child; } GameObject childGameObject = this.GameObject.transform.Find(name)?.gameObject; if (childGameObject == null) { return null; } child = ComponentFactory.Create<UI, string, GameObject>(name, childGameObject); this.Add(child); return child; } } }
加载卸载使用案例
核心修改就上面那些了,剩下的就是对ET运行中原有加载方式的一些修改。
在我的这套方案中,所有的资源加载控制以Asset为单位。而ET原有的加载方案,是用字符串控制的。可能很多用惯ET的用户来说,字符串控制更方便,但实际上字符串背后是由字典维护的,在资源查找方面会带来一定的开销。取而代之的采用Asset对象来控制,只消耗少量内存指针,加快了代码运行速度,且在资源卸载上更安全。
Asset使用方案差异,可参考下面列出的UI的加载卸载案例
-
UI加载卸载
UI Factory
using ETModel; using System; namespace ETHotfix { public static class UILogin_LobbyFactory { public static UI Create() { try { Asset asset = ETModel.ComponentFactory.Create<Asset>().LoadAsset("Assets/Bundles/UI/Login/UILogin_Lobby.prefab"); if (asset == null || asset.GameObject == null) { Log.Error("UI加载失败:" + UIType.UILogin_Lobby); return null; } UI ui = ComponentFactory.Create<UI, string, Asset>(UIType.UILogin_Lobby, asset, false); var Component = ui.AddComponent<UILogin_LobbyComponent>(); Component.UILayer = UILayer.Medium; return ui; } catch (Exception e) { Log.Error(e); return null; } } } }
RemoveUI(由于修改了UI基类的源码,所以在UI销毁时,会自动销毁持有的资源)
using ETModel; namespace ETHotfix { [Event(EventIdType.UILogin_Lobby_Finish)] public class RemoveUILogin_Lobby : AEvent { public override void Run() { Game.Scene.GetComponent<UIComponent>().Remove(UIType.UILogin_Lobby); } } }
-
Assets/Model/Entity/Hotfix.cs
using System; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEngine; #if !ILRuntime using System.Reflection; #endif namespace ETModel { public sealed class Hotfix : Object { #if ILRuntime private ILRuntime.Runtime.Enviorment.AppDomain appDomain; private MemoryStream dllStream; private MemoryStream pdbStream; #else private Assembly assembly; #endif private IStaticMethod start; private List<Type> hotfixTypes; public Action Update; public Action LateUpdate; public Action OnApplicationQuit; public void GotoHotfix() { #if ILRuntime ILHelper.InitILRuntime(this.appDomain); #endif this.start.Run(); } public List<Type> GetHotfixTypes() { return this.hotfixTypes; } public void LoadHotfixAssembly() { var asset = ComponentFactory.Create<Asset>().LoadAsset("Assets/Bundles/Independent/Code.prefab"); GameObject code = asset.GameObject; byte[] assBytes = code.Get<TextAsset>("Hotfix.dll").bytes; byte[] pdbBytes = code.Get<TextAsset>("Hotfix.pdb").bytes; asset.Dispose(); #if ILRuntime Log.Debug($"当前使用的是ILRuntime模式"); this.appDomain = new ILRuntime.Runtime.Enviorment.AppDomain(); this.dllStream = new MemoryStream(assBytes); this.pdbStream = new MemoryStream(pdbBytes); this.appDomain.LoadAssembly(this.dllStream, this.pdbStream, new ILRuntime.Mono.Cecil.Pdb.PdbReaderProvider()); this.start = new ILStaticMethod(this.appDomain, "ETHotfix.Init", "Start", 0); this.hotfixTypes = this.appDomain.LoadedTypes.Values.Select(x => x.ReflectionType).ToList(); #else Log.Debug($"当前使用的是Mono模式"); this.assembly = Assembly.Load(assBytes, pdbBytes); Type hotfixInit = this.assembly.GetType("ETHotfix.Init"); this.start = new MonoStaticMethod(hotfixInit, "Start"); this.hotfixTypes = this.assembly.GetTypes().ToList(); #endif } } }
-
Assets/Hotfix/Module/Config/ConfigHelper.cs
using System; using ETModel; using UnityEngine; namespace ETHotfix { public static class ConfigHelper { public static string GetText(string key) { try { var asset = ETModel.ComponentFactory.Create<Asset>().LoadAsset("Assets/Bundles/Independent/Config.prefab"); GameObject config = asset.GameObject; //(GameObject)ETModel.Game.Scene.GetComponent<ResourcesComponent>().GetAsset("config.unity3d", "Config"); string configStr = config.Get<TextAsset>(key).text; asset.Dispose(); return configStr; } catch (Exception e) { throw new Exception($"load config file fail, key: {key}", e); } } public static T ToObject<T>(string str) { return JsonHelper.FromJson<T>(str); } } }
-
Init加载配置
// 加载配置 var asset = ComponentFactory.Create<Asset>().LoadAsset("Assets/Bundles/Independent/Config.prefab"); Game.Scene.AddComponent<ConfigComponent>(); asset.Dispose();
至于其他地方,根据报错慢慢查找修改,暂时我只想到上面这些。
备注
XAsset不再需要关注Bundle,所以在资源加载时无需加载Bundle,直接加载Asset即可