Unity5新的AssetBundle系統使用心得

        Unity的AssetBundle系統是對資源管理的一個擴展,動態更新,網頁遊戲,資源下載都是基於AssetBundle系統的。但是不得不說,這個系統非常噁心,坑很深。至於有多深,請看這篇文章: http://www.cnblogs.com/ybgame/p/3973177.html

        原先的AssetBundle系統需要自己寫一大坨導出的代碼(BuildPipeline),這個新手會無從下手,老手也經常會被坑到。想正確處理好資源的依賴關係從而保證資源完整而又不會產生重複資源,確實不是一件非常容易的事情。       

        Unity5新的AssetBundle系統大大簡化了這一操作。Unity打包的時候會自動處理依賴關係,並生成一個.manifest文件,這個文件描述了assetbundle包大小、crc驗證、包之間的依賴關係等等,是一個文本文件。加載資源的時候Unity會自動處理好其依賴包的加載。(參見最後的補充,Unity並沒有如此智能,新的assetbundle簡化很多,但是也有一些新的坑)

        打包代碼簡化爲一個函數(其實也沒什麼必要了,因爲流程固定了,做成內嵌的菜單選項也沒什麼影響)

BuildPipeline.BuildAssetBundles(outputPath);

       執行這個函數,它會自動打包工程目錄下的所有的assetbundle,函數足夠智能,它只會打包有修改的資源。

       如何添加一個AssetBundle呢?

       很簡單,在資源屬性窗口底部有一個選項,這個地方設置AssetBundle的名字。它會修改資源對應的.meta文件,記錄這個名字。 AssetBundle的名字固定爲小寫。另外,每個AssetBundle都可以設置一個Variant,其實就是一個後綴,實際AssetBundle的名字會添加這個後綴。如果有不同分辨率的同名資源,可以使用這個來做區分。

      



      我手頭的模型資源非常多,所以我又寫了個腳本自動遍歷prefab的meta文件,添加AssetBundle名字。有一個需要注意的地方就是.meta文件貌似權限問題,無法直接寫入,需要刪除原文件,然後使用新的文件替換。。

# -*- coding: utf-8 -*-
import os, sys, shutil;

EXT_LIST = ['.prefab.meta', '.png.meta', '.jpg.meta'];

def doWork(path):
    for root, dirs, files in os.walk(path):
        for file in files:
        	for ext in EXT_LIST:
	            if file.endswith(ext):
	                fullPath = os.path.join(root, file)
	                fullPath = fullPath.replace('\\', '/')
	                prefabName = fullPath.replace(path, '');
	                prefabName = prefabName[:prefabName.find('.')] + '.data';
	                
	                fileData = [];
	                fp = open(fullPath, 'r');
	                for line in fp:
	                	if line.find('assetBundleName:') != -1:
	                		fileData.append('  assetBundleName: ' + prefabName.lower() + '\n');
	                	else:
	                		fileData.append(line);
	                fp.close();
	                # os.remove(fullPath);
	                # return;
	                fpw = open(fullPath + '.tmp', 'w');
	                fpw.writelines(fileData);
	                fpw.close();
	                os.remove(fullPath)
	                shutil.copy(fullPath + '.tmp', fullPath);
	                os.remove(fullPath + '.tmp')
	                break;


doWork(r'Assets/Resources/Prefab/')
os.system('PAUSE')

c#編輯器擴展(與python代碼功能一樣,喜歡哪個用哪個)

public class ExportAssetBundles : Editor
{
    // 設置assetbundle的名字(修改meta文件)
    [MenuItem("Tools/SetAssetBundleName")]
    static void OnSetAssetBundleName()
    {

        UnityEngine.Object obj = Selection.activeObject;
        string path = AssetDatabase.GetAssetPath(Selection.activeObject);

        string[] extList = new string[] { ".prefab.meta", ".png.meta", ".jpg.meta" , ".tga.meta" };
        EditorUtil.Walk(path, extList, DoSetAssetBundleName);

        //刷新編輯器
        AssetDatabase.Refresh();
        Debug.Log("AssetBundleName修改完畢");
    }

