尼尔:机械纪元

上周介绍了Unity项目中的资源配置,今天和大家分享一个AssetBundle打包工具。相信从事Unity开发或多或少都了解过AssetBundle,但简单的接口以及众多的细碎问题也给工作带来较多的困扰。今天分享AssetBundle工具的实践与想法,相信这块内容对帮助理解AssetBundle有较大的帮助。

Unity提供了两种资源加载方式,一种是Resources,另外种就是AssetBundle。所有的资源只要放在Resources目录下,在打包的时候会自动打进去,并可以通过相应的接口加载。正常情况下Resources非常方便,可以满足日常的需求,但资源放Resources会带来资源更新上的问题。之前写过一篇文章Unity资源目录及加载接口介绍可以了解些细节。

假设首包所有资源都放Resources,后续更新资源的走AssetBundle,会发现AssetBundle和Resources的资源互相不兼容。当调整一个模型的材质参数后,对模型进行打包仍需要把Mesh,Texture等资源都打进去。这会导致更新包过大,同时在加载这个模型时,这些资源是不共用的,相同的资源可能在内存中存在两份。所以正常情况下,项目发布时所有需要更新的资源要打成AssetBundle。

正常项目中资源的提交与变更非常频繁,手工对每个资源配置Bundle费时费力,基本不可取。所以一般项目中的Bundle都是程序自动创建的。同时为了避免有多余的资源被打包,通常需要配置哪些资源是发布资源(直接加载的),其他资源通过引用的形式获取。这个配置需要方便修改,来满足日常变更。

Bundle的打包规则对资源加载速度,更新大小,重复资源数量以及最终包数量等等都有较大影响。一个可靠的Bundle打包方案应该是根据实际情况对Bundle打包规则做调整慢慢产生的。

在Unity 4,只有最基础的几个打包接口可以用于打包。Unity 5简化了Bundle打包时候的依赖关系,但实际如何创建Bundle以及对依赖资源的配置都节省不了。远远不能满足项目对资源打包这块的需求。

这里实现的AssetBundle打包工具帮助简化这个繁琐的打包过程,同时方便做规则调整,得到更优的打包方案。目前工具BundleBuildTool已经放在GitHub,可以作为一份打包实现的参考,也可以直接使用这工具来进行打包。

AssetBundle

An AssetBundle is an archive file containing platform specific Assets (Models, Textures, Prefabs, Audio clips, and even entire Scenes) that can be loaded at runtime.

资源类型

不同类型资源会有不同的打包方式,比如场景文件的打包接口和其他资源的打包接口就是不一样的。通过定义不同的资源类型,可以实现不同的打包方式,支持更多资源的打包。

public enum BundleType
{
None = 0,
Script, // .cs
Shader, // .shader or build-in shader with name
Font, // .ttf
Texture, // .tga, .png, .jpg, .tif, .psd, .exr
Material, // .mat
Animation, // .anim
Controller, // .controller
FBX, // .fbx
TextAsset, // .txt, .bytes
Prefab, // .prefab
UnityMap, // .unity
}

对于特殊类型的资源,通过类型可以做一些定制化操作。比如把所有的Script配置在一个Bundle里面,然后在启动的时候对这个Bundle做预加载。通常情况下也会把所有的Shader配置到一个Bundle里面。

正常一个模型会有自己的Texture,Mesh & Animation,把资源按类型打成三个包,在加载的时候可以得到更高的加载速度。Unity异步加载接口会同时进行多个资源加载,资源配置在不同的包里,可以有较好的加载速度提升,所以一般是按资源类型来进行打包。不过要注意如果太分散的话,一样会影响加载速度。

资源加载速度这个是在文章Asset Bundles vs. Resources: A Memory Showdown提及。

These blocks sizes are optimized for loading multiple Assets and bundles in parallel. For example, you should be able to load objects from 4 to 5 Asset Bundles at the same time without the the allocators for Asset Bundle Async loading or Type Trees needing new blocks.

资源依赖

处理资源依赖应该是打包过程最复杂的一块功能,这里把获取资源依赖文件列表单独设计一个类,做一些特殊情况处理。如果发现一些依赖关系上的错误,除了修改资源本身外,也可以在打包环节实现一些脚步做保障。

