[游戏开发][Unity]Assetbundle打包篇(5)使用Manifest二次构建资源索引

这篇具有很好参考价值的文章主要介绍了[游戏开发][Unity]Assetbundle打包篇(5)使用Manifest二次构建资源索引。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

打包与资源加载框架目录

正文

正文开始前,先把打包代码放过来,请注意,前面的代码已省略,自己去对比前面的文章。本篇文章从第一次执行打包代码开始。

public void PostAssetBuild()
{
    //前面的代码省略,和上一篇文章一致

    Log($"开始构建......");
    BuildAssetBundleOptions opt = MakeBuildOptions();
    AssetBundleManifest buildManifest = BuildPipeline.BuildAssetBundles(OutputPath, buildInfoList.ToArray(), opt, BuildTarget);
    if (buildManifest == null)
        throw new Exception("[BuildPatch] 构建过程中发生错误!");

    //本篇的代码从这开始==============================================
    // 清单列表
    string[] allAssetBundles = buildManifest.GetAllAssetBundles();
    Log($"资产清单里总共有{allAssetBundles.Length}个资产");

    //create res manifest
    var resManifest = CreateResManifest(buildMap, buildManifest);
    var manifestAssetInfo = new AssetInfo(AssetDatabase.GetAssetPath(resManifest));
    var label = "Assets/Manifest";
    manifestAssetInfo.ReadableLabel = label;
    manifestAssetInfo.AssetBundleVariant = PatchDefine.AssetBundleDefaultVariant;
    manifestAssetInfo.AssetBundleLabel = HashUtility.BytesMD5(Encoding.UTF8.GetBytes(label));
    var manifestBundleName = $"{manifestAssetInfo.AssetBundleLabel}.{manifestAssetInfo.AssetBundleVariant}".ToLower();
    _labelToAssets.Add(manifestBundleName, new List<AssetInfo>() { manifestAssetInfo });

    //build ResManifest bundle
    buildInfoList.Clear();                
    buildInfoList.Add(new AssetBundleBuild()
    {
        assetBundleName = manifestAssetInfo.AssetBundleLabel,
        assetBundleVariant = manifestAssetInfo.AssetBundleVariant,
        assetNames = new[] { manifestAssetInfo.AssetPath }
    });
    var resbuildManifest = BuildPipeline.BuildAssetBundles(OutputPath, buildInfoList.ToArray(), opt, BuildTarget);
    //加密代码省略,后面文章讲解
}

第一次调用BuildPipeline.BuildAssetBundles打包API后(详见代码第七行),会返回AssetBundleManifest的引用,

【疑问】:BuildPipeline.BuildAssetBundles打包API已经帮我们创建好了AB包之间的依赖关系引用了,为何还要创建AB包的引用关系?

【解答】:BuildPipeline.BuildAssetBundles打包API执行完生成的UnityManifest.manifest文件记录了所有AB包信息以及依赖关系,但是!企业级项目打包是要考虑增量打包的,因此我们想要知道每个AB是哪个版本打出的,需要一个标记,比如记录该AB包是从SVN 某某某阶段打出来的。因此打包接口生成的UnityManifest.manifest文件是个半成品。


下面开始正式介绍对UnityManifest.manifest文件的二次加工

string[] allAssetBundles = buildManifest.GetAllAssetBundles();拿到allAssetBundles再使用CreateResManifest方法创建一个Unity的Asset文件,把UnityManifest.manifest内为数不多的数据都序列化到该asset文件内。asset的序列化脚本是ResManifes,如下图

unity manifest,开发心得,项目管理,unity,游戏开发,Powered by 金山文档

UnityManifest.manifest文件的二次加工代码如下:

