返回

XAsset6.1.1解析

XAsset学习记录,仅供参考,持续更新

XAsset官网

窗口

菜单栏功能键

相关代码查看编辑器入口类MenuItems

  • Build:打包功能相关
    • Groups:打包分组
    • Bundles:打包资源
    • Manifests:打包清单
    • Player:打包播放器(发布Unity)
    • Copy To StreamingAssets:复制资源到StreamingAssets文件夹
    • Clear:清空打包资源
  • View:查看目录相关
    • Settings:查看打包资源设置
    • Build Path:查看打包资源目录
    • Download Path:查看下载资源目录
    • Temporary:查看临时资源目录
  • Groups:打开Groups窗口(代码在编辑器脚本GroupsWindow中)
  • Compute CRC:查看选中资源的CRC
  • Clear Progress Bar
  • Copy Path:复制选中资源路径

对象

Asset

打包资源的最小粒度

没有实体Unity对象

存储了资源基本的路径,所属Group等。简单理解,我们放入分组中单个目录即为一个Asset

Group

分组

我们在打包时创建的分组对象,手动更新时选择的更新分组等

包含了打包模式,所属打包清单Manifest,所包含的Asset,以及是否包含在包体、是否只读选项

Manifest

打包表单

用于分布式打包选项

包含了表单内所有的Groups,及其内部的所有Asset,该表单所占内存大小,所属打包配置Settings。

Settings

打包配置

包含了版本号,所有打包表单,打包设置选项,编辑器下资源加载模式,启动场景。

其他

以上所有对象,都可以在编辑器代码中的Settings目录下找到

在打包过程中的缓存对象

  • Build

  • BundleBuild

  • AssetBuild

打包环境配置

打开Group窗口

在工具栏下点击Group按钮后,会通过GroupsWindow脚本创建一个Unity窗口,接下来,在该类的OnGUI方法创建一个GroupsEditor对象,并调用该对象的OnGUI方法对窗口进行绘制

GroupsEditor

GroupEditorOnGUI方法中完成了非常重要的两步操作

  1. 创建默认打包配置对象Settings
  2. 创建第一个打包表单对象Manifest
  1. OnGUI中,首先会通过Settings.GetDefaultSettings()方法获取Settings对象,如果该对象不存在,方法则会在指定位置创建一个

  2. 从上一步获取到的Settings对象中获取一个Manifest,如果没有,则会调用CreateManifest方法提示用户创建一个Manifest。待创建完成后调用settings.AddManifest()方法将Manifest保存到Settings中。

后续OnGUI会继续调用DrawTreeDrawToolbar方法,对窗口进行绘制,具体略过,编辑器绘制太麻烦了,下面只挑重点讲

创建Group

该步骤代码见AssetTreeView脚本的ContextClicked方法(由于AssetTreeView继承了**TreeView**,所以实现的ContextClicked方法可以监听窗口内鼠标点击事件)(就这一个类一千多行代码,我人傻了)

点击创建后会调用manifest.AddGroup方法,在指定目录生成一个Group对象。并将Group对象的manifest指定为自己,而后保存到manifest.groups

同样的对GroupRenameRemove操作也在ContextClicked方法中。

拖动目录创建Asset

同创建Group相同,这一步骤也是在AssetTreeView脚本中完成的,使用的是方法HandleDragAndDrop。精确添加Asset调用在HandleDragAndDropPaths方法中的manifest.AddAsset(path, parent);

manifest.AddAsset方法中,调用 Asset.Create方法创建一个Asset,然后添加到Group中,并保存到Manifest的assets中。

打包

构建分组

主要流程概括说分为三项:

  • 刷新Manifest中所有的Asset,防止一些过期的(被删除)资源参与后续流程
  • 处理Asset依赖
  • 创建Auto分组Group

可能到此有疑问,没有提到有关Bundle相关的操作,那么为什么Auto分组里以已经分好资源所属Bundle名。

在XAsset中,Bundle名不在使用Unity打包时的AssetLabels标签。Bundle名是由资源所在的Group所选的打包方式决定的(相关代码在Asset.Bundle属性中),不同的打包方式,决定了该资源返回的Bundle名。

点击按钮后,会执行如下流程

BuildScript.BuildGroups();

  1. 调用Settings.GetDefaultSettings();加载默认的打包资源设置Setting
  2. 遍历Setting中的所有打包清单Manifest
  3. 调用清单的Manifest.BuildGroups()方法进行分组构建