正常情况下,通过AssetDatabase.GetDependencies即可获取一个资源的所以依赖文件。但实际情况中,Unity内部是通过分析内部guid来生成依赖文件。有时候在文件里面会存在一些脏的guid这会产生多余的依赖。比如你修改一个材质贴图属性名,然后设置了一张新的贴图给这个新的属性名。打开材质文件会发现旧的属性名以及引用guid出现在材质文件,通过GetDependencies获取的最后结果也包含这个数据。实现自己获取依赖函数来处理这种多余依赖关系。同时提供带缓存接口,提高打包效率。

下面是对材质依赖贴图文件获取的代码实现。

...
MaterialProperty[] proTes = MaterialEditor.GetMaterialProperties(new Object[] {mat});
for (int i = 0; proTes != null && i < proTes.Length; ++i)
{
if (proTes[i].type == MaterialProperty.PropType.Texture)
{
Texture tex = mat.GetTexture(proTes[i].name);
string path = AssetDatabase.GetAssetPath(tex);
if (!dict.ContainsKey(path))
{
dict.Add(path, path);
}
Resources.UnloadAsset(tex);
}
}
...

资源剔除

处理完资源依赖后,还碰到一个问题就是最后打包Assets资源。通过AssetDatabase.LoadAllAssetAtPath获取这个文件依赖的所有的Assets资源。如果对所有的这些Assets资源都做打包的话,会发现一些编辑器用数据也会被打包进去。特别是对于FBX类型文件,通常会存在一个"__preview_Take 001"的动作资源使包体变大很多。对于这些不必要的数据,在打包环节中增加一个剔除规则,减少包体大小。

public static List<UnityEngine.Object> FilterObjectByType(UnityEngine.Object[] assets, BundleType bundleType)
{
List<UnityEngine.Object> ret = new List<UnityEngine.Object>();
foreach (UnityEngine.Object asset in assets)
{
switch (bundleType)
{
case BundleType.FBX:
if (!(asset.GetType() == typeof(AnimationClip) && asset.name == "__preview_Take 001"))
{
ret.Add(asset);
}
break;
default:
ret.Add(asset);
break;
}
}
return ret;
}

Unity 5刚出的时候会把这个数据打进AssetBundle造成包体过大,后面版本观察已经修复这个问题。不过也可以发现这个环节的必要性,如果发现资源出问题在这个环节处理即可。

这个环节不仅可以剔除不必要的数据,还可以直接修改数据本身。就拿Mesh数据举例,美术在制作过程中会导出多余的顶点数据在文件里面(uv3,uv4...)。通常配置Optimize Mesh可以干掉这些无用数据,不过直接启用可能会出现删除了需要数据情况,比如color数据丢失。所以自己来做,通过把Mesh对象上不需要的对象数据置空,然后再打包即可。在之前分享的资源配置工具里已经做了对Mesh顶点数据的配置,基本上就是为这个打包环节服务,因为无法修改FBX文件,只能美术重新导出。

资源大小

资源大小影响最后的包体大小,如果对包体大小以及更新量有关注的话,对资源大小做预估是一个非常有必要的环节。在资源大小计算环节,不能疏漏之前二个资源环节对资源的处理,同时不同类型的资源统计方式不一样。

通常通过下面两个方式预估资源大小

int resSize = UnityEngine.Profiling.Profiler.GetRuntimeMemorySize(asset);
FileInfo fileInfo = new FileInfo(assetPath);
int fileSize = fileInfo.Length;

如何对一个资源做一个大小估算,并不是一件非常方便的事情的。如果依赖资源已经在之前打包了,那这个资源的实际大小是要考虑减去依赖资源那部分的大小。如果不统计依赖资源的大小,那这个资源的包的大小也是不准确的。所以这里的实际逻辑较为复杂,但实际一个大致的值就可以了,然后观察最后的包大小做一些配置微调即可。

Bundle模型

讨论完资源上的一些细节,下面开始Bundle设计的介绍。一个Bundle模型用name做唯一标识,为了方便管理加入了parent与children数据。同时一个Bundle应该有一个固定资源类型。为了方便对包大小做限制加入了size属性,作为资源大小的预估。

public class BundleData
{
public string name = string.Empty;
public string parent = string.Empty;
public BundleType type = BundleType.None;
public BundleLoadState loadState = BundleLoadState.UnloadImmediately;
public int size = 0;
public List<string> includs = new List<string>();
public List<string> children = new List<string>();
}