//assetList在前面的打包代码里有
//buildManifest第一次打包API返回的文件
private ResManifest CreateResManifest(List<AssetInfo> assetList , AssetBundleManifest buildManifest)
{
    string[] bundles = buildManifest.GetAllAssetBundles();
    var bundleToId = new Dictionary<string, int>();
    for (int i = 0; i < bundles.Length; i++)
    {
        bundleToId[bundles[i]] = i;
    }

    var bundleList = new List<BundleInfo>();
    for (int i = 0; i < bundles.Length; i++)
    {                
        var bundle = bundles[i];
        var deps = buildManifest.GetAllDependencies(bundle);
        var hash = buildManifest.GetAssetBundleHash(bundle).ToString();

        var encryptMethod = ResolveEncryptRule(bundle);
        bundleList.Add(new BundleInfo()
        {
            Name = bundle,
            Deps = Array.ConvertAll(deps, _ => bundleToId[_]),
            Hash = hash,
            EncryptMethod = encryptMethod
        });
    }

    var assetRefs = new List<AssetRef>();
    var dirs = new List<string>();
    foreach (var assetInfo in assetList)
    {
        if (!assetInfo.IsCollectAsset) continue;
        var dir = Path.GetDirectoryName(assetInfo.AssetPath).Replace("\\", "/");
        CollectionSettingData.ApplyReplaceRules(ref dir);
        var foundIdx = dirs.FindIndex(_ => _.Equals(dir));
        if (foundIdx == -1)
        {
            dirs.Add(dir);
            foundIdx = dirs.Count - 1;
        }

        var nameStr = $"{assetInfo.AssetBundleLabel}.{assetInfo.AssetBundleVariant}".ToLower();
        assetRefs.Add(new AssetRef()
        {
            Name = Path.GetFileNameWithoutExtension(assetInfo.AssetPath),
            BundleId = bundleToId[$"{assetInfo.AssetBundleLabel}.{assetInfo.AssetBundleVariant}".ToLower()],
            DirIdx = foundIdx
        });
    }

    var resManifest = GetResManifest();
    resManifest.Dirs = dirs.ToArray();
    resManifest.Bundles = bundleList.ToArray();
    resManifest.AssetRefs = assetRefs.ToArray();
    EditorUtility.SetDirty(resManifest);
    AssetDatabase.SaveAssets();
    AssetDatabase.Refresh();

    return resManifest;
}
unity manifest,开发心得,项目管理,unity,游戏开发,Powered by 金山文档

下面是序列化数据的代码:

 /// <summary>
    /// design based on Google.Android.AppBundle AssetPackDeliveryMode
    /// </summary>
    [Serializable]
    public enum EAssetDeliveryMode
    {
        // ===> AssetPackDeliveryMode.InstallTime
        Main = 1,
        // ====> AssetPackDeliveryMode.FastFollow
        FastFollow = 2,
        // ====> AssetPackDeliveryMode.OnDemand
        OnDemand = 3
    }

    /// <summary>
    /// AssetBundle打包位置
    /// </summary>
    [Serializable]
    public enum EBundlePos
    {
        /// <summary>
        /// 普通
        /// </summary>
        normal,
        
        /// <summary>
        /// 在安装包内
        /// </summary>
        buildin,

        /// <summary>
        /// 游戏内下载
        /// </summary>
        ingame,
    }

    [Serializable]
    public enum EEncryptMethod
    {
        None = 0,
        Quick, //padding header
        Simple, 
        X, //xor
        QuickX //partial xor
    }

    [Serializable]
    [ReadOnly]
    public struct AssetRef
    {
        [ReadOnly, EnableGUI]
        public string Name;

        [ReadOnly, EnableGUI]
        public int BundleId;

        [ReadOnly, EnableGUI]
        public int DirIdx;
    }

    [Serializable]
    public enum ELoadMode
    {
        None,
        LoadFromStreaming,
        LoadFromCache,
        LoadFromRemote,
    }
     

    [Serializable]
    public struct BundleInfo
    {
        [ReadOnly, EnableGUI]
        public string Name;

        [ReadOnly, EnableGUI]
        [ListDrawerSettings(Expanded=false)]
        public int[] Deps;

        [ReadOnly]
        public string Hash;

        [ReadOnly]
        public EEncryptMethod EncryptMethod;
        
        // public ELoadMode LoadMode;
    }
    
    public class ResManifest : ScriptableObject
    {
        [ReadOnly, EnableGUI]
        public string[] Dirs = new string[0];
        [ListDrawerSettings(IsReadOnly = true)]
        public AssetRef[] AssetRefs = new AssetRef[0];
        [ListDrawerSettings(IsReadOnly = true)]
        public BundleInfo[] Bundles = new BundleInfo[0];
    }
}