Manifest.BuildGroups()

  1. 调用CollectAssets()方法采集规则Manifest中所有的Asset资源,方法内会对Manifest的所有Asset进行一次清理,重新创建,以保证资源的正确性。返回Dictionary<Path, Asset>

  2. 调用AnalysisDependencies方法处理上一步获取到的Asset资源的依赖(获取文件依赖的核心方法是Asset.GetDependencies())。返回Dictionary<Path, List<Asset>>(遍历Asset的依赖,将非重复的且未添加的依赖保存到字典中返回)

  3. 调用AutoGrouping方法并传入前两步返回的字典,对公共资源依赖进行分组

    • 首先获取或创建名为 Auto 的Group分组,并设置分组的打包方式为分组名+资源打包方式

    • 将前面获取的所有Asset的路径和所属Bundle名,按行写入到Build目录下的auto_assets_dependencies_for_文本文档中。

      这里需要注意,Asset所属Bundle名通过Asset.Bundle属性获取,根据Asset所属打包分组打包方式的不同,所生成名称也不相同

    • 调用AddAsset方法,传入上一步获取的Asset路径,和AutoGroup,创建新的Asset,并标记未Auto,绑定在AutoGroup

    构建分组的任务到这里就结束了,后面是方法中剩余的功能

  4. 调用CreateBundles方法生成新增BundleBuild队列,Out 所有BundleBuild队列。

    • 遍历Manifest.assets,根据AssetBundle名创建BundleBuild对象并保存到Out队列中,并将单个Asset转换成AssetBuild对象,保存到BundleBuild.assets中。
    • 通过GetBuild方法创建Build实例,在GetBuild方法中会反序列化打包目录下的Json文件,然后反序列化为Build实例,也就是说,Build对象持有之前打包的记录信息
    • 调用BuildGetAssets方法,获取之前的生成的AssetBuild信息。然后和当前生成的AssetBuild进行比对,筛选出修改或新增的BundleBuild返回。
  5. 遍历上一步返回的BundleBuild队列,调用其GetBuild方法,获取AssetBundleBuild(参数有assetBundleName(指向BundleBuild.name->Asset.bundle)与assetNames(指向AssetBuild->Asset)),保存到字典中。

    请记住AssetBundleBuild,在后续打包流程中,将使用它作为打包核心参数

  6. 调用BuildRaws方法,传入第4步Out的BundleBuild队列。该步骤针对标记了Raw格式的分组

    • 在方法中对Manifest的所有rawGroup的所有Asset调用了BuildRaw方法
    • BuildRaw方法中计算了单个Asset目标文件的大小和唯一码CRC、以及哈希名,并生成BundleBuild文件添加到队列中
    • 等待所有BuildRaw执行完所有Asset后,将队列中的目标文件,重命名后拷贝到相对打包目录目录
    • 返回Out队列
  7. 返回第5步的字典转换成的Value数组。

构建资源

构建资源主要做了以下流程:

  • 重新构建分组
  • 生成Build对象及其BundleBuild缓存,从中提取出AssetBundleBuild
  • 使用Unity的BuildPipeline.BuildAssetBundles方法和AssetBundleBuild进行打包
  • 持久化Build为Json文件
  • 生成版本控制文件

BuildScript.BuildGroups();

  1. 调用Settings.GetDefaultSettings();加载默认的打包资源设置Setting
  2. 遍历Setting中的所有打包清单Manifest,调用BuildBundles方法传入Manifest

BuildScript.BuildBundles(Manifest manifest);

  1. 执行构建分组时的Manifest.BuildGroups()方法,不同的是,会将方法全部执行完,返回AssetBundleBuild[] buildsList<BundleBuild> bundleBuildsList<BundleBuild> rawBundleBuilds

  2. 根据返回的builds数量是否为0,分为两种情况。

    >0

    1. 准备打包所需:当前manifest的路径,Unity的Build设置,打包资源输出目录,BuildAssetBundleOptions(注意:这里手动添加了向 assetBundle 名称附加哈希的条件)

    2. 调用BuildPipeline.BuildAssetBundles进行打包,传入AssetBundleBuild[] builds,返回AssetBundleManifest

    3. 调用manifest.CreateVersions()生成版本文件,传入AssetBundleManifestList<BundleBuild> bundleBuilds(除了下面提到的几个主要文件生成,流程中还对Build一些对象进行了刷新)

      • 首先会使用AssetBundleManifest中的哈希码对所有BundleBuild.nameWithAppendHash参数进行命名(Bundle名+哈希码)

      • 调用AfterBuild()方法,传入bundleBuilds,在资源输出目录创建由Build反序列的两个Json文件。作用见:构建分组->Manifest.BuildGroups()->第4步

      • 调用CreateBuild()方法,

        • 生成版本文件mymanifest,内部持有版本内所有资源的依赖信息

        • 生成版本配置文件mymanifest.version持有版本号,内存大小,CRC内容

      • 调用Save(),保存当前编辑器所有更改

    <0

    1. 如果List<BundleBuild> rawBundleBuilds个数不小于0,或Build对象中的Bundle数与当前BundleBuilds中个数不相符。

      调用manifest.CreateVersions(),传入null,List<BundleBuild> bundleBuilds。(见>0情况的第3步)

