返回

ET融合XAsset

本方案用于在ET5.0框架中融入XAsset6.1.1

ET版本5.0(6.0还未尝试,等6.0正式发布了再融合)

XAsset版本6.1.1(6.0+版本都可参考)

在进行代码修改前,请弄明白XAsset示例代码中加载资源与版本更新代码的运行机制,否则修改过程中遇到问题可能无法理解

准备阶段

下载XAsset的源码或Dll,将运行时和编辑器代码分别放入Editor文件夹与ThirdParty文件夹中。

删除ET原有代码

  1. 删除Assets/Editor/BuildEditor目录下BuildEditor脚本BuildHelper脚本
  2. 删除Assets/Model/Module/AssetsBundle目录下AssetsBundleLoaderAsyncAssetsLaderAsyncVersionConfig脚本

增加代码

  1. Assets/Model/Module/AssetsBundle目录下新增脚本Asset

    using 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;
                }
            }
    
        }
    }
    

修改代码

  1. 修改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);
                }
            }
        }
    }
    
    
  2. 修改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);
                }
            }
        }
    }
    
    
  3. 分别修改Assets/Model/Module/UIAssets/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的加载卸载案例

  1. 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);
            }
        }
    }
    
  2. 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
            }
        }
    }
    
  3. 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);
            }
        }
    }
    
  4. Init加载配置

                    // 加载配置
                    var asset = ComponentFactory.Create<Asset>().LoadAsset("Assets/Bundles/Independent/Config.prefab");
                    Game.Scene.AddComponent<ConfigComponent>();
                    asset.Dispose();
    

至于其他地方,根据报错慢慢查找修改,暂时我只想到上面这些。

备注

XAsset不再需要关注Bundle,所以在资源加载时无需加载Bundle,直接加载Asset即可

Licensed under CC BY-NC-SA 4.0
0