看图就可知,CreateResManifest方法就是创建了一套属于我们自己的,资源与AB包索引关系。

ResManifes序列化(代码在下面)文件存储了3类数据,

  1. 所有资源文件夹List

  1. 资源所在的AB包List编号、资源所在文件夹List编号

  1. AB包的Name、依赖包名字、版本号MD5,使用加密类型。


【疑问】:为何要序列化这个asset文件?

回答问题之前,先提出一个问题:资源加载肯定是给开发人员用的,开发人员要如何找到想要的资源在哪个ab包里?

【解答】:项目启动的时候,我们要使用这个asset文件去创建所有资源的一个引用信息,项目启动后是要加载这个asset,加载代码如下。

protected virtual ResManifest LoadResManifest()
{
    string label = "Assets/Manifest";
    var manifestBundleName = $"{HashUtility.BytesMD5(Encoding.UTF8.GetBytes(label))}.unity3d";
    string loadPath = GetAssetBundleLoadPath(manifestBundleName);
    var offset = AssetSystem.DecryptServices.GetDecryptOffset(manifestBundleName);
    var usingFileSystem = GetLocation(loadPath) == AssetLocation.App 
        ? FileSystemManagerBase.Instance.MainVFS 
        : FileSystemManagerBase.Instance.GetSandboxFileSystem(PatchDefine.MainPackKey);
    if (usingFileSystem != null)
    {
        offset += usingFileSystem.GetBundleContentOffset(manifestBundleName);
    }
    
    AssetBundle bundle = AssetBundle.LoadFromFile(loadPath, 0, offset);
    if (bundle == null)
        throw new Exception("Cannot load ResManifest bundle");

    var manifest = bundle.LoadAsset<ResManifest>("Assets/Manifest.asset");
    if (manifest == null)
        throw new Exception("Cannot load Assets/Manifest.asset asset");

    for (var i = 0; i < manifest.Dirs.Length; i++)
    {
        var dir = manifest.Dirs[i];
        _dirToIds[dir] = i;
    }

    for (var i = 0; i < manifest.Bundles.Length; i++)
    {
        var info = manifest.Bundles[i];
        _bundleMap[info.Name] = i;
    }

    foreach (var assetRef in manifest.AssetRefs)
    {
        var path = StringFormat.Format("{0}/{1}", manifest.Dirs[assetRef.DirIdx], assetRef.Name);
        // MotionLog.Log(ELogLevel.Log, $"path is {path}");
        if (!_assetToBundleMap.TryGetValue(assetRef.DirIdx, out var assetNameToBundleId))
        {
            assetNameToBundleId = new Dictionary<string, int>();
            _assetToBundleMap.Add(assetRef.DirIdx, assetNameToBundleId);
        }
        assetNameToBundleId.Add(assetRef.Name, assetRef.BundleId);
    }

    bundle.Unload(false);
    return manifest;
}

看上面代码就知道,这个asset文件也是被打进了bundle里,并且单独一个ab包。再看一下本篇文章的标题:《使用Manifest二次构建资源索引》,那么,这个asset所在的bundle就是本篇文章的核心!!!

讲述一下在项目中开发人员是如何加载资源的,首先,开发人员会调用一个Loader去加载资源,如果是使用AB包加载模式(本地资源加载不讨论),那么一定会传入一个资源路径,和加载成功回调

Loader.Load("Assets/Works/Resource/Sprite/UIBG/bg_lihui",callbackFunction)

//成功后回调
void callbackFunction(资源文件)
{
    //使用资源文件
}

我们知道,项目启动时会加载这个资源索引文件,所以框架当然知道所有资源路径和它引用的AB包名称,因此加载资源时会自然而然的找到对应的AB包,同时资源索引文件还记录了AB包的互相依赖关系,加载目标AB包时,递归加载所有依赖包就好啦。

项目里如何使用这个二次构建的资源索引文件上面已经讲清楚了,下面开始讲如何在项目启动时热更下载所有AB包。