打包清单

该功能只生成清单文件,不会构建分组与资源

直接调用了Manifest.CreateBuild方法,见构建资源的BuildScript.BuildBundles(Manifest manifest);方法->第3步骤-> >0 ->第三节

打包播放器

代码在BuildScript.BuildPlayer方法中,没什么好讲的,将Setting中的启动场景进行打包发布,都是一些基本的发布配置。如果需要更改输出目录,也在这里面修改

复制资源到StreamingAssets文件夹

Settings.CopyToStreamingAssets

  1. 删除StreamingAssets文件夹并重新创建

  2. 遍历所有manifest,并调用Manifest.CreateBuild(true),见构建资源的BuildScript.BuildBundles(Manifest manifest);方法->第3步骤-> >0 ->第三节

    注意与之不同的是在生成Json文件前,会调用Build.GetBundlesInBuild方法,这在之前是没有使用,所以未提及

    随后正常生成Json和版本文件

Build.GetBundlesInBuild

  1. 遍历所有分组,如果分组标记了includeInBuild标签,那么将该分组的所有Bundle,从Build输出目录复制到StreamingAssets文件夹中

清空打包资源

调用Settings.Save(true),删除Build输出目录

编辑器运行时加载模式

核心代码见编辑器脚本Initializer

由于该脚本的Init方法,加上了特性[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)],所以能在Unity编辑器启动时运行:RuntimeInitializeOnLoadMethod

Init方法中,获取到Settings对象,从中查询到当前编辑器的资源加载模式。根据模式不同执行下列流程

  • Simulation:仿真模式

                        Versions.CreateAsset = EditorAsset.Create;
                        Versions.CreateScene = EditorScene.Create;
                        Versions.CreateManifest = EditorManifestFile.Create;
    

    什么意思呢,默认情况下Versions.CreateAsset是空的,在XAsset框架启动时会判断是否为空,如果为空则赋值为BundledAsset.Create。当前编辑器类提前给它赋值为EditorAsset了,那么后续加载都会通过EditorAsset加载,而不是BundledAsset

    EditorAsset类中的相关资源加载都是走的AssetDatabase.LoadAssetAtPath,所以也就无需资源的更新等操作,所有资源加载都走本地。

  • Preload:预加载模式

          Versions.PlayerDataPath = Path.Combine(Environment.CurrentDirectory, EditorUtility.PlatformBuildPath);
    

    在代码中,对包体资源加载路径进行了修改,指向了输出目录,也就意味着标记了Include In Build分组的Bundle都不需要下载更新,已经提前下载好了。

    而新增资源需要走服务器更新下载。

  • Incremental:增量模式

    这个模式没有任何代码操作,完全仿照移动端的更新方式。跟随打包的资源(标记了Include In Build分组的Bundle)从StreamingAssets文件目录下加载,而新增资源从服务器下载。

分布式打包

在回过头来讲到分布式打包,分布式打包本质上就是将资源划分为几个独立的版本,以打包清单Manifest为粒度进行维护。

一般来说小体量游戏确实用不到。为了更好理解它的作用,举个例子:

大家小时候应该玩过光碟游戏,就是那种可以接手柄的DVD影碟机。播放光碟的时候会出现一个游戏菜单供我们选择游戏,然后可以选择任意一个小游戏进行游玩。这里,我们把游戏菜单比作主项目,里面的小游戏比作子项目,如果我们对其中一个子项目游戏进行了更新,那么这时候进行资源打包,不仅会影响到其他子项目,而且由于整个主项目进行更新打包还慢。为此分布式打包优势就出来了。

我们可以将单个子项目划分出一个Manifest,来进行维护。需要更新的时候,我们只需要更新单个子项目的Manifest,从而加快打包速度、提示项目安全。

运行时代码

由于这是6.1版本新增功能,所以这里对它的运行时调用做一些讲解。简单来说就是6.0版本只需要更新一个版本控制文件,6.1现在需要更新多个,但后续加载过程都是相同的

在框架启动挂在脚本Startup中我们可以找到相关代码。

  1. Start方法中,我们可以看到Versions.InitializeAsync(manifests);方法传入了一组ManifestInfo数组。数组中的清单配置信息决定了我们在后续流程中更新哪些项目资源。
  2. InitializeAsync方法中,创建了一个InitializeVersions对象,用于初始化。
  3. InitializeVersions对象的Start方法中,调用ManifestFile.LoadAsync方法加载配置清单。随后加载流程和6.0相同

Raw格式

等待完善

Licensed under CC BY-NC-SA 4.0
0