最后一个Bundle包含多个资源文件路径。尽管AssetBundle是按Assets打包的,但在正常环境下的资源是以文件存在的。一个资源文件可能包含多个资源,也可能引用到其他资源。资源文件可以用路径来标识,Unity内部通过GUID来标识资源文件,所以即使你挪动文件因为GUID不变,还是可以找到这个文件。这里决定直接用资源路径来标识资源而不是使用GUID,因为挪动资源目录有较多的风险,原则上禁止挪动资源。如果真挪动了资源,按最新的资源路径生成Bundle是一个不错的选择。

如果有对Bundle有其他属性上的需求,在这个类扩展就好。

Bundle创建规则

定义Bundle后,创建Bundle是很困扰的一个问题。在大型项目中,资源的量非常大,资源之间的互相引用也较为复杂。这里定义一个数据结构帮忙创建Bundle。

public class BundleImportData
{
public string RootPath = "";
public string FileNameMatch = "*.*";
public int Index = -1;
public int TotalCount = 0;
public BundleType Type = BundleType.None;
public BundleLoadState LoadState = BundleLoadState.OnUnloadAsset;
public bool Publish = false;
public int LimitCount = -1;
public int LimitKBSize = -1;
public bool PushDependice = false;
public bool SkipData = false;
}

对于一个Bundle,可以约束它的大小,对象数量、类型、加载方式、打包方式。然后根据规则,自动给每个资源文件配置Bundle。

资源分加载资源和被依赖引用到的资源,对于直接加载的资源,需要配置Publish为True。Bundle创建就是从这些配置了Publish的资源文件以及其依赖生成的。

对所有可能被打包的资源配置打包规则,没有被配置资源文件,则会被一起打倒最后资源的包里面。这里会碰到一个问题,有些资源需要补分包,但是通用规则会包含不需要分包的资源。这里增加了一个SkipData属性,当为True时这些资源不单独创建Bundle。

然后讨论下PushDependice属性,正常情况下只有在打Prefab类型的资源的时候才会做这个操作。因为Prefab数据本身是不共享的,然后避免Prefab与Prefab之间的复杂依赖。

最后讨论下打包的顺序,因为资源之间有互相依赖,所以需要配置资源的打包顺序。这里资源的打包顺序就是BundleImportData创建的顺序。这里需要对资源之间的依赖以及资源类型有一定的认识。

已经配置过Bundle的资源不会变更,新增的资源会按规则配置相应的Bundle。通常规则发生变更会影响非常多的资源,如果所有资源重新配置会导致更新包过大。

Bundle构建

首次创建的Bundle,由于本地文件不存在,会触发构建。然后资源之间有互相依赖,所有被依赖的Bundle也需要参加构建。对于增量构建,这里做了一个简化设计,不自己去计算文件是否变更,而是由外部提供一个文件变化列表。通过这个列表工具自动生成Bundle构建列表,提高打包速度。

在配置打包参数为BuildAssetBundleOptions.DeterministicAssetBundle后,如果不对资源做修改,两次打包的文件是一样的。所以即使有很多资源因为依赖要重新打包,最后的文件未发生变化,就不会触发更新。

Bundle索引

Bundle构建完后只是一堆二进制文件,需要根据Bundle之间的依赖关系生成出一份数据。除了需要知道Bundle之间的依赖之外,同时还需要知道资源路径与Bundle之间的映射关系。最后还要把Bundle状态信息保存下来,用于Bundle更新、加载和卸载。

public class BundleState
{
public string bundleID = string.Empty;
public uint crc = 0;
public uint compressCrc = 0;
public int version = -1;
public long size = -1;
public BundleLoadState loadState = BundleLoadState.OnUnloadAsset;
public BundleStorePos storePos = BundleStorePos.Building;
}
// like UnityEngine.AssetBundleManifest
public class BundleManifest { ... }

这个文件自己定义形式,可以使分散的多个文件,也可以统一放到一个文件里面,自己实现可以优化数据结构减少内存开销。

通用的Bundle打包方案

下面是在Unity Standard Assets资源上做配置后的结果

 
BundleBuildTool

按大小配置基础资源,然后对于Prefab和Unity文件限定下个数,避免过多的资源依赖。配置结束后点击CreateBundle就可以得到下面的结果。

[完 2017-07-13 Carber]

作者:carber
链接:https://www.jianshu.com/p/a7720cbbe4c4
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