CreatePatchManifestFile方法是创建AB包下载清单,请注意,创建新清单前会先加载老清单,并且对比AB包生成的MD5有没有发生变化,如果没变化,则继续沿用老清单的版本号,举个例子:假设UI_Login预设是在版本1生成的,这次打包时版本2,由于UI_Login在本次打包中对比发现MD5没变化,则UI_Login所在的AB包版本依然写1,其他变化的、以及新添加的资源版本号写2。


        /// <summary>
        /// 1. 创建补丁清单文件到输出目录
        /// params: isInit 创建的是否是包内的补丁清单
        ///         useAAB 创建的是否是aab包使用的补丁清单
        /// </summary>
        private void CreatePatchManifestFile(string[] allAssetBundles, bool isInit = false, bool useAAB = false)
        {
            // 加载旧文件
            PatchManifest patchManifest = LoadPatchManifestFile(isInit);

            // 删除旧文件
            string filePath = OutputPath + $"/{PatchDefine.PatchManifestFileName}";
            if (isInit)
                filePath = OutputPath + $"/{PatchDefine.InitManifestFileName}";
            if (File.Exists(filePath))
                File.Delete(filePath);

            // 创建新文件
            Log($"创建补丁清单文件:{filePath}");
            var sb = new StringBuilder();
            using (FileStream fs = File.OpenWrite(filePath))
            {
                using (var bw = new BinaryWriter(fs))
                {
                    // 写入强更版本信息
                    //bw.Write(GameVersion.Version);
                    //sb.AppendLine(GameVersion.Version.ToString());
                    int ver = BuildVersion;
                    // 写入版本信息
                    // if (isReview)
                    // {
                    //     ver = ver * 10;
                    // }
                    bw.Write(ver);
                    sb.AppendLine(ver.ToString());
                    
                    // 写入所有AssetBundle文件的信息
                    var fileCount = allAssetBundles.Length;
                    bw.Write(fileCount);
                    for (var i = 0; i < fileCount; i++)
                    {
                        var assetName = allAssetBundles[i];
                        string path = $"{OutputPath}/{assetName}";
                        string md5 = HashUtility.FileMD5(path);
                        long sizeKB = EditorTools.GetFileSize(path) / 1024;
                        int version = BuildVersion;
                        EBundlePos tag = EBundlePos.buildin;
                        string readableLabel = "undefined";
                        if (_labelToAssets.TryGetValue(assetName, out var list))
                        {
                            readableLabel = list[0].ReadableLabel;
                        if (useAAB)
                            tag = list[0].bundlePos;
                        }

                        // 注意:如果文件没有变化使用旧版本号
                        PatchElement element;
                        if (patchManifest.Elements.TryGetValue(assetName, out element))
                        {
                            if (element.MD5 == md5)
                                version = element.Version;
                        }
                        var curEle = new PatchElement(assetName, md5, version, sizeKB, tag.ToString(), isInit);
                        curEle.Serialize(bw);
                        
                        
                        if (isInit)
                            sb.AppendLine($"{assetName}={readableLabel}={md5}={sizeKB}={version}={tag.ToString()}");
                        else
                            sb.AppendLine($"{assetName}={readableLabel}={md5}={sizeKB}={version}");
                    }
                }

                string txtName = "PatchManifest.txt";
                if (isInit)
                    txtName = "InitManifest.txt";
                File.WriteAllText(OutputPath + "/" + txtName, sb.ToString());
                Debug.Log($"{OutputPath}/{txtName} OK");
            }
        }

生成的AB包清单长下面这个样子。

unity manifest,开发心得,项目管理,unity,游戏开发,Powered by 金山文档

第一行是SVN版本号

第二行是AB包数量

从第三行开始是资源包信息,以=号分割开有效数据,分别是

MD5.unity3d = 资源路径 = 资源路径的HashId = 包体KB大小 = SVN版本号 = 启动热更模式

最终把这个InitManifest.txt写成bytes,传到服务器就可以对比数据包了

本系列文章加载篇我会正式的讲解AB包的加载,本文只是简单介绍一下。

第一步:

