Unity资源打包之Assetbundle
本⽂原创版权归 csdn 所有,转载请详细注明原创作者及出处,以⽰尊重!
作者:
如果这篇⽂章对你有帮助,敬请关注作者《Unity⼿游之路》系列教程。
在⼿游的运营过程中,更新资源是⽐不可少的。资源管理第⼀步是资源打包。传统的打包可以将所有物件制成预设Prefab,打包成场景。
今天我们来⼀起学习官⽅推荐的Assetbundle,它是Unity(Pro)提供的资源打包策略。利⽤AssetBundle,可以将⼏乎所有的资源都打
包封装,便于客户端更新下载新的资源。
创建AssetBundle
1.创建⼀个空的Prefab,命名Cube,然后创建⼀个Cube,将其拉到刚创建好的Prefab
2.新建⼀个脚本ExportAssetBundles.cs(代码来⾃官⽅⽂档),保存在Asset/Editor⽬录下
//在Unity编辑器中添加菜单
[MenuItem("Assets/Build AssetBundle From Selection")]
static void ExportResourceRGB2()
{
// 打开保存⾯板,获得⽤户选择的路径
string path = EditorUtility.SaveFilePanel("Save Resource", "", "New Resource", "assetbundle");
if (path.Length != 0)
{
// 选择的要保存的对象
Object[] selection = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);
//打包
BuildPipeline.BuildAssetBundle(Selection.activeObject, selection, path, BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.Comple }
}
这时我们将看到Asset下⾯出现Build AssetBundle From Selection和Build Scene
3.选中预设Cube,运⾏Build AssetBundle From Selection。这时会弹出⼀个保存框,将其命名为cube.unity3d(这⾥为了测试⽅便,放在c盘。实际项⽬中,我们是需要将他们放在
web服务器,供所有客户端下载更新)
4.新建⼀个场景scene1.unity,上⾯放置⼏个模型,然后保存
5.选中该场景,在之前的ExportAssetBundles.cs脚本中添加打包场景的函数,运⾏Assets->Build Scene,保存为scene1.unity3d(这
⾥为了测试⽅便,也放在c盘)
[MenuItem("Assets/Save Scene")]
static void ExportScene()
{
// 打开保存⾯板,获得⽤户选择的路径
string path = EditorUtility.SaveFilePanel("Save Resource", "", "New Resource", "unity3d");
if (path.Length != 0)
{
// 选择的要保存的对象
Object[] selection = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);
string[] scenes = {"Assets/scene1.unity"};
//打包
BuildPipeline.BuildPlayer(scenes,path,BuildTarget.StandaloneWindows,BuildOptions.BuildAdditionalStreamedScenes);
}
}
注意事项
a.AssetBundle的保存后缀名可以是assetbundle或者unity3d
b.BuildAssetBundle要根据不同的平台单独打包,BuildTarget参数指定平台,如果不指定,默认的webplayer
加载AssetBundle
我们通过⼀个简单的代码来演⽰如何加载assetbundle,包括加载普通asset和场景。
using System;
using UnityEngine;
using System.Collections;
public class Load: MonoBehaviour
{
private string BundleURL = "file:///C:/cube.assetbundle";
private string SceneURL = "file:///C:/scene1.unity3d";
void Start()
{
//BundleURL = "file//"+Application.dataPath+"/cube.assetbundle";
Debug.Log(BundleURL);
StartCoroutine(DownloadAssetAndScene());
}
IEnumerator DownloadAssetAndScene()
{
/
/下载assetbundle,加载Cube
using (WWW asset = new WWW(BundleURL))
{
yield return asset;
AssetBundle bundle = asset.assetBundle;
Instantiate(bundle.Load("Cube"));
bundle.Unload(false);
yield return new WaitForSeconds(5);
}
//下载场景,加载场景
using (WWW scene = new WWW(SceneURL))
{
yield return scene;
AssetBundle bundle = scene.assetBundle;
Application.LoadLevel("scene1");
}
}
}
注意事项
a.LoadFromCacheOrDownload 可以指定版本,如果本地版本是新的,将不会从服务器读取
b.如果是多个资源打包在⼀起,我们要通过bundle.Load(),加载特定的资源
c.挂载在模型上的脚本也可以⼀起打包,但是保证脚本在原⽬录也要存在,否则加载出来⽆法运⾏。关于如何更新脚本,我将放在以后的章节中阐述。
AssetBundle依赖关系
如果⼀个公共对象被多个对象依赖,我们打包的时候,可以有两种选取。⼀种是⽐较省事的,就是将这个公共对象打包到每个对象中。这样会有很多弊端:内存被浪费了;加⼊公共对象改变了,每个依赖对象都得重新打包。AssetBundle提供了依赖关系打包。我们通过⼀个简单的例⼦来学习
//启⽤交叉引⽤,⽤于所有跟随的资源包⽂件,直到我们调⽤PopAssetDependencies
BuildPipeline.PushAssetDependencies();
var options =
BuildAssetBundleOptions.CollectDependencies |
BuildAssetBundleOptions.CompleteAssets;
//所有后续资源将共享这⼀资源包中的内容,由你来确保共享的资源包是否在其他资源载⼊之前载⼊
BuildPipeline.BuildAssetBundle(
AssetDatabase.LoadMainAssetAtPath("assets/artwork/lerpzuv.tif"),
null, "Shared.unity3d", options);
//这个⽂件将共享这些资源,但是后续的资源包将⽆法继续共享它
BuildPipeline.PushAssetDependencies();
BuildPipeline.BuildAssetBundle(
AssetDatabase.LoadMainAssetAtPath("Assets/Artwork/Lerpz.fbx"),
null, "Lerpz.unity3d", options);
BuildPipeline.PopAssetDependencies();
这个⽂件将共享这些资源,但是后续的资源包将⽆法继续共享它
BuildPipeline.PushAssetDependencies();
BuildPipeline.BuildAssetBundle(
AssetDatabase.LoadMainAssetAtPath("Assets/Artwork/explosive guitex.prefab"),
null, "explosive.unity3d", options);
BuildPipeline.PopAssetDependencies();
BuildPipeline.PopAssetDependencies();
我们在程序加载的时候必须保证先加载公共对象。否则,只能是在各个对象加载成功后,再通过程序⼿动添加进来,⽐较繁琐。在实际项⽬中,由于是团队开发,对象间的依赖关系通常会⽐较凌乱,最好在开发周期就定好相关的规范约束,⽅便管理。
总结
这⼀节的内容偏实际操作,官⽅⽂档和⾬松的blog中已经有更加详细介绍。如果⼤家还有不明⽩的地⽅,可以结合⽂档再实际操作⼀下。后⾯的章节,我将花更多的时间介绍核⼼的内容:资源的增量更新,和代码程序的更新。
源码
参考资料
Assetbundle 是Unity Pro提供提供的功能,它可以把多个游戏对象或者资源⼆进制⽂件封装到Assetbundle中,提供了封装与解包的⽅法使⽤起来很便利。
1.预设
Assetbundle可以将Prefab封装起来,这是多么⽅便啊! ⽽且我也强烈建议⼤家将Prefab封装成Assetbundle,因为Prefab可以将游戏对象⾝上带的游戏游戏组件、游戏脚本、材质都封装在⼀起。当从服务器上将Assetbundle下载以后直接Instantiate就可以放⼊游戏中。
试想⼀下,如果只能将原始的⼆进制资源⽂件放在服务器上下载,当资源⽂件下载完毕后,需要动态的创建游戏对象、然后动态的将脚本绑定在游戏对象、动态的将贴图赋予游戏对象等等各种动态的操作。。所以强烈建议使⽤Prefa,不解释
另外,我在举个例⼦,因为模型有可能会带很多动画⽂件,那么这样⼀组模型资源就可能是多个FBX ⽂件 和 若⼲png贴图⽂件 材质⽂件。这时我只需要把原始模型放⼊Prefab中,它就会包含这个模型的所有组件、甚⾄包括它的动画资源、贴图。那么如下图所⽰,Mode就是模
这时我只需要把原始模型放⼊Prefab中,它就会包含这个模型的所有组件、甚⾄包括它的动画资源、贴图。那么如下图所⽰,Mode就是模型的Prefab⽂件,那么我仅仅只需要把Mode这个预设打包成Ass
etbundle即可。 当我在服务器上下载这个Assetbundle并且载⼊游戏中就可以直接使⽤了,切换动画、换贴图都可以。。
2.⼆进制⽂件
也并不是Assetbundle中全都要⽤预设,Assetbundle它也可以将⼆进制⽂件直接封装在⾥⾯,⽐如图⽚、声⾳、⽂本信息等等。
3.场景⽂件
在Unity中可以将⼀个场景保存在Scene中,Scene就会包含这个场景中的所有,那能不能把Scene也封装成Assetbundle中?答案是能,但是它不能在移动平台上⽤,因为移动平台上是不能更新脚本的,换句话来说就是即使将脚本绑定在Prefab中,然后下载Assetbundle 后,所有脚本是不会执⾏的,后⾯说另外⼀种巧妙⽤法。
4.移动平台
上⾯MOMO已经将Assetbundle 的使⽤原理⼤致介绍了⼀下 ,我们在谈谈移动平台。脚本不能更新是移动平台下最⼤的伤,这就意味着开发者⽆法绕过App store和 google Play这种在线商店升级应⽤程序。唯⼀能做到的就是更新资源、举个例⼦,游戏中在处理版本升级时,⼀般会有个⼤版本号和⼀个
unity 教程⼩版本号,⼤版本号就是 2.0、3.0这种 版本需要在AppStore中更新,⼤版本主要是升级游戏脚本,然后当⼩版本号,⽐如2.0.1 或2.0.2这种只是更新游戏中的资源,通过⾃⼰游戏的服务器就可以完成,通过Assetbundle在⾃⼰服务器上下载,然后适应在游戏中。如果⾮要更新脚本,或不得不更新脚本那么只能在Appstore或者google Play去更新⼤版本。
移动平台上不能更新脚本,那么Prefab上绑定的脚本怎么办?在任何平台上都可以把脚本添加到Prefab上,然后打包成Assetbundle,只有移动平台上有点特殊,⽐如将Test.cs这条脚本绑定在Prefab中,最后程序通过服务器下载这个Assetbundle ,当载⼊⼯程中这条脚本是不会被执⾏的。
但是如果本地⼯程有Test.cs这条脚本,那么Unity会⾃动将这条脚本绑定在下载的Prefab中,并且他们执⾏的⾮常好。如果本地⼯程中没有Test.cs这条脚本,那么Prefab上的脚本是永远都不会执⾏的。有时我们会在脚本中写⼀些Public的变量,有可能不同的Prefab上绑定的是相同的脚本,只是Inspector 脚本中的public参数不同。别担⼼这⼀点Assetbundle 中的Prefab也是没问题,所以说只要⼤版本中的脚本没问题,在⼩版本中只更新游戏资源是⼀点问题都么有的。
5.移动优化
之前我们说过可以将游戏中的某个游戏对象封装成Assetbundle,也可以将游戏中的整个场景也封装成Assetbundle。但是我认为需要巧妙的使⽤封装场景,因为场景中肯定有很多公⽤的模型,如果打包场
景的话那么内存与size就是 公⽤模型的size * N个场景,想想其实挺恐怖的。其实我们可以巧妙的使⽤,⾸先把场景中公⽤的部分和私有的部分统统放⼊Unity, 然后烘培整个场景。 当场景烘培完毕后把公⽤的模型部分在拿出去,场景只只保留私有的模型。还可以做⼀个⼯具将公⽤模型在场景中的坐标保存在XML中(每个场景⽂件会对应⼀个公⽤模型的XML信息),最后在将公⽤的模型分别封装在别的Assetbundle中。
服务器上提供每个场景的Assetbundle ,和公⽤模型的Assetbundle,⼀般公⽤模型的Assetbundle可以放在常驻内存中(可能使⽤频繁、根据项⽬的不同⽽定)场景Assetbundle下载完毕后,现载⼊场景然后在根据场景对应的XML信息将公⽤模型部分动态的在添加到场景中,这样就完成了⼀个场景的构建。
6.总结
对游戏中所有资源进⾏打包,⽐如按类型分为五个⼤部分 界⾯,模型,特效,声⾳,场景,脚本。
界⾯部分:
公⽤资源包(可复⽤的资源包)和 每个界⾯独有得资源包(不可复⽤的资源包)统⼀使⽤Prefab 打包成.assetbundle ⼆进制格式。
模型部分:
按⾓⾊分类,统⼀使⽤Prefab 打包成.assetbundle ⼆进制格式。 模型部分包括模型⽂件与动画⽂件,每⼀个模型⽂件对应⼀组动画⽂件。(如果模型需要换装还需提供对应换装的模型与贴图) ,因为unity4的重定向动画不⽀持动态加载,所以⽬前不需要考虑 不同⼤⼩ 不同规格 不同性别 的模型重定向动画。
同规格 不同性别 的模型重定向动画。
特效部分: 统⼀使⽤Prefab 打包成.assetbundle ⼆进制格式。
声⾳部分: 统⼀使⽤Prefab 打包成.assetbundle ⼆进制格式。
场景部分:场景和前⾯的有点区别,场景需要导出烘培的光信息并且只能烘培场景之上永远不动的模型,但是这些永远不动的模型有可能会同时在多个场景中使⽤,所以场景烘培完毕后要把重复使⽤的对象删除,(运⾏游戏在动态的加载进来)场景中只保留该场景中永远不会变的模型,以及烘培的光照信息。 打包场景后会⽣成.unity3D ⼆进制格式,它和 assetbundle 打包⽅式是不同的。(另外,也可以考虑json xml ⼆进制 来动态组装场景)。
脚本部分:如果Prefab上是带脚本打包Assetbundle的话 脚本是不会被运⾏的(移动平台), 但是unit
y有⼀个技巧,Prefab上的脚本 如果本地有的话它会把本地的同名脚本绑定在Prefab对象上,它会很好的执⾏。
Prefab打包技巧: Prefab打包时⾃⾝是不占多少空间的 <=1KB 但是Prefab上是可以关联 这五⼤部分 “界⾯,模型,特效,声⾳,场景,脚本”以及在Hierarchy视图中 坐标/缩放/旋转。 关联这些信息以后就会很⼤,所以为了避免资源的浪费尽量避免Prefab重复关联。
⼀个prefab下⾯可以同时关联多个游戏对象 ,这⾥举个例⼦如果你的 Prefab下⾯放了⼀个模型 它的⼤⼩可能是500k ,在 Prefab下⾯放了⼗个完全相同模型 它的⼤⼩可能是501k 。 如果Prefab下⾯放了两个不同的模型,它的⼤⼩可能就会是 500k x 2 的size ,也就是说Prefab与关联的数量是⽆关的 。
加密部分: assetbundle 是可以转换成 字节数组 ,客户端与服务器约定⼀组解密 字节数组的算法就可以实现资源加密。
⼤版本升级:
unity的版本升级其实主要是升级主程序中的脚本。 因为所有的资源都是assetbundle 和 .unity3d 这些资源放在本地或者服务器 解包的⽅式是完全⼀样,所以理论上我们的主程序包的⼤⼩可以做到很⼩,可以很好设置把多少资源放在包⾥ 或者把所少资源放在服务器上。在运⾏的时候服务端应该把所有 assetbundle 和 .unity3d的资源⽂件的下载地址列表返回给客户端。
⼩版本升级:
⼩版本升级也就是更新资源,因为不能更新脚本, 在登陆的时候服务端应该把所有 assetbundle 和 .unity3d的资源⽂件的下载地址列表返回给客户端。
还有个需要考虑的地⽅,⽐如现在⼤版本是2.0.0 ,⼩版本已经是2.0.5 ,⽤户的⼿机上是⼀个1.5.0的包。 此时⽤户在打开游戏的时候 应当强制它去appstore中去下载⼤版本2.0.0 ,当⽤户下载完毕后登陆游戏,此时服务器告诉客户端现在已经是2.0.5的⼩版本了,这时候客户端去下载对应⼩版本的所有 assetbundle 和 .unity3d⽂件地址列表。
增量更新:理论上增量更新是可⾏的。因为unity不能更新脚本,所以在处理增量更新的话 需要在代码中做可以兼容增量更新的可能。
因为Assetbundle这块的代码⽐较多,我还是决定分成两篇⽂章来写,这篇⽂章先说原理、下篇⽂章说代码。欢迎⼤家来讨论!
前⼏天我和Unity鑫哥聊天,他告诉我IOS上是⽆法运⾏时更新脚本、但是Android上是可以运⾏时更新脚本,我回家也试了⼀下但是没能成功,后来我考虑即使成功了项⽬中我也不打算那么做,因为这样Android和IOS 做起来的差别就太多了, 另外Unity商店中有⼀个处理运⾏时更新脚本的插件 unityLua ⼤家可以去研究研究。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论