一个灵活的AssetBundle打包工具的更多相关文章

  1. Unity 游戏框架搭建 (十一) 简易AssetBundle打包工具(一)

    最近在看Unity官方的AssetBundle(以下简称AB)的教程,也照着做了一遍,不过做出来的AssetBundleManager的API设计得有些不太习惯.目前想到了一个可行的解决方案.AB相关 ...

  2. Unity 游戏框架搭建 (十二) 简易AssetBundle打包工具(二)

    上篇文章中实现了基本的打包功能,在这篇我们来解决不同平台打AB包的问题. 本篇文章的核心api还是: BuildPipeline.BuildAssetBundles (outPath, 0, Edit ...

  3. Unity自动打包工具

    转载 https://blog.csdn.net/ynnmnm/article/details/36774715 最开始有写打包工具的想法,是因为看到<啪啪三国>王伟峰分享的一张图,他们有 ...

  4. Unity自己主动打包工具

    最開始有写打包工具的想法,是由于看到<啪啪三国>王伟峰分享的一张图,他们有一个专门的"工具程序猿"开发各种工具. (ps:说起来这个王伟峰和他的创始团队成员,曾经跟我是 ...

  5. [Unity3D] 5.0 图集合并扩展工具,用于解决UGUI与AssetBundle打包造成资源包过大的问题

    [Unity3D] 5.0 图集合并扩展工具,用于解决UGUI与AssetBundle打包造成资源包过大的问题 2017年07月05日 15:57:44 阅读数:1494 http://www.cpp ...

  6. 用winform实现一个B/S代码更新打包工具

    一个.net程序员必须拥有的能力就是可以随时随地写出一个自己需要的小工具,于是记录一下我的个人工具吧. 新建一个窗体应用项目,代码如下: namespace 打包工具 { partial class ...

  7. Parcel上手——又一个打包工具

    Parcel是什么? 极速零配置Web应用打包工具 说到打包工具,大多人应该都用过Webpack,Parcel也是这一类工具. Parcel相比Webpack有什么优势? 配置简单 打包速度快 以下是 ...

  8. 【厚积薄发】Crunch压缩图片的AssetBundle打包

    这是第133篇UWA技术知识分享的推送.今天我们继续为大家精选了若干和开发.优化相关的问题,建议阅读时间10分钟,认真读完必有收获. UWA 问答社区:answer.uwa4d.com UWA QQ群 ...

  9. Winform打包工具SetupFactory 9 的使用

    写了个WinForm的小程序..以前没打过包..只是直接把Bin里的东西复制出来使用..自己使用是足够.但是发给别人毕竟不太好看(不牛逼)..所以就想着打包.. Vs2012自带的有打包的功能..相信 ...

随机推荐

  1. asp.net 页面如何将Eval中的时间显示为“yyyy-MM-dd ” 格式

    <table> <tr>    <td style="width:273px;color:#105db5;" valign="top&quo ...

  2. 自然语言17_Chinking with NLTK

    https://www.pythonprogramming.net/chinking-nltk-tutorial/?completed=/chunking-nltk-tutorial/ 代码 # -* ...

  3. Currency Exchange 分类: POJ 2015-07-14 16:20 10人阅读 评论(0) 收藏

    Currency Exchange Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 22180 Accepted: 8015 De ...

  4. JavaScript闭包演示

    <!DOCTYPE HTML> <html> <head> <meta charset="utf-8" /> <title&g ...

  5. 获取腾讯soso地图坐标代码

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  6. php 登录实例演示

    <pre name="code" class="python">一.模板的使用 (重点) a.规则 模板文件夹下[TPL]/[分组文件夹/][模板主 ...

  7. VisionPro学习笔记:用IEEE1394相机抓取图像

    1)找到采集卡: CogFrameGrabber1394DCAMs cameras = new CogFrameGrabber1394DCAMs(); 2)列举相连接的相机: ICogFrameGra ...

  8. CSS---光标cursor设置、浮动布局与clear的关系

    光标设置 {cursor:auto;}--光标根据需要自动变化. {cursor:crosshair;}--光标变成“+”. {cursor:pointer;}--光标变成手指模式. {cursor: ...

  9. enable-ssh-key-logon-disable-password-password-less-logon-centos/

    cat ~/.ssh/id_rsa.pub | ssh root@destination_server_address "cat >> ~/.ssh/authorized_key ...

  10. string.PadLeft &amp; string.PadRight

    比如我想让他的长度是20个字符有很多字符串如string a = "123",只有3个字符怎么让他们在打印或显示在textBox上的时候不够的长度用空格补齐呢? string.Pa ...