当客户端启动后,进入下载清单状态机, Http先下载InitManifest.txt或者InitManifest.bytes文件,并解析AB包清单。

下面是解析AB包清单的代码。


    public class PatchElement
    {
        /// <summary>
        /// 文件名称
        /// </summary>
        public string Name { private set; get; }

        /// <summary>
        /// 文件MD5
        /// </summary>
        public string MD5 { private set; get; }

        /// <summary>
        /// 文件版本
        /// </summary>
        public int Version { private set; get; }

        /// <summary>
        /// 文件大小
        /// </summary>
        public long SizeKB { private set; get; }

        /// <summary>
        /// 构建类型
        /// buildin 在安装包中
        /// ingame  游戏中下载
        /// </summary>
        public string Tag { private set; get; }

        /// <summary>
        /// 是否是安装包内的Patch
        /// </summary>
        public bool IsInit { private set; get; }

        /// <summary>
        /// 下载文件的保存路径
        /// </summary>
        public string SavePath;

        /// <summary>
        /// 每次更新都会先下载到Sandbox_Temp目录,防止下到一半重启导致逻辑不一致报错
        /// temp目录下的文件在重新进入更新流程时先校验md5看是否要跳过下载
        /// </summary>
        public bool SkipDownload { get; set; }


        public PatchElement(string name, string md5, int version, long sizeKB, string tag, bool isInit = false)
        {
            Name = name;
            MD5 = md5;
            Version = version;
            SizeKB = sizeKB;
            Tag = tag;
            IsInit = isInit;
            SkipDownload = false;
        }

        public void Serialize(BinaryWriter bw)
        {
            bw.Write(Name);
            bw.Write(MD5);
            bw.Write(SizeKB);
            bw.Write(Version);
            if (IsInit)
                bw.Write(Tag);
        }

        public static PatchElement Deserialize(BinaryReader br, bool isInit = false)
        {
            var name = br.ReadString();
            var md5 = br.ReadString();
            var sizeKb = br.ReadInt64();
            var version = br.ReadInt32();
            var tag = EBundlePos.buildin.ToString();
            if (isInit)
                tag = br.ReadString();
            return new PatchElement(name, md5, version, sizeKb, tag, isInit);
        }
    }

第二步:

请注意,中断续传也是个很重要的功能,AB包清单记录了每个AB包的大小,当项目启动时,优先遍历Temp文件夹内的AB包,如果大小和清单内的不一致,则开启Http的下载功能,Http是支持断点续传的,Http的Header里定义要下载的数据段。如果你觉得这样不保险,可以直接删掉这个AB包重新下载。

AB包清单解析完后,切换到下载清单状态机,开启清单的每一个文件下载,请注意,热更下载文件时,我们可以先创建一个Temp文件夹,未全部下载成功前的AB包都在这里,全部下载成功后,再全部剪切到PersistentData文件夹内,PersistentData文件夹是Unity内置的沙盒目录,Unity有读写权限。

全部下载完成后,完成PersistentData文件夹剪切工作。

第三步:

全部资源已就绪,启动正式业务框架。

疑问:为何在热更完后再启动正式业务框架?

目前大多数商业项目都是Tolua、Xlua框架,很多框架层代码都是写到Lua中去的,Lua代码属于AB包的一部分,因此只能等热更完后启动。文章来源地址https://www.toymoban.com/news/detail-772636.html

