• Unity Xlua热更新框架(三):资源管理


    4. 资源加载

    逻辑:解析版本文件,保存文件信息——>获取文件信息——>加载Bundle(——>检查依赖——>获取依赖文件信息再次递归加载bundle)——>加载资源——>完成后回调
    可能一个bundle需要另一个bundle(而这个有需要别的bundle),需要递归加载。

    必须加载完依赖bundle和自身bundle都,才能加载资源,,,最后在通知应用层加载完成。
    这种异步加载方式,在商业中比较常用。

    C# internal解析_愤怒的YYZ的博客-CSDN博客_c# internal

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using System.IO;
    using System;
    //改名
    using UObject = UnityEngine.Object;
    
    /// 
    /// 资源管理器类:
    /// 定义了所有使用bundle信息可用的bundleinfo类,该类用于加载bundle时使用,,,另外字典m_BundleInfos存放资源名与所保存该资源的bundle信息
    /// ParseVersionFile解析版本文件:获得版本文件中所有要加载的资源名和其所在的bundle信息以及该资源又依赖哪些bundle,,并把lua文件的信息传给luaManager保存
    /// 加载资源:加载前需要解析版本文件,LoadBundleAsync通过m_BundleInfos查找资源递归(因为有的bundle依赖别的bundle)的异步加载bundle资源,或在编辑器模式下使用EditorLoadAsset加载资源
    /// 封装了loadxx的方法,用来加载指定类型资源,例如loadUI,从UI目录加载UI类型资源
    /// 
    public class ResourceManager : MonoBehaviour
    {
        //定义一个类,用来解析版本信息
        internal class BundleInfo
        {
            public string AssetName;
            public string BundleName;
            public List<string> Dependeces;
        }
    
        //存放bundle信息的集合
        private Dictionary<string, BundleInfo> m_BundleInfos = new Dictionary<string, BundleInfo>();
    
        /// 
        /// 解析版本文件
        /// 
        private void ParseVersionFile()
        {
            //拿到版本文件路径
            string url = Path.Combine(PathUtil.BundleResourcePath, AppConst.FileListName);
            //对文件进行读取
            string[] data = File.ReadAllLines(url);
    
            //解析文件信息
            for (int i = 0; i < data.Length; i++)
            {
                BundleInfo bundleInfo = new BundleInfo();
                string[] info = data[i].Split('|');
                bundleInfo.AssetName = info[0];
                bundleInfo.BundleName = info[1];
                //list特性:本质是数组,可动态扩容
                bundleInfo.Dependeces = new List<string>(info.Length - 2);
                for (int j = 2; j < info.Length; j++)
                {
                    bundleInfo.Dependeces.Add(info[j]);
                }
                m_BundleInfos.Add(bundleInfo.AssetName, bundleInfo);
            }
        }
    
        //异步加载bundle,使用回调通知应用层加载完毕,,,需要通过Action回调把加载好的Object返回回去
        //Object在UnityEngine和System会重名,使用using改名UObject
        /// 
        /// 异步加载资源
        /// 
        /// 资源名
        /// 完成回调,默认null,不传值默认为空
        /// 
        IEnumerator LoadBundleAsync(string assetName,Action<UObject> action = null)
        {
            string bundleName = m_BundleInfos[assetName].BundleName;
            //这个是小写的bundle.ab的路径名
            string bundlePath = Path.Combine(PathUtil.BundleResourcePath, bundleName);
            List<string> dependences = m_BundleInfos[assetName].Dependeces;
            if(dependences != null && dependences.Count > 0)
            {
                //递归加载依赖bundle,因为依赖的资源目录名就是bundle资源名
                for (int i = 0; i < dependences.Count; i++)
                {
                    yield return LoadBundleAsync(dependences[i]);
                }
            }
            //创建异步加载bundle申请
            AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(bundlePath);
            yield return request;
    
            //从bundle申请加载指定路径名的文件,例如prefab
            AssetBundleRequest bundleRequest = request.assetBundle.LoadAssetAsync(assetName);
            yield return bundleRequest;
    
            //如果回调和request都不为空,语法糖
            action?.Invoke(bundleRequest?.asset);
        }
    
        //直接上面异步的方法向外面提供接口使用StartCoroutine不方便,提供一个接口
        public void LoadAsset(string assetName, Action<UObject> action)
        {
            StartCoroutine(LoadBundleAsync(assetName, action));
        }
    
        void Start()
        {
            ParseVersionFile();
            LoadAsset("Assets/BuildResources/UI/Prefabs/TestUI.prefab", OnComplete);
        }
    
        private void OnComplete(UObject obj)
        {
            GameObject go = Instantiate(obj) as GameObject;
            go.transform.SetParent(this.transform);
            go.SetActive(true);
            go.transform.localPosition = Vector3.zero;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109

    5. 资源路径规划

    需要把每一个资源按照这个文件夹的类型写一个接口
    image.png

    public static string GetLuaPath(string name)
    {
        return string.Format("Assets/BuildResources/LuaScripts/{0}.bytes", name);
    }
    
    public static string GetUIPath(string name)
    {
        return string.Format("Assets/BuildResources/UI/Prefabs/{0}.prefab", name);
    }
    
    public static string GetMusicPath(string name)
    {
        return string.Format("Assets/BuildResources/Audio/Music/{0}", name);
    }
    
    public static string GetSoundPath(string name)
    {
        return string.Format("Assets/BuildResources/Audio/Sound/{0}", name);
    }
    
    public static string GetEffectPath(string name)
    {
        return string.Format("Assets/BuildResources/Effect/Prefabs/{0}.prefab", name);
    }
    
    public static string GetModelPath(string name)
    {
        return string.Format("Assets/BuildResources/Model/Prefabs/{0}.prefab", name);
    }
    
    //例如图片音乐等,后缀名也要传进来
    public static string GetSpritePath(string name)
    {
        return string.Format("Assets/BuildResources/Sprites/{0}", name);
    }
    
    public static string GetScenePath(string name)
    {
        return string.Format("Assets/BuildResources/Scenes/{0}.unity", name);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    6. 编辑器模式加载资源

    需要让编辑器在不需要打Bundle的时候,直接使用Unity内部的资源加载来测试逻辑。

    #if UNITY_EDITOR
    /// 
    /// 在编辑器环境下使用,虽然是同步加载,但是模拟成前面的异步加载函数
    /// 
    /// 
    /// 
    void EditorLoadAsset(string assetName, Action<UObject> action = null)
    {
        UObject obj = UnityEditor.AssetDatabase.LoadAssetAtPath(assetName, typeof(UObject));
        if(obj = null)
        {
            Debug.LogError("asset name is not exist:" + assetName);
            action?.Invoke(obj);
        }
    }
    #endif
    
    //直接上面异步的方法向外面提供接口使用StartCoroutine不方便,提供一个接口
    private void LoadAsset(string assetName, Action<UObject> action)
    {
        //如果是编辑器模式,就加载Assets/BuildResources/UI/Prefabs/TestUI.prefab
    #if UNITY_EDITOR
        if (AppConst.GameMode == GameMode.EditorMode)
            EditorLoadAsset(assetName, action);
        else    //否则加载StreamingAssets/ui/prefabs/testui.prefab.ab
    #endif
            StartCoroutine(LoadBundleAsync(assetName, action));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    :::info
    GameMode主要是ResourceManager和LuaManager运行时用来加载文件进行判断当前的运行模式,但是只判断EditorMode和else(PackageMode和UpdateMode),因为两者的加载方法不一样,前者从文件路径直接加载,后者需要从ab包加载。。第三处用到GameMode的是PathUtil的BundleResourcePath属性,需要根据到底是PackageMode还是UpdateMode返回到底是只读目录还是可读写目录。
    :::

    public enum GameMode
    {
        //编辑器模式,包模式,更新模式
        EditorMode,
        PackageBundle,
        UpdateMode,
    }
    
    public class AppConst
    {
        public const string BundleExtension = ".ab";
        public const string FileListName = "filelist.txt";
    	//为什么不写成const?因为要在inspector中手动修改状态,而不能每次进来修改代码
        public static GameMode GameMode = GameMode.EditorMode;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    Hierarchy中添加框架的入口(空物体)root,添加新脚本GameStart

    public class GameStart : MonoBehaviour
    {
        public GameMode GameMode;
        // Start is called before the first frame update
        void Awake()
        {
            AppConst.GameMode = this.GameMode;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Unity 出现error CS0103: The name ‘AssetDatabase‘ does not exist in the current context_旭God的舔狗的博客-CSDN博客_unity报错cs0103
    问题描述
    在Unity场景中,在进行build操作时出现这种报错,导致资源bundle无法正常生成,出现以下问题:
    error CS0103: The name ‘AssetDatabase’ does not exist in the current context
    error CS0234: The type or namespace name ‘AssetDatabase’ does not exist in the namespace ‘UnityEditor’ (are you missing an assembly reference?)
    ps:上面两种错误都是同一种问题造成的,报错不一样的原因是由于UnityEditor在代码中的位置不同造成的:
    前者在开头声明了using UnityEditor,在方法中使用AssetDatabase.LoadAssetAtPath;
    后者未声明using UnityEditor,在方法中使用了UnityEditor.AssetDatabase.LoadAssetAtPath
    原因分析
    在非Editor文件夹下的脚本中,存在着有关UnityEditor方法的使用
    方法中第一行使用了UnityEditor中的AssetDatabase.LoadAssetAtPath方法,并且该方法所在的文件并非是在Editor文件夹下,导致build操作时出现报错
    image.png
    6nld413u.bmp
    解决方案
    添加
    #if UNITY_EDITOR
    #endif
    image.png
    注意
    注:需要把调用该方法的地方也要用#if #endif包括起来
    因为该方法时需要被调用的,然后测试的时候出现了以下问题
    error CS0103: The name ‘EditorLoadAsset’ does not exist in the current context
    出现问题的原因是调用此方法的地方未用#if #endif包含进去,在正式运行状态下,他会认为该方法不存在,找不到该方法导出出现报错。所以要将调用该方法的地方也要用#if #endif包括进来,让正式运行状态下也不用执行调用该方法的语句

  • 相关阅读:
    Redis哨兵(Sentinel)模式的配置方法及其在Java中的用法
    ubuntu 18及以上版本配置IP的方法,你get了吗
    亚商投资顾问 早餐FM/1110我国推进甲烷排放
    马斯克打了个响指,推特50%员工被裁....
    Spring Data JPA 之 Web MVC 开发的支持
    使用百度飞桨 EasyDL 完成垃圾分类
    2021年6月大学英语六级作文
    QStyleFactor和QPalette
    Java-API简析_java.net.Inet6Address类(基于 Latest JDK)(浅析源码)
    云计算赛项容器云2023搭建
  • 原文地址:https://blog.csdn.net/weixin_42264818/article/details/128211097