首页 > 代码库 > Unity3D教程:制作与载入AssetBundle

Unity3D教程:制作与载入AssetBundle

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
通常我们在游戏程式执行过程,并不希望一次将全部的资源都载入,而比较希望实际上有使用到的才载入,以免占用多余的记忆体,所以我们可能会尽量规划好不同功能的场景,在需要时才载入场景并释放掉前个场景中不需要的资源,或是将资源放在 Resource 资料夹中,在真正需要时才利用 Resources.Load() 把资源载入;这些都是不错的管理方法,但是当我们游戏中的资源相当多时,输出的程式还是会相当庞大,而且如果是时常会更新内容资源的游戏,也会因为庞大的资源而造成编译输出时要花相当多的时间;特别是手机或网页游戏,几乎输出最後的结果只是一个档案而已,这个档案如果很庞大,将可能造成下载或启动游戏的不方便;这时候我们可能就要考虑制作 AssetBundle。
 
制作与游戏主程式分离的资源包可以得到某程度上的好处,我们可以让游戏主程式只管理游戏逻辑的部份,当有需要某些资源则从外部的 AssetBundle 载入资源,这样一来,如果有任何游戏更新只是添加或更换资源,并没有变更到游戏程式逻辑或内容部署,我们只需要重新打包资源的部份就能完成更新,而不再需要整个游戏重新编译输出。
 
例如我们有已上架的 iPhone 游戏,因为特定节日想把游戏内的图片换成另一种气氛,通常就必须要重新输出,然後送审,等节日过後又想把图片换回原本的,要再次输出游戏,再送审,一方面送审费时费工,另一方面是从送审到架上游戏更新完成的时间,我们并不能准确掌控;如果事先将资源分离打包成 AssetBundle 储存在我们自己管理的伺服器中,在这种需要换图的时刻,只需要打包自行在伺服器中将 AssetBundle 更新即可,可以省掉不断送审的麻烦,时间上也更能掌控,另外就是游戏主程式中没有庞大的资源资料,主程式也就会小一点,那麽玩家要下载我们的游戏也会更快速些。
 
还有就是,我们输出为网页游戏时一定会发现到,输出结果只是一个 html 档及一个 unity3d 档,应该有写过网页的人都知道,网页的运作是浏览器将网页中的文件、图片、音乐、影像… 等等的资料下载到客户端暂存之後再执行呈现出结果,那麽如果我们游戏内容的庞大资源都与主程式编在一起的话,那麽输出後的 unity3d 档应该也会不小,此时我们可能要思考一下,我们希望玩家花多久时间完成启动页面再进行游戏呢?也许 Unity 的 Web Player 有办法解决这个问题使玩家不会花太多时间启动页面,但是正因为网页的运作方式是如此,所以当我们在伺服器端更新时,玩家只要没有重新载入页面,玩到的游戏内容始终是旧版的,此时又要考虑到,如果没有更新游戏逻辑或内容规则的情况下,是否有必要强制玩家中断游戏重新载入页面;如果将资源都打包成 AssetBundle 分离出来,那麽 unity3d 档这个主程式的部份将会变得更小,而当有任何资源更新时只需要在伺服器端将 AssetBundle 的档案换掉,玩家不需要重新载入页面一样能体验到更新後的内容。
 
以上主要说明 AssetBundle 是个很重要又好用的东西,毕竟在没变更程式或游戏部署的情况下,没必要去重新 Build 游戏主程式,以下就来说明如何制作 AssetBundle …
 
首先要自定义选单命令,使我们能够操作何时开始制作 AssetBundle 及如何做,接下来我们需要能够指定哪些资源将打包成 AssetBundle,所以需要使用到 Editor class 的 Selection.GetFiltered(),还有就是要过滤哪些型别的资源才是我们要的,选择错误则略过,不要处理,最後再使用 BuildPipeline.BuildAssetBundle() 建立 AssetBundle 即可,以下范例会在 Unity 专案资料夹(不是 Asset 资料夹)中建立 _AssetBunldes 资料夹用来存放打包好的 AssetBundle,打包的目标为 GameObject、Texture2D、Material 三种型别的资源,如果储存档案路径档名相同则会先删除旧档:
 
             
[MenuItem("Custom Editor/Create AssetBunldes")]
     
