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
在GroupEditor的
OnGUI
方法中完成了非常重要的两步操作
- 创建默认打包配置对象Settings
- 创建第一个打包表单对象Manifest
-
在
OnGUI
中,首先会通过Settings.GetDefaultSettings()
方法获取Settings对象,如果该对象不存在,方法则会在指定位置创建一个 -
从上一步获取到的Settings对象中获取一个Manifest,如果没有,则会调用
CreateManifest
方法提示用户创建一个Manifest。待创建完成后调用settings.AddManifest()
方法将Manifest保存到Settings中。
后续OnGUI
会继续调用DrawTree
和DrawToolbar
方法,对窗口进行绘制,具体略过,编辑器绘制太麻烦了,下面只挑重点讲
创建Group
该步骤代码见AssetTreeView脚本的ContextClicked
方法(由于AssetTreeView继承了**TreeView**,所以实现的ContextClicked
方法可以监听窗口内鼠标点击事件)(就这一个类一千多行代码,我人傻了)
点击创建后会调用manifest.AddGroup
方法,在指定目录生成一个Group对象。并将Group对象的manifest指定为自己,而后保存到manifest.groups中
同样的对Group的Rename
和Remove
操作也在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();
- 调用
Settings.GetDefaultSettings();
加载默认的打包资源设置Setting - 遍历Setting中的所有打包清单Manifest
- 调用清单的
Manifest.BuildGroups()
方法进行分组构建
Manifest.BuildGroups()
-
调用
CollectAssets()
方法采集规则Manifest中所有的Asset资源,方法内会对Manifest的所有Asset进行一次清理,重新创建,以保证资源的正确性。返回Dictionary<Path, Asset>
-
调用
AnalysisDependencies
方法处理上一步获取到的Asset资源的依赖(获取文件依赖的核心方法是Asset.GetDependencies()
)。返回Dictionary<Path, List<Asset>>
(遍历Asset的依赖,将非重复的且未添加的依赖保存到字典中返回) -
调用
AutoGrouping
方法并传入前两步返回的字典,对公共资源依赖进行分组-
首先获取或创建名为 Auto 的Group分组,并设置分组的打包方式为分组名+资源打包方式
-
将前面获取的所有Asset的路径和所属Bundle名,按行写入到Build目录下的
auto_assets_dependencies_for_
文本文档中。这里需要注意,Asset所属Bundle名通过
Asset.Bundle
属性获取,根据Asset所属打包分组的打包方式的不同,所生成名称也不相同 -
调用
AddAsset
方法,传入上一步获取的Asset路径,和AutoGroup,创建新的Asset,并标记未Auto,绑定在AutoGroup下
构建分组的任务到这里就结束了,后面是方法中剩余的功能
-
-
调用
CreateBundles
方法生成新增BundleBuild队列,Out 所有BundleBuild队列。- 遍历Manifest.assets,根据Asset的Bundle名创建BundleBuild对象并保存到Out队列中,并将单个Asset转换成AssetBuild对象,保存到BundleBuild.assets中。
- 通过
GetBuild
方法创建Build实例,在GetBuild
方法中会反序列化打包目录下的Json文件,然后反序列化为Build实例,也就是说,Build对象持有之前打包的记录信息 - 调用Build的
GetAssets
方法,获取之前的生成的AssetBuild信息。然后和当前生成的AssetBuild进行比对,筛选出修改或新增的BundleBuild返回。
-
遍历上一步返回的BundleBuild队列,调用其
GetBuild
方法,获取AssetBundleBuild(参数有assetBundleName
(指向BundleBuild.name->Asset.bundle)与assetNames
(指向AssetBuild->Asset)),保存到字典中。请记住AssetBundleBuild,在后续打包流程中,将使用它作为打包核心参数
-
调用
BuildRaws
方法,传入第4步Out的BundleBuild队列。该步骤针对标记了Raw格式的分组- 在方法中对Manifest的所有rawGroup的所有Asset调用了
BuildRaw
方法 - 在
BuildRaw
方法中计算了单个Asset目标文件的大小和唯一码CRC、以及哈希名,并生成BundleBuild文件添加到队列中 - 等待所有
BuildRaw
执行完所有Asset后,将队列中的目标文件,重命名后拷贝到相对打包目录目录 - 返回Out队列
- 在方法中对Manifest的所有rawGroup的所有Asset调用了
-
返回第5步的字典转换成的Value数组。
构建资源
构建资源主要做了以下流程:
- 重新构建分组
- 生成Build对象及其BundleBuild缓存,从中提取出AssetBundleBuild
- 使用Unity的
BuildPipeline.BuildAssetBundles
方法和AssetBundleBuild进行打包 - 持久化Build为Json文件
- 生成版本控制文件
BuildScript.BuildGroups();
- 调用
Settings.GetDefaultSettings();
加载默认的打包资源设置Setting - 遍历Setting中的所有打包清单Manifest,调用
BuildBundles
方法传入Manifest
BuildScript.BuildBundles(Manifest manifest);
-
执行构建分组时的
Manifest.BuildGroups()
方法,不同的是,会将方法全部执行完,返回AssetBundleBuild[] builds
、List<BundleBuild> bundleBuilds
、List<BundleBuild> rawBundleBuilds
-
根据返回的
builds
数量是否为0,分为两种情况。>0
-
准备打包所需:当前manifest的路径,Unity的Build设置,打包资源输出目录,BuildAssetBundleOptions(注意:这里手动添加了向 assetBundle 名称附加哈希的条件)
-
调用
BuildPipeline.BuildAssetBundles
进行打包,传入AssetBundleBuild[] builds
,返回AssetBundleManifest -
调用
manifest.CreateVersions()
生成版本文件,传入AssetBundleManifest、List<BundleBuild> bundleBuilds
。(除了下面提到的几个主要文件生成,流程中还对Build一些对象进行了刷新)
<0
-
如果
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
-
删除StreamingAssets文件夹并重新创建
-
遍历所有manifest,并调用
Manifest.CreateBuild(true)
,见构建资源的BuildScript.BuildBundles(Manifest manifest);
方法->第3步骤-> >0 ->第三节注意与之不同的是在生成Json文件前,会调用
Build.GetBundlesInBuild
方法,这在之前是没有使用,所以未提及随后正常生成Json和版本文件
Build.GetBundlesInBuild
- 遍历所有分组,如果分组标记了
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中我们可以找到相关代码。
- 在
Start
方法中,我们可以看到Versions.InitializeAsync(manifests);
方法传入了一组ManifestInfo数组。数组中的清单配置信息决定了我们在后续流程中更新哪些项目资源。 - 在
InitializeAsync
方法中,创建了一个InitializeVersions对象,用于初始化。 - 在InitializeVersions对象的
Start
方法中,调用ManifestFile.LoadAsync
方法加载配置清单。随后加载流程和6.0相同
Raw格式
等待完善