    static void DoSetAssetBundleName(string path)
    {
        path = path.Replace("\\", "/");
        int index = path.IndexOf(EditorConfig.PREFAB_PATH);
        string relativePath = path.Substring(path.IndexOf(EditorConfig.PREFAB_PATH) + EditorConfig.PREFAB_PATH.Length);
        string prefabName = relativePath.Substring(0, relativePath.IndexOf('.')) + EditorConfig.ASSETBUNDLE;
        StreamReader fs = new StreamReader(path);
        List<string> ret = new List<string>();
        string line;
        while((line = fs.ReadLine()) != null) {
            line = line.Replace("\n", "");
            if (line.IndexOf("assetBundleName:") != -1) {
                line = "  assetBundleName: " + prefabName.ToLower();

            }
            ret.Add(line);
        }
        fs.Close();

        File.Delete(path);

        StreamWriter writer = new StreamWriter(path + ".tmp");
        foreach (var each in ret) {
            writer.WriteLine(each);
        }
        writer.Close();

        File.Copy(path + ".tmp", path);
        File.Delete(path + ".tmp");
    }

    [MenuItem("Tools/CreateAssetBundle")]
    static void OnCreateAssetBundle()
    {
        BuildPipeline.BuildAssetBundles(EditorConfig.OUTPUT_PATH);

        //刷新編輯器
        AssetDatabase.Refresh();
        Debug.Log("AssetBundle打包完畢");
    }
}


補充:

        之前我簡單的認爲Unity既然已經做了資源依賴鏈,那麼加載資源的時候理所應當的會自動處理好依賴關係。結果是我想的太簡單了。我們必須自己手動加載依賴的assetbundle。

        我們直接調用打包函數,在打包目錄會生成一個與打包目錄同名的文件,比如我們打包到AssetsBundle/windows目錄下,則此目錄下會生成一個windows文件,以及一個windows.manifest文件。

        這個windows文件就包含了當前assetbundle的列表和依賴關係。我們需要在遊戲開始的時候手動加載這個assetbundle(即windows),獲取一個AssetBundleManifest對象。這個對象有GetAllAsssetBundles()函數還有一個GetAllDependencies函數。

        我們可以使用GetAllDependencies傳入一個assetbundle的名字來獲取其依賴的assetbundle,然後分別加載這些assetbundle。注意傳入的文件名是我們生成assetbundle設置的名字,而不是全路徑。可以參考windows.manifest文件查看assetbundle名字形式。

        新的問題:

       由於我的資源量比較大,所以我拆分了多個生成工程。而生成assetbundle的時候指向的是一個路徑,最終合併後就是一個完整的遊戲資源包。我們在遊戲運行加載資源的時候需要windows這個manifest文件,而它又是Unity自己生成的,並且只保存了當前項目生成的assetbundle信息,無法自動合併。從這點來說它無法滿足我的需求。

       我們之所以需要這個manifest文件,是因爲我們要從中獲取依賴關係。所以我在項目中自己維護資源列表。它可以根據生成的manifest自動生成、合併資源列表(一個json格式的文本文件)。我們在遊戲開始時解析這個json文件獲取到資源列表。這個資源列表同時也可以作爲自動更新的文件列表。

      實現方式很簡單,assetbundle打包完畢後,加載manifest文件(注意這裏有個坑,我會在另一篇文章裏面講解如何在Editor下加載AssetBundle),獲取所有資源信息,與當前已存在的資源列表進行比對,合併。多個項目之間只要維護好一份統一的資源列表就可以了。


補充2:

       1、似乎沒有必要給所有的模型創建prefab來使用,我們可以直接把fbx打進AssetBundle,然後實例化這個fbx就可以得到正確的GameObject。這個在我的地圖文件裏面非常必要,因爲地圖文件很小很碎很多,如果非要創建prefab的話,可能prefab都要佔幾十兆的空間。而在使用過程中,理論上我不會對這種模型做特殊的修改,僅僅是當做資源來使用。  其實人物等模型也是如此,如果僅僅當做資源來使用的話,是沒有必要創建prefab的。不過如果要綁定一些腳本(理論上也可以通過代碼來搞定),設置物理參數就需要一個prefab來保存這些信息了。

       2、不可以給一個文件夾設置assetbundle名字,會報錯。  把文件夾下面的所有文件設置成一樣的名字,就可以把這些資源打包到一個assetbundle裏面。

       3、所有可能共用的資源(比如公共紋理,尤其是shader)一定要設置assetbundle的名字,這樣這些會單獨打包,並且其他使用這些資源的assetbundle會自動依賴這些包。如果shader不設置打包的話,則每個assetbundle裏面都有一份shader,這樣就浪費了很多空間,並且當修改shader內容的時候,所有的包都需要重新制作。

       4、加載完assetbundle,然後使用LoadAsset加載資源。傳入的名字要麼是以Assets/開頭的全路徑,要麼只有一個文件名。其他形式的名字無法正確匹配到對應資源。如果只有一個文件名,要保證這個assetbundle內不包含同名,同類型的文件,否則會返回第一個查找到的資源。  名字大小寫無關

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章