• Unity Xlua热更新框架(二):构建AssetBundle


    2. Bundle构建工具

    框架开发流程,,,热更新最重要的是Bundle,所以Bundle处理放在最前面,,后面二三阶段是C#与Lua的交互

    • 第一阶段:Bundle处理
      • 构建
      • 加载
      • 更新
    • 第二阶段:C#调用Lua
      • Lua脚本的加载与内存的管理
      • Lua脚本的逻辑绑定与执行(Lua是独立的脚本,需要绑定到实体对象上,每一个功能单独写一个Lua脚本,例如一个UI,有对应UI的操作,UI的逻辑就全部写到Lua脚本里,把Lua脚本绑定到UI上)
    • 第三阶段:向Lua提供接口(逻辑在Lua开发,有的东西不适合在Lua写,写进框架,后期提供接口)
    • 第四阶段:完善和优化

    环境安装

    开发工具:Unity、VS、Git、TortoiseGit
    导入Xlua:官方下载地址:https://github.com/Tencent/xLua
    image.png
    XLua的目录就是Unity的目录,只需要把Assets下面的Plugins和Xlua文件夹复制进Unity项目中即可
    构建Bundle需要做什么?

    • 查找BuildResources下的资源文件(根据策略模式打成Bundle包)
    • 使用Unity提供的BuildPipeline进行构建(直接使用这个就可以打Bundle包)

    meta不需要打入bundle包
    Bundle Build策略?

    • 按文件夹打包image.png,按照文件夹细分功能,根据最后一层构建Bundle
      • 优势:bundle数量少,小包模式(第一次安装包小,没有热更资源,CDN下载热更文件):首次下载快
      • 劣势:后期更新时,更新补丁大(例如一个图片bundle 5M,实际里面就一个小图片修改了,导致整个热更补丁大)
    • 按文件打包image.png,例如把Image变成预设文件,每一个文件都要构建Bundle
      • 优势:更新补丁很小,每一个文件都是一个Bundle包
      • 劣势:小包模式:首次下载稍慢(也不会慢特别多,,两者区别不大,这个更简单)

    打Bundle的工具是编辑器的代码,放到Scripts-Editor中,其他脚本放到Framework中image.png
    创建BuildTool脚本image.png,BuildTool必须继承Editor,而非Mono,因为不用打进包里

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEditor;
    using System.IO;
    
    /// 
    /// 构建工具类:
    /// 创建了三种构建方法windows android ios。
    /// Build方法:从所有路径查找文件,排除meta后,把每个文件名作为被打包资源名和bundle名(当然一个bundle可以打包多个文件),GetDependence获取所有文件的依赖文件,
    /// 把这些信息写入到bundleInfos,BuildPipeline.BuildAssetBundles一下,就建好了ab包,然后把所有包的信息写入filelist中
    /// 
    public class BuildTool : Editor
    {
        //如何使用Build呢,直接添加工具栏
        [MenuItem("Tools/Build Windows Bundle")]
        static void BundleWindowsBuild()
        {
            Build(BuildTarget.StandaloneWindows);
        }
    
        //如何使用Build呢,直接添加工具栏
        [MenuItem("Tools/Build Android Bundle")]
        static void BundleAndroidBuild()
        {
            Build(BuildTarget.Android);
        }
    
        //如何使用Build呢,直接添加工具栏
        [MenuItem("Tools/Build IOS Bundle")]
        static void BundleIOSBuild()
        {
            Build(BuildTarget.iOS);
        }
    
        //为了能够构建多平台,需要把目标平台作为参数传入。
        static void Build(BuildTarget target)
        {
            //主要目的是收集这个build信息,需要打哪些文件,需要给bundle包用一个什么样的名字,BuildAssetBundles函数用到这个Build数组
            List<AssetBundleBuild> assetBundleBuilds = new List<AssetBundleBuild>();
    
            //第一步搜索出我们这个所有文件的文件名Directory.GetDirectories和Directory.GetFiles对应两种打包策略一个获取文件夹一个获取文件,GetFiles比较简单
            //searchPattern通配符,*是默认 https://www.cnblogs.com/ost/archive/2006/08/20/481625.html
            string[] files = Directory.GetFiles(PathUtil.BuildResourcesPath, "*", SearchOption.AllDirectories);
            //所有文件都找出来了,需要排除调meta文件和json文件
            for (int i = 0; i < files.Length; i++)
            {
                if (files[i].EndsWith(".meta") || files[i].EndsWith(".json"))
                { 
                    continue; 
                }
                //创建一个需要build的Bundle
                AssetBundleBuild assetBundle = new AssetBundleBuild();
    
                //处理出来的路径斜杠可能不同。需要规范一下
                string fileName = PathUtil.GetStandardPath(files[i]);
    
                string assetName = PathUtil.GetUnityPath(fileName);//获取unity相对路径
    			//一个assetBundle可以打包多个文件,这里只放一个文件
                assetBundle.assetNames = new string[] { assetName };//assetBundle是一个相对路径文件名
    
    			//创建包名
                string bundleName = fileName.Replace(PathUtil.BuildResourcesPath, "").ToLower();
                assetBundle.assetBundleName = bundleName + ".ab";//Bundle需要后缀是.ab,,,,,,,,至此,Bundle的信息收集完了,需要放进list
    
                assetBundleBuilds.Add(assetBundle);
            }
    
            //为什么不用另一个重载BuildAssetBundles(string outputPath, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform),是因为需要自己去资源设置bundle名打标签,很麻烦
            //第二个参数把list转为array数组
            //第三个参数是压缩格式,选择默认
            //第四个参数是目标平台,先选择win
            if(Directory.Exists(PathUtil.BundleOutPath))
            {
                //判断是否有路径,如果有这个文件夹,就删掉文件,,递归recursive删掉所有文件和子文件。
                Directory.Delete(PathUtil.BundleOutPath, true);
            }
            Directory.CreateDirectory(PathUtil.BundleOutPath);//删除路径后,创建路径
    
            BuildPipeline.BuildAssetBundles(PathUtil.BundleOutPath, assetBundleBuilds.ToArray(), BuildAssetBundleOptions.None, target);
        }
    }
    
    • 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

    为什么48行要排除json格式?因为vscode可以通过Settings.json文件添加一下几行代码让vscode识别到.bytes格式的lua文件并且排除.meta文件,但是这个json文件打包成ab包后,解ab包读取过程json读不出来,因此,直接排除掉json文件即可
    https://blog.csdn.net/weixin_42264818/article/details/127442751
    :::info
    {
    “files.associations”: {
    .bytes": “lua”
    },
    “files.exclude”: {
    "
    .meta”: true
    },
    }
    :::

    创建特定的工具类,用来管理路径PathUtil

    /// 
    /// 路径工具类:
    /// 1定义了所有用到的路径
    /// 2返回标准路径或返回unity下的几个文件夹的相对路径
    /// 
    //因为所有路径都要用到,所以写入一个只读变量中,用来后期访问
    public class PathUtil
    {
        //为什么要把Application定义出来,因为每一次访问都需要GC一次,定义出来就访问一次;
        public static readonly string AssetPath = Application.dataPath;
    
        //只读的,需要打Bundle的目录
        public static readonly string BuildResourcesPath = AssetPath + "/BuildResources/";
    
        //Bundle输出目录
        public static readonly string BundleOutPath = Application.streamingAssetsPath;
    
        /// 
        /// 获取Unity的相对路径
        /// 
        /// 绝对路径
        /// 
        public static string GetUnityPath(string path)
        {
            if(string.IsNullOrEmpty(path))
            {
                return string.Empty;
            }
            //从Assets位置拿到相对目录
            return path.Substring(path.IndexOf("Assets"));
        }
    
        /// 
        /// 获取标准路径
        /// 
        /// 路径
        /// 
        public static string GetStandardPath(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                return string.Empty;
            }
            //先处理空格,在处理反斜杠
            return path.Trim().Replace("\\", "/");
        }
    }
    
    
    • 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

    https://blog.csdn.net/Czhenya/article/details/88181930
    dataPath :返回程序的数据文件所在的文件夹的路径(只读)。返回路径为相对路径,一般是相对于程序安装目录的位置。不同游戏平台的数据文件保存路径不同。
    StreamingAssetsPath: 此属性用于返回数据流的缓存目录,返回路径为相对路径,适合设置一些外部数据文件的路径。(只读)
    PersistentDataPath:返回一个持久化数据存储目录的路径,可以在此路径下存储一些持久化的数据文件。对应同一平台,在不同程序中调用此属性时,其返回值是相同的,但是在不同的运行平台下,其返回值会不一样。
    temporaryCachePath:此属性用于返回一个临时数据的缓冲目录(只读)。对于同一平台,在不同程序中调用此属性时,其返回值是相同的,但是在不同的运行平台下,其返回值是不一样的。
    persistentDataPath
    temporaryCachePath
    的返回值一般是程序所在平台的固定位置,适合程序在运行过程中产生的数据文件。

    点击构建Bundle的自定义工具栏按钮,就可以构建Bundle包了
    image.pngimage.pngimage.png

    3. 完善BuildTool

    使用Gitee推送现有的版本,,,,在XluaFramework路径下面git,把unity那个文件夹下面的文件拖进git生成的本地仓库xlua-framework,以后开发都用这个进行开发
    如果只是加载了ab包,不会把prefab依赖的图片等还原出来,需要处理一下,,意味着所有打了ab包的都需要还原,,如果需要自动加载ab包,需要把依赖包绑定到主ab包作关联
    image.png
    需要使用一个版本文件把Bundle文件的文件信息保存下来。文件信息包括:文件路径名、bundle名、依赖文件列表

    public class Test : MonoBehaviour
    {
        //因为需要异步加载方式,因此改为协程
        IEnumerator Start()
        {
    		//资源包异步创建请求。
            //AssetBundleCreateRequest用于加载bundle文件返回的东西,,,,,加载bundle只需要.ab文件
            AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/ui/prefabs/testui.prefab.ab");
            yield return request;
    
            //手动加载prefab用到的两个图片的ab包,,,,后面有自动加载依赖ab包的方法,,,如果需要自动加载ab包,需要把依赖包绑定到主ab包作关联
            AssetBundleCreateRequest request1 = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/ui/res/menu-background-image.png.ab");
            yield return request1;
            AssetBundleCreateRequest request2 = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/ui/res/button.png.ab");
            yield return request2;
    
    
        	//从一个资源包(AssetBundle)异步加载请求。
            //等待AB包加载完成,去加载指定bundle包内的名字的文件,例如xx.prefab
            AssetBundleRequest bundleRequest = request.assetBundle.LoadAssetAsync("Assets/BuildResources/UI/Prefabs/TestUI.prefab");
            yield return bundleRequest;
    
            //加载完文件,实例化
            GameObject go = Instantiate(bundleRequest.asset) 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
    public class AppConst
    {
        public const string BundleExtension = ".ab";
        public const string FileListName = "filelist.txt";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    //为了能够构建多平台,需要把目标平台作为参数传入。
    static void Build(BuildTarget target)
    {
        ...
            
        //文件信息列表
        List<string> bundleInfos = new List<string>();
        
        for (int i = 0; i < files.Length; i++)
        {
            ...
                
            //添加文件和依赖信息
            List<string> dependenceInfo = GetDependence(assetName);
            //版本信息包括文件路径名、bundle名、依赖文件列表
            string bundleInfo = assetName + "|" + bundleName + ".ab";
    
            if (dependenceInfo.Count > 0)
                bundleInfo = bundleInfo + "|" + string.Join("|", dependenceInfo);
    
            bundleInfos.Add(bundleInfo);
        }
    
        ...
            
        //写bundle信息文件
        File.WriteAllLines(PathUtil.BundleOutPath + "/" + AppConst.FileListName, bundleInfos);
        //创建好文件后,在unity资源库中刷新一下
        AssetDatabase.Refresh();
    }
    
    /// 
    /// 获取依赖文件列表
    /// 
    /// 需要获取依赖的文件
    /// 
    static List<string> GetDependence(string curFile)
    {
        List<string> dependence = new List<string>();
        //把这个文件的依赖文件全部或取出来,,会获取脚本文件和自身文件,,因此需要去掉。
        string[] files = AssetDatabase.GetDependencies(curFile);
        dependence = files.Where(file => !file.EndsWith(".cs") && !file.Equals(curFile)).ToList();
        return dependence;
    }
    
    • 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

    image.png

  • 相关阅读:
    掌握功能优化=学会面试
    ECCV2022|时尚领域的多模态预训练预训练模型FashionViL,在五个下游任务中SOTA!
    创新破万“卷”,但创新自己得“卷”
    在网络安全、爬虫和HTTP协议中的重要性和应用
    测试日常工作中需要具备哪些知识和能力,在需求评审时需要考虑哪些方面,在技术方面评审时需要考虑哪些方面,从什么方面进行设计测试用例
    eclipse中open Type 、 open type in Hierachy、open Resource的区别
    广和通5G AIoT模组引领亮相2022国际物联网展(IOTE),智赋行业数字化新价值
    从单体到微服务:使用Spring Boot构建事件驱动的Java应用程序
    数组18—push() :将一个或多个元素添加到数组的末尾
    第七章 操作位和位串(三)
  • 原文地址:https://blog.csdn.net/weixin_42264818/article/details/128210980