到了这里,关于[游戏开发][Unity]Assetbundle打包篇(5)使用Manifest二次构建资源索引的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • [游戏开发][Unity]Assetbundle下载篇(7)获取运行时(边玩边下)下载列表

    打包与资源加载框架目录 啥是运行时下载清单?现在大多数手游都会有一个边玩边下功能,会提示用户是否开启下载,要XXX流量,如果你下载完了,可能还会有奖励。   疑问:为何要有边玩边下功能? 解答:为了发包的包体小,并且是越小越好! 我们的游戏想要赚钱,第一

    2024年02月08日
    浏览(45)
  • [游戏开发][Unity]Assetbundle下载篇(1)热更前准备与下载AB包清单

    打包与资源加载框架目录 热更流程都不是固定的,每个人写的状态机也有所差别,但是一些必要步骤肯定不可能少,例如下载清单,对比版本,下载AB包,标记下载完成。我接下来的每一篇文章都是下载AB包的重要步骤,大概率是不能省略的。 检查沙盒路径是否存在 检查下载

    2024年02月15日
    浏览(39)
  • [游戏开发][Unity]Assetbundle加载篇(4)检查断点续传以及开始下载AB包

    打包与资源加载框架目录 下 载 AB包之前,要检查该AB包是否下载中断过,例如用户杀程序,卡死等情况。 前文有讲解过,下载AB包会先下载到临时文件夹,全部下载成功后,全部剪切到persistentDataPath沙盒目录中。 回顾一下之前的筛选机制,哪些AB包可以加入下载列表,其中并

    2024年02月08日
    浏览(47)
  • [游戏开发][Unity]Assetbundle下载篇(4)检查断点续传以及开始下载AB包

    打包与资源加载框架目录 下 载 AB包之前,要检查该AB包是否下载中断过,例如用户杀程序,卡死等情况。 前文有讲解过,下载AB包会先下载到临时文件夹,全部下载成功后,全部剪切到persistentDataPath沙盒目录中。 回顾一下之前的筛选机制,哪些AB包可以加入下载列表,其中并

    2024年02月09日
    浏览(46)
  • Unity-AssetBundle Browser 打包和加载

    进入unity手册中,搜素 Unity Asset Bundle Browser 工具,选择你使用的版本号,根据显示出的操作进行资源导入即可。 注:有的版本可能没有资源链接,可以百度一下有很多。 1、制作预制体(省略)  2、选择一个预制体,在Inspector窗口下方,设置预制体的ab包名(包名只能为小写字

    2024年04月13日
    浏览(31)
  • Unity引擎打包AssetBundle后模型网格数据丢失问题

    大家好,我是阿赵。 在项目里面,有时候会遇到这样一个问题。在Unity编辑器里面编写了一个shader,在编辑器看,效果都是没有问题的。但如果把资源打包成AssetBundle后,会发现shader的表现不对了。遇到了这种问题的时候,一般人会各自的怀疑,怀疑是不是shader写得不对,导

    2024年02月07日
    浏览(48)
  • Unity 解决SpriteAtlas图集打包AssetBundle白图问题

    之前文档上说勾选了Include in build之后,就不需要自己增加一个Binding脚本,但是仍然会弹出警告,并且会出现白图现象 Tight Packing 勾选之后当Sprite有透明通道可能会出现错位 不推荐勾选 选项取消勾选之后打包AssetBundle会报警告 并且出现白图现象 那是因为没有进行bing  Sprite

    2024年02月11日
    浏览(41)
  • Unity AssetBundle批量打包、加载(场景、Prefab)完整流程

    1、文章介绍 2、具体思路和写法         (1)AB包的打包         (2)AB包的加载         (3)AB包卸载 3、结语 本篇博客主要起记录和学习作用,简单的介绍一下AB包批量的打包和加载AB包的方式,若各位同学有幸看到本篇博客,希望能够对你有所帮助。 (1)

    2024年02月04日
    浏览(39)
  • 【Unity 3D】AssetBundle打包、上传、加载、卸载详解及演示(附源码)

    需要源码及依赖请点赞关注收藏后评论区留言私信~~~ AssetBundle最重要的操作就是AssetBundle打包 下载 加载以及卸载 下面就来了解一下AssetBundle的常用操作 AssetBundle打包主要用到下面两个API BuildAssetBundles(string outputPath,AssetBundleBuild[] builds, BuildAssetBundleOptions assetBundleOptions,BuildTarg

    2024年02月10日
    浏览(33)
  • Unity3D打包Assetbundle丢失Shader问题

    详情见:https://www.pianshen.com/article/5391338163/ 1、Unity3D在打包Assetbundle时,可能会遇到Shader丢失的问题,解决方法:打开 Edit-Project Settings-Graphics,在Always Included Shaders列表添加上所需的shader ,再打包即可。 2、参考 3、原文复制 Scriptable RenderLoop settings This is an experimental setting wh

    2023年04月22日
    浏览(57)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包