通常在游戏执行过程中,并不希望一次将全部的资源都载入。而是在资源被使用的时候载入,以免占用过多的存储空间。所以我们可能会尽量规划好不同功能的场景,在需要的时候才载入场景并释放掉前个场景中不需要的资源。或是将资源放在Resources文件下,在需要使用的时候用Resources.Load()方法把资源载入。这些者是不错的管理方法,但是当我们游戏中的资源相当多时,输出的程序文件还是会很大。而且如果是时常会更新内容资源的游戏,也会因为过大的资源造成编译输出过长的后果,特别是手机或是网页游戏。最后编译出来的只是一个文件,如果文件非常大,将可能造成下载或启动游戏的不方便;这时候我们可能就要考虑制作AssetBundle。
制作游戏时,将资源和主程序部分分离的话,可以得到某种程度的好处,我们可以让游戏主程序只管理游戏的逻辑部分。当需要某些资源则从外部的AssetBundle载入资源,这样一来,如果有任何游戏更新只是添加或更换资源,并没有变更到程序的逻辑部分。只需要重新打包资源就可以完成更新,而不需要重新编译主程序并重新发布。
例如我们有已上架的iPhone游戏,因为特定节日想把游戏内的图片换成另一种气氛。通常必须要重新输出,然后送审。等到节日过后又想把图片换回原本的,要再次编译新的程序,再送审,一方面送审费时费工,另一方面是从送审到架上游戏更新完成的时间,我们并不能准确掌控;如果事先将资源分离打包成AssetBundle存储在我们自己管理的服务器上,在这种需要换图的时刻,只需要将自己服务器上的AssetBundle更新即可,可以省掉不断送审的麻烦,时间上也更能掌控,另外就是游戏主程序中没有大的资源,主程序会小一点,那么玩家要下载我们的游戏也会更快速一些。
还有就是,我们编译网页游戏时一定会发现,编译出来的只是一个html文件和一个unity3d文件。写过网页的人都知道,网页的运作是浏览器将网页中的文件、图片、音乐、视频等等下载到客户端暂存之后再执行呈现出结果,那么如果我们游戏内容的庞大资源与主程序编在一起的话,那么编译出来的unity3d文件也会很大,此时我们可能要思考一下,我们希望玩家花多久时间完成启动页面再进行游戏呢?也许,Unity的Web Player有办法解决这个问题使玩家不会花太多时间启动页面(传说中流模式),但是正因为网页游戏运作方式是如此,所以当我们在服务器更新时,玩家只要没有重新载入页面,玩到的游戏内容始终是旧版的,此时又要考虑到,如果没有更新游戏逻辑或内容规则的情况下,是否有必要强制玩家中断游戏重新载入页面;如果将资源打包成AssetBundle分离出来,那么unity3d文件这个主程序的部分将会变得比较小,而当有任何资源更新时只需要在服务器端将AssetBundle换掉,玩家不需要重新载入页面,一样能体验到更新后的内容。
以上写了一大堆,主要就是要说明AssetBundle是个很重要又很好用的东西,毕竟在没变更程序或游戏部署的情况下, 没必要去重新编译游戏主程序,以下就来说明如何制作AssetBundle:
首先要使用[Unity插件:自定义菜单命令]的方法自定义菜单命令,使我们能够何时去打包。接下来我们需要能够指定哪些资源进行打包,所以需要使用到Editor class的Selection.GetFiltered(),还有就是要过滤哪些类型的资源才是我们要的,选择错误略过不处理,最后使用BuildPipeline.BuilAssetBundle()建立AssetBundle即可,以下范例会在Unity项目目录下建立_AssetBundle目录用来存放打包好的AssetBundle,打包的目标为GameObject, Texture2D, Material三种类别的资源,如果存储文件路径名相同则会先删除旧文件。
制作好AssetBundle最终目的还是要在游戏中载入,以下用简短范例在游戏画面上显示一个按钮,当按下按钮之后载入类别为GameObject的AsseBundle并利用Instantiate()使其实例化而在场景中产生GameObject。
因为载入的资源必须要等待载入完成以确保游戏运作正常,在C#中使用yield除了返回类型要是IEnumerator之外,调用时也必须使用StartCoroutine()调用,如果使用Javascript则可直接调用函数。如果AssetBundle已载入过,下次再请求载入将会出现[The asset bundle ‘文件名’ can’t be loaded because another asset bundle with the same files are already loaded]的错误信息,所以必须在每次载入使用完后,将AssetBundle卸载(bundle.assetBundle.Unload(false));另外,要特别注意的是载入AssetBundle到卸载期间可能消耗些微时间,如果连续调用载入同一个AssetBundle,造成在卸载前重复载入也可能出现前面的错误,所以,最好是能定义些变量,记录载入的AssetBundle有哪些,避免重复载入。
打包的脚本:
- using UnityEngine;
- using System.Collections;
- using UnityEditor;
- public class Test : Editor
- {
- [MenuItem("Custom Editor/Create AssetBunldes Main")]
- static void CreateAssetBunldesMain ()
- {
- //获取在Project视图中选择的所有游戏对象
- Object[] SelectedAsset = Selection.GetFiltered (typeof(Object), SelectionMode.DeepAssets);
- //遍历所有的游戏对象
- foreach (Object obj in SelectedAsset)
- {
- //本地测试:建议最后将Assetbundle放在StreamingAssets文件夹下,如果没有就创建一个,因为移动平台下只能读取这个路径
- //StreamingAssets是只读路径,不能写入
- //服务器下载:就不需要放在这里,服务器上客户端用www类进行下载。
- string targetPath = Application.dataPath + "/StreamingAssets/" + obj.name + ".assetbundle";
- //安卓建立 AssetBundle
//BuildPipeline.BuildAssetBundle(obj, null,targetPath, BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets, BuildTarget.Android);
//苹果建立 AssetBundle
//BuildPipeline.BuildAssetBundle(obj, null, targetPath, BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets, BuildTarget.iPhone);
//建立 AssetBundle
- if (BuildPipeline.BuildAssetBundle (obj, null, targetPath, BuildAssetBundleOptions.CollectDependencies)) {
- Debug.Log(obj.name +"资源打包成功");
- }
- else
- {
- Debug.Log(obj.name +"资源打包失败");
- }
- }
- //刷新编辑器
- AssetDatabase.Refresh ();
- }
- [MenuItem("Custom Editor/Create AssetBunldes ALL")]
- static void CreateAssetBunldesALL ()
- {
- Caching.CleanCache ();
- string Path = Application.dataPath + "/StreamingAssets/ALL.assetbundle";
- Object[] SelectedAsset = Selection.GetFiltered (typeof(Object), SelectionMode.DeepAssets);
- foreach (Object obj in SelectedAsset)
- {
- Debug.Log ("Create AssetBunldes name :" + obj);
- }
- //这里注意第二个参数就行
- if (BuildPipeline.BuildAssetBundle (null, SelectedAsset, Path, BuildAssetBundleOptions.CollectDependencies)) {
- AssetDatabase.Refresh ();
- } else {
- }
- }
- [MenuItem("Custom Editor/Create Scene")]
- static void CreateSceneALL ()
- {
- //清空一下缓存
- Caching.CleanCache();
- string Path = Application.dataPath + "/MyScene.unity3d";
- string []levels = {"Assets/Level.unity"};
- //打包场景
- BuildPipeline.BuildPlayer( levels, Path,BuildTarget.WebPlayer, BuildOptions.BuildAdditionalStreamedScenes);
- AssetDatabase.Refresh ();
- }
- }
下载类WWW
WWW bundle = new WWW(path);
这样的做法是通过一个路径进行下载(无论是服务器路径还是本地路径下载操作都一样)但是bundle只能保存在内存中,也就是退出游戏在进入还得重新下,很显然在游戏中我们不能使用这种方式。
IEnumerator loadBundleOBject(string url)
{
WWW date = WWW.LoadFromCacheOrDownload(url,5);
yield return date;
Instantiate(date.assetBundle.mainAsset);//实例化加载的资源,我加载的是个模型。
date.assetBundle.Unload(false);
}
使用的方法是WWW.LoadFromCacheOrDownload(path,5);参数1:服务器或者本地下载地址 参数2:版本号Unity会下载Assetbundle本地中,它的工作原理是先通过(版本号和下载地址)先在本地去找看有没有这个Assetbundle,如果有直接返回对象,如果没有的话,在根据这个下载地址重新从服务器或者本地下载。这里版本号起到了很重要的作用,举个例子,同一下载地址版本号为1的时候已经下载到本地,此时将版本号的参数改成2 那么它又会重新下载,如果还保持版本号为1那么它会从本地读取,因为本地已经有版本号为1的这个Assetbundle了。你不用担心你的资源本地下载过多,也不用自己手动删除他们,这一切的一切Unity会帮我们自动完成,它会自动删除掉下载后最不常用的Assetbundle ,如果下次需要使用的话只要提供下载地址和版本后它会重新下载。(转自雨松MOMO研究院)
Assetbundle 中的脚本,在移动平台下Assetbundle里面放的脚本是不会被执行的,在手机上将Assetbundle下载到本地后,加载进游戏中Prefab会自动在本地找它身上挂着的脚本,他是根据脚本的名来寻找,如果本地有这条脚本的话,Prefab会把这个脚本重新绑定在自身,并且会把打包前的参数传递进来。如果本地没有,身上挂的条脚本永远都不会被执行。(转自雨松MOMO研究院)
最后再说说我遇到的问题,本地加载.unity3d的资源没问题,但是我挂到IIS上就失败了,显示404错误,找不到资源,无法实例化。这就需要配置服务器的MIME
加载脚本:
- private IEnumerator LoadScene()
- {
- WWW download = WWW.LoadFromCacheOrDownload("http://192.168.1.200/xiaowei/dxw.assetbundle", 1);
- yield return download;
- //var bundle = download.assetBundle;
- //yield return Instantiate(download.assetBundle.mainAsset);
- //GameObject obj = (GameObject)download.assetBundle.Load("dxw");
- if (download.assetBundle.Contains("dxw"))
- {
- print(1);
- obj = (GameObject)download.assetBundle.Load("dxw");
- print(obj);
- obj.transform.Rotate(new Vector3(0,0,360));
- }
- //Application.LoadLevel("Level");
- //卸载 AssetBundle
- //download.assetBundle.Unload(false);
- }