static void ExecCreateAssetBunldes(){
         
// AssetBundle 的資料夾名稱及副檔名  
string targetDir = "_AssetBunldes";
string extensionName = ".assetBunldes";
  
//取得在 Project 視窗中選擇的資源(包含資料夾的子目錄中的資源)  
Object[] SelectedAsset = Selection.GetFiltered(typeof (Object), SelectionMode.DeepAssets); 
     
//建立存放 AssetBundle 的資料夾
if(!Directory.Exists(targetDir)) Directory.CreateDirectory(targetDir); 
foreach(Object obj in SelectedAsset){  
//資源檔案路徑   
string sourcePath = AssetDatabase.GetAssetPath(obj);   
// AssetBundle 儲存檔案路徑  
string targetPath = targetDir + Path.DirectorySeparatorChar + obj.name + extensionName;
if(File.Exists(targetPath)) File.Delete(targetPath);   
if(!(obj is GameObject) && !(obj is Texture2D) && !(obj is Material)) continue;
//建立 AssetBundle
if(BuildPipeline.BuildAssetBundle(obj, null, targetPath, BuildAssetBundleOptions.CollectDependencies)){
Debug.Log(obj.name + " 建立完成"); 
}else
Debug.Log(obj.name + " 建立失敗"); 
}  
}  
}
以上是直接将选择的资源打包成 AssetBundle,另外,你也可以在打包前依需求将型别为 GameObject 的资源 Instantiate 到场景中添加需要的 Component 并回到 Project 中建立 Prefab,最後再以这个 Prefab 打包成 AssetBundle,一切就看个人如何运用了。
 
制作好的 AssetBundle 最终目的还是要在游戏中载入,以下用简短的范例在游戏画面上显示一个按钮,当按下按钮之后载入型别为 GameObject 的 AssetBundle 并利用 Instantiate() 使其实例化而在场景中产生 GameObject:
 
             
void OnGUI(){  
if(GUI.Button(new Rect(5,35,100,25) , "Load GameObject")){ 
StartCoroutine(LoadGameObject());  
}  
}
     
private IEnumerator LoadGameObject(){  
// AssetBundle 檔案路徑
string path = string.Format("file://{0}/../_AssetBunldes/{1}.assetBunldes" , Application.dataPath , "TestGameObject"); 
//  載入 AssetBundle 
WWW bundle = new WWW(path);
//等待載入完成   
yield return bundle;
     
//實例化 GameObject 並等待實作完成   
yield return Instantiate(bundle.assetBundle.mainAsset);
     
//卸載 AssetBundle   
bundle.assetBundle.Unload(false);
}
范例中的档案路径(path)字串是以此程式码在 Unity 编辑器中执行为例,实作时应该以实际执行平台及个人喜好指定 AssetBundle 存放路径,详情可参考官方文件 Application.dataPath 说明;因为载入的资源必须要等待载入完成以确保游戏运作正常,在 C# 使用 yield 除了回传型别为 IEnumerator 之外,调用时也必须使用 StartCoroutine() 调用,如果使用 Javascript 则可直接呼叫该 function;如果 AssetBundle 已载入过,下次再请求载入将会出现 [The asset bundle ‘档名‘ can‘t be loaded because another asset bundle with the same files are already loaded]的错误讯息,即使使用区域变数(范例中的 bundle)仍然会得到这个错误讯息,所以必须在每次载入使用完后将 AssetBundle 卸载;另外,要特别注意的是载入 AssetBundle 到卸载期间可能消耗些微时间,如果连续调用载入同一个 AssetBundle,造成在卸载前重复载入也可能出现前面的错误讯息,所以,最好是能定义个阵列或其它变数来记录载入中的 AssetBundle 有哪些,以此变数内容在载入 AssetBundle 前检查。