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;

/// <summary>
/// 构建工具类:
/// 创建了三种构建方法windows android ios。
/// Build方法:从所有路径查找文件,排除meta后,把每个文件名作为被打包资源名和bundle名(当然一个bundle可以打包多个文件),GetDependence获取所有文件的依赖文件,
/// 把这些信息写入到bundleInfos,BuildPipeline.BuildAssetBundles一下,就建好了ab包,然后把所有包的信息写入filelist中
/// </summary>
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);
    }
}

为什么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

/// <summary>
/// 路径工具类:
/// 1定义了所有用到的路径
/// 2返回标准路径或返回unity下的几个文件夹的相对路径
/// </summary>
//因为所有路径都要用到,所以写入一个只读变量中,用来后期访问
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;

    /// <summary>
    /// 获取Unity的相对路径
    /// </summary>
    /// <param name="path">绝对路径</param>
    /// <returns></returns>
    public static string GetUnityPath(string path)
    {
    
    
        if(string.IsNullOrEmpty(path))
        {
    
    
            return string.Empty;
        }
        //从Assets位置拿到相对目录
        return path.Substring(path.IndexOf("Assets"));
    }

    /// <summary>
    /// 获取标准路径
    /// </summary>
    /// <param name="path">路径</param>
    /// <returns></returns>
    public static string GetStandardPath(string path)
    {
    
    
        if (string.IsNullOrEmpty(path))
        {
    
    
            return string.Empty;
        }
        //先处理空格,在处理反斜杠
        return path.Trim().Replace("\\", "/");
    }
}

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

public class AppConst
{
    
    
    public const string BundleExtension = ".ab";
    public const string FileListName = "filelist.txt";
}
//为了能够构建多平台,需要把目标平台作为参数传入。
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();
}

/// <summary>
/// 获取依赖文件列表
/// </summary>
/// <param name="curFile">需要获取依赖的文件</param>
/// <returns></returns>
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;
}

image.png

猜你喜欢

转载自blog.csdn.net/weixin_42264818/article/details/128210980
今日推荐