1. 程式人生 > >Unity遊戲執行資源的製作與下載

Unity遊戲執行資源的製作與下載

我們的遊戲製作完釋出出去提供給玩家,為了給玩家帶來更好的遊戲體驗,要做各種的優化以及設計,首先,遊戲資源的載入就是一個非常重要的方面(尤其是網頁遊戲)。由於我們的遊戲資源比較大,不能一下全部加載出來,如果是這樣,可能會造成玩家長時間的等待。所以我們應該採取動態載入的方式,讓玩家在玩遊戲的過程中來一點一點從伺服器載入遊戲資源。要實現這樣的效果,首先就必須要製作用於一點點載入的遊戲資源。

(注:本文只是談及這些遊戲資源的製作和下載,關於遊戲執行中的動態載入不做討論)

(再注:本文涉及到的程式碼都是以C#語言來編寫的)

開發環境:

Windows 7

Unity3D 3.5.1f2

本文中將會涉及到以下的內容:

3、 匯出功能的具體實現

4、 資源的下載

5、 下載後使用

1、 UnityEditor名稱空間

這個名稱空間下的類是在Unity的編輯模式下使用的,我們可以用它來製作各種小工具來輔助開發,提高開發效率。這裡的所有的類都不能在Unity的執行時裡使用。只能在編輯器下使用,並且在使用他們的時候還必須要放到專案Project檢視下的Editor資料夾中。需要注意一點的就是,我們的專案程式碼裡如果有使用到UnityEditor名稱空間時,在專案的最後編譯是不能通過的,必須要移除他們。

我們來看一個我們即將使用到的一個Attribute:

MenuItem是UnityEditor名稱空間下的一個屬性標誌,它可以定義出一個選單條目,並新增在Unity編輯器的選單欄中,語法:

[MenuItem(“Tools/Export”)]

我們來新建一個工程看一下效果(具體建立步驟這裡真的不說了)

(注:我的專案中加了很多裝飾性的東西,這裡就不一一說明怎麼實現了哈)

完成之後,先在Project下建立Editor資料夾並建立一個指令碼檔案,輸入以下內容:


using UnityEditor;
using UnityEngine;
using System.Collections;

/// <summary>
/// author : qyxls
/// </summary>
public class ExportTools : MonoBehaviour 
{
	[MenuItem("Tools/Export")]
	static void Execute () 
	{
		Debug.Log("Menu is selected !!");
	}
}
當我們點選選單欄上的對應選單選項:ToolsàExport時,

選單項會呼叫靜態的Execute()方法,即可在Console面板中打印出”Menu is selected”。


這裡要注意兩點:

1、 引入UnityEditor名稱空間。

2、 MenuItem要呼叫的方法需要是static的。

關於UnityEditor的更多詳細內容,請參照官方文件,這裡不做重點講解。

2、Editor模式下視窗製作

       要製作一個小工具,提供出一個友好介面是很有必要的。UnityEditor下的類可以很方便的完成這一需求。我們通過這些類,可以實現各種不同的控制元件:


怎麼樣,還算豐富吧?這些控制元件的具體實現我不想說,請自行檢視API吧。

這裡我還是遵循本文的主旨,圍繞本文的中心思想(本文我們是要匯出資源到伺服器,並在遊戲中下載這個資源過來使用)實現一個介面。

用例描述:

匯出場景中的一個模型,並帶著預設材質,如果該模型有多個可替換的貼圖,也把這些貼圖作為該模型的資源一併匯出到一個資源包中。

按照這個需求,我猜想介面應該是這樣的:

一個匯出模型的口,一個提供可選貼圖數量的口,根據使用者輸入的可選數量,給提供出對應的貼圖匯出口,最後填寫完畢之後有一個按鈕用於匯出互動。


大笑,不好意思,這哪裡是猜想,我其實早就寫好了。其實也沒騙你了,我在寫之前是猜想的!

要實現上面這個視窗,我該怎麼做呢?

       首先,定義一個繼承EditorWindow的類,然後,重寫OnGUI方法即可。我們這裡在之前的程式碼基礎上做修改新增:

using UnityEditor;
using UnityEngine;

/// <summary>
/// author : qyxls
/// </summary>
public class ExportTools : EditorWindow 
{
	[MenuItem("Tools/Export")]
	static void Execute () 
	{
// 例項化一個Window視窗 //
		ExportTools windows = EditorWindow.GetWindow<ExportTools>(true, "Export Tools");
	}
	
	void OnGUI()
	{
		
	}
}

這裡要注意的就是將原來的指令碼有繼承自MonoBehaviour 修改為繼承自EditorWindow。並在Execute ()方法中對當前的Window例項化。這時我們就可以得到一個Window視窗了:

其次,就是向我們生成的視窗中新增不同的控制元件,這些控制元件的生成都是在OnGUI()方法中實現的。和MonoBehaviour的OnGUI方法一樣,EditorWindow的OnGUI()方法也主要是處理UI的,我們關於UI控制元件的生成處理都要寫在這個方法裡。OnGUI()這個方法每幀呼叫好幾次(每個事件一次),所以一些邏輯處理要避免在這裡呼叫。

	private string savePath;
	private GameObject exportObject;
	private int optionalCount = 0;
	private Texture2D[] optionalTexture = new Texture2D[0];
	
	void OnGUI()
	{
		/*
		 * ObjectField:
		 * 是這裡的第一個控制元件,它可以允許使用者拖拽將一個Object的物件賦給它。
		 * 如果要限制可接收的物件型別,可以通過第三個引數來限制類型這裡表示直接收GameObject型別
		 * 第四個bool型的引數標誌能否接受當前scene裡的物件,true表示接受
		 * 這個方法返回的是一個Object型別的值,最後要將它轉化為需要的型別
		 */
		exportObject = EditorGUILayout.ObjectField("Export Object", exportObject, 
													typeof(GameObject), true) 
													as GameObject;
		// 就相當於提供一個換行,用於格式化控制元件的 //
		EditorGUILayout.Space();
		// IntField:該控制元件只能輸入 int 型別的值//
		optionalCount = EditorGUILayout.IntField("Optional Count", optionalCount);
		for(int i=0; i<optionalCount; i++)
		{
			if(optionalTexture.Length != optionalCount)
			{
				optionalTexture = new Texture2D[optionalCount];
			}
			
			EditorGUILayout.Space();
			// 這裡將 ObjectField 限制只接受Texture2D型別的值 //
			optionalTexture[i] = EditorGUILayout.ObjectField("Optional Textures " + i, optionalTexture[i], 
															  typeof(Texture2D), false) 
															  as Texture2D;
		}
		
		EditorGUILayout.Space();
		EditorGUILayout.Space();
		
		EditorGUILayout.BeginHorizontal();
		EditorGUILayout.Space();
		// 匯出按鈕 //
		if(GUILayout.Button("EXPORT", GUILayout.Width(100), GUILayout.Height(20)))
		{
			
		}
		
		EditorGUILayout.EndHorizontal();
	}

這裡一些必要的東西我都新增都註釋理了,就不重複了。

到這裡這個視窗就基本算是完成了。

3、匯出功能的具體實現

以上只是實現出了這樣一個視窗,具體響應功能,以及必要的邏輯實現還都不具備,這裡我們將為這個視窗新增具體的功能實現程式碼。

private void ExportAndSave(GameObject go)
	{
		//該方法將開啟儲存對話方塊,選擇匯出檔案的儲存位置//
		savePath = EditorUtility.SaveFilePanel("Save", @"E:\", go.name, "unity3d");
		Export(go, savePath);
	}
	
	private void Export(GameObject go, string filePath)
	{
		// IsPersistent 判斷傳入的物件是磁碟檔案還是場景檔案(即是否是Project檢視下的檔案,是返回true)//
		if(!EditorUtility.IsPersistent(go))
		{
			GameObject tmp = GameObject.Instantiate(go) as GameObject;
			go = GetPrefab(tmp, go.name) as GameObject;
		}
		Object[] asset = optionalTexture;
		if(File.Exists(filePath)) File.Delete(filePath);
		/*
			BuildPipeline.BuildAssetBundle():該方法是將提供的物件匯出成Unity能識別的二進位制檔案
			第一個引數是提供一個要匯出的物件,第二個引數是一個Object[]型別,它可以將資料附加到第一個
			引數定義的主資料一起整體匯出.但是這兩個引數要求必須是磁碟檔案的格式,所以上面的if語句判斷
			是否是磁碟檔案型別,如果不是,先將其轉化為prefab,在Assets下臨時儲存一下。這個轉化就是要
			用到 PrefabUtility 類裡的方法。
		*/
		BuildPipeline.BuildAssetBundle(go, asset, filePath, BuildAssetBundleOptions.CollectDependencies, BuildTarget.StandaloneWindows);
		// 將暫時生成的prefab檔案使用完後刪除 //
		AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(go));
	}
	
	/// <summary>
	/// 該方法來產生臨時prefab檔案
	/// </param>
	private Object GetPrefab(GameObject go, string name)
	{
		Object result = PrefabUtility.CreateEmptyPrefab("Assets/" + name + ".prefab");
		result = PrefabUtility.ReplacePrefab(go, result);
		Object.DestroyImmediate(go);
		return result;
	}

這裡我又新添加了三個方法來具體實現匯出並儲存的邏輯:

  •  private voidExportAndSave(GameObject go):

在這個方法裡只要關注一下怎麼開啟一個儲存對話方塊就可以了

//該方法將開啟儲存對話方塊,選擇匯出檔案的儲存位置。第二和第三個引數表示預設儲存位置和預設檔名//
savePath =EditorUtility.SaveFilePanel("Save", @"E:\", go.name,"unity3d");
  • private void Export(GameObjectgo, string filePath)

這個方法具體實現了匯出二進位制檔案的功能。這裡需要說明的是 BuildPipeline.BuildAssetBundle(): 該方法是將提供的物件匯出成Unity能識別的二進位制檔案第一個引數是提供一個要匯出的物件,第二個引數是一個Object[]型別,它可以將資料附加到第一個引數定義的主資料一起整體匯出.但是這兩個引數要求必須是磁碟檔案的格式,所以上面的if語句判斷是否是磁碟檔案型別,如果不是,先將其轉化為prefab,在Assets下臨時儲存一下。這個轉化就是要用到 PrefabUtility 類裡的方法。具體判斷是否是磁碟檔案,是通過 if(!EditorUtility.IsPersistent(go))這一句來判斷的:如果go不是磁碟檔案,是場景物件,則執行該語句裡的程式碼來生成磁碟檔案,具體的是下面這個方法來實現的。

  • private ObjectGetPrefab(GameObject go, string name)

我們在匯出前,如果匯出資訊設定的不正確,可能會致使匯出的檔案有問題或者不可用,所以在匯出之前對資訊有效性的驗證也是必要的:

/// <summary>
	/// 資料驗證,如果匯出資訊填寫有誤,將給使用者錯誤提示
	/// </summary>
	private bool Validate()
	{
		bool b1 = (exportObject == null);
		bool b2 = false;
		
		foreach(Texture2D t in optionalTexture)
		{
			b2 = b2 || (t == null);
		}
		
		return !(b1 || b2);
	}

如果使用者全部資訊都填寫完整了,該方法會返回true,匯出時可以根據返回值狀態來做相應的響應。

// 匯出按鈕 //
		if(GUILayout.Button("EXPORT", GUILayout.Width(100), GUILayout.Height(20)))
		{
			if(Validate())
			{
				ExportAndSave(exportObject);
				Clear();//成功匯出資料後,清除匯出資訊//
			}
			else
			{
				//匯出資訊填寫有誤時,給出提示//
				EditorUtility.DisplayDialog("錯誤提示", "匯出資訊設定有誤,請返回檢查!", "確定");
			}
		}

這裡可以看到我還添加了一個Clear()方法,該方法在使用者匯出完畢時,將匯出工具面板的資訊清除掉,以便開始匯出其它資源:

/// <summary>
	/// 所有資料正確匯出後,清除填寫的匯出資訊,以便匯出下一條資料
	/// </summary>
	private void Clear()
	{
		exportObject = null;
		optionalCount = 0;
	}

到這裡,我們匯出的所有邏輯就完成了,這樣子的一個匯出工具也基本完成了。此時,我們的完整程式碼應該是這個樣子的:

using System.IO;
using UnityEditor;
using UnityEngine;

/// <summary>
/// author : qyxls
/// </summary>
public class ExportTools : EditorWindow 
{
	[MenuItem("Tools/Export")]
	static void Execute () 
	{
		// 例項化一個Window視窗 //
		EditorWindow.GetWindow<ExportTools>(true, "Export Tools");
	}
	
	private string savePath;
	private GameObject exportObject;
	private int optionalCount = 0;
	private Texture2D[] optionalTexture = new Texture2D[0];
	
	void OnGUI()
	{
		/*
		 * ObjectField:
		 * 是這裡的第一個控制元件,它可以允許使用者拖拽將一個Object的物件賦給它。
		 * 如果要限制可接收的物件型別,可以通過第三個引數來限制類型這裡表示直接收GameObject型別
		 * 第四個bool型的引數標誌能否接受當前scene裡的物件,true表示接受
		 * 這個方法返回的是一個Object型別的值,最後要將它轉化為需要的型別
		 */
		exportObject = EditorGUILayout.ObjectField("Export Object", exportObject, 
													typeof(GameObject), true) 
													as GameObject;
		// 就相當於提供一個換行,用於格式化控制元件的 //
		EditorGUILayout.Space();
		// IntField:該控制元件只能輸入 int 型別的值//
		optionalCount = EditorGUILayout.IntField("Optional Count", optionalCount);
		for(int i=0; i<optionalCount; i++)
		{
			if(optionalTexture.Length != optionalCount)
			{
				optionalTexture = new Texture2D[optionalCount];
			}
			
			EditorGUILayout.Space();
			// 這裡將 ObjectField 限制只接受Texture2D型別的值 //
			optionalTexture[i] = EditorGUILayout.ObjectField("Optional Textures " + i, optionalTexture[i], 
															  typeof(Texture2D), false) 
															  as Texture2D;
		}
		
		EditorGUILayout.Space();
		EditorGUILayout.Space();
		
		EditorGUILayout.BeginHorizontal();
		EditorGUILayout.Space();
		// 匯出按鈕 //
		if(GUILayout.Button("EXPORT", GUILayout.Width(100), GUILayout.Height(20)))
		{
			if(Validate())
			{
				ExportAndSave(exportObject);
				Clear();//成功匯出資料後,清除匯出資訊//
			}
			else
			{
				//匯出資訊填寫有誤時,給出提示//
				EditorUtility.DisplayDialog("錯誤提示", "匯出資訊設定有誤,請返回檢查!", "確定");
			}
		}
		
		EditorGUILayout.EndHorizontal();
	}
	
	private void ExportAndSave(GameObject go)
	{
		//該方法將開啟儲存對話方塊,選擇匯出檔案的儲存位置。第二和第三個引數表示預設儲存位置和預設檔名//
		savePath = EditorUtility.SaveFilePanel("Save", @"E:\", go.name, "unity3d");
		Export(go, savePath);
	}
	
	private void Export(GameObject go, string filePath)
	{
		// IsPersistent 判斷傳入的物件是磁碟檔案還是場景檔案(即是否是Project檢視下的檔案,是返回true)//
		if(!EditorUtility.IsPersistent(go))
		{
			GameObject tmp = GameObject.Instantiate(go) as GameObject;
			go = GetPrefab(tmp, go.name) as GameObject;
		}
		//Texture2D本身就是磁碟檔案了,這裡就沒必要再轉化了//
		Object[] asset = optionalTexture;
		if(File.Exists(filePath)) File.Delete(filePath);
		/*
			BuildPipeline.BuildAssetBundle():該方法是將提供的物件匯出成Unity能識別的二進位制檔案
			第一個引數是提供一個要匯出的物件,第二個引數是一個Object[]型別,它可以將資料附加到第一個
			引數定義的主資料一起整體匯出.但是這兩個引數要求必須是磁碟檔案的格式,所以上面的if語句判斷
			是否是磁碟檔案型別,如果不是,先將其轉化為prefab,在Assets下臨時儲存一下。這個轉化就是要
			用到 PrefabUtility 類裡的方法。
		*/
		BuildPipeline.BuildAssetBundle(go, asset, filePath, BuildAssetBundleOptions.CollectDependencies, BuildTarget.StandaloneWindows);
		// 將暫時生成的prefab檔案使用完後刪除 //
		AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(go));
	}
	
	/// <summary>
	/// 該方法來產生臨時prefab檔案
	/// </param>
	private Object GetPrefab(GameObject go, string name)
	{
		Object result = PrefabUtility.CreateEmptyPrefab("Assets/" + name + ".prefab");
		result = PrefabUtility.ReplacePrefab(go, result);
		Object.DestroyImmediate(go);
		return result;
	}
	
	/// <summary>
	/// 資料驗證,如果匯出資訊填寫有誤,將給使用者錯誤提示
	/// </summary>
	private bool Validate()
	{
		bool b1 = (exportObject == null);
		bool b2 = false;
		
		foreach(Texture2D t in optionalTexture)
		{
			b2 = b2 || (t == null);
		}
		
		return !(b1 || b2);
	}
	
	/// <summary>
	/// 所有資料正確匯出後,清除填寫的匯出資訊,以便匯出下一條資料
	/// </summary>
	private void Clear()
	{
		exportObject = null;
		optionalCount = 0;
	}
}
工具介面應該是這樣子:

到這裡我們通過這個小小的匯出工具就可以製作出要需要的資原始檔了,這些資原始檔是存放在伺服器上的,接下來我們一起看看關於這些資原始檔的下載。

4、獲取資原始檔

這些檔案是可以就從本地磁碟載入進遊戲裡使用的,但這裡為了模擬從遠端伺服器下載這樣一個模式,我還是將剛剛製作好的檔案上傳到遠端主機來給大家展示一下這種的從遠端獲取的做法(其實從本地磁碟載入幾乎是一樣的)。

第一步:將檔案上傳到伺服器。

我真的沒有伺服器,但是我感覺度娘很熱情,估計能幫上我們什麼忙。

(此處省略好幾個字。。。。。。其實就是怎麼將剛剛匯出的檔案上傳到“百度雲”大笑)

上傳不說了,這裡看看怎麼獲取剛剛上傳資源的完整地址。

用Google瀏覽器(碼農用這個沒有什麼大問題吧?)登上“百度雲”,找到剛剛上傳的檔案,點選下載,然後按Ctrl+J開啟下載列表,右擊正在下載的檔案,選擇“複製連結地址”就可以取到該檔案的完整地址了。

這個是我的:

這裡我們暫且先這樣用著,在真正的專案開發中,資源的地址肯定會直接或間接的給出來的,這裡不必糾結。

我們來具體看看下載,這裡下載要使用到的類是WWW。在例項化WWW的時候,我們只需將資源的url地址給它,即可開始下載,例項化完WWW後我們只需判斷這個例項是否下載完成,如果完成了,即可以取下載來的資源來用了。程式碼是這樣的:(這個類不是UnityEditor裡的類,新建一個C#類並繼承自MonoBehaviour)

using UnityEngine;
using System.Collections;
/// <summary>
/// author : qyxls
/// </summary>
public class Downloader : MonoBehaviour 
{	
	private string url = " http://qd.baidupcs.com/file/5d06bd73f17afc5a5eb3e5497d0b6007?xcode=71a918e1fad4242470466f5cc4a869c8ec18245ec1f0d579&fid=604160625-250528-2002528139&time=1377831362&sign=FDTAXER-DCb740ccc5511e5e8fedcff06b081203-FsMzIz4cENbozwsto38a47bDc64%3D&to=qb&fm=Q,B,U&expires=8h&rt=pr&r=709102273&logid=1896966947&fn=dice.unity3d ";
	private WWW www;
	
	void Start () 
	{
		this.www = new WWW(this.url);
	}
	
	void Update () 
	{
		if(www == null) return;
		if(www.isDone)
		{
			print ("Download completed");
		}
	}
}

當啟動了Unity之後,會發現很快就會在Console檢視中打印出來了“Download completed”,而且還孜孜不倦的一直不肯停歇,這裡我們下載完了,只要對下載完的資源處理一次就夠了,沒必要沒完沒了的處理,多浪費感情啊,所以我們該定義一個標誌,來標記下載完成這麼一個狀態:

private bool isCompleted = false;
void Update () 
{
	if(www == null) return;
	if(!isCompleted && www.isDone)
	{
		print ("Download completed");
		isCompleted = true;
	}
}

現在是不是隻有這麼一條列印資訊了?

這段程式碼是非常簡單的,這裡也沒有什麼要多說的,就是提這麼一點,這裡我們是直接根據資源的URL去訪問下載的該資源,但在實際專案中,我們經常要處理的是根據不同的條件訪問同一地址而返回不同的資料來使用,這裡要使用的是WWW的另一個構造方法,可以帶除URL外的其它請求引數:

private void WWWWithParameter(string url, string parameter)
{
	WWWForm form = new WWWForm();
	form.AddField("Content", parameter);
	WWW www = new WWW(url, form);
}

可以看到,只需將引數封裝在WWWForm中再去用WWW訪問伺服器就可以了。

(本例中我們沒有采用帶引數的訪問是因為這樣的話,我們還要加一個後臺處理程式,要根據請求引數來返回資料,這樣我們就必須要在本機上安裝伺服器,書寫伺服器程式碼等等等等,這樣就得多做很多其它與我們這個話題相去深遠的工作了。。。。。。。。(好吧,我承認我不會配置伺服器))

到此本節的全部程式碼是這樣子的:

using UnityEngine;
using System.Collections;
/// <summary>
/// author : qyxls
/// </summary>
public class Downloader : MonoBehaviour 
{	
	private string url = " http://qd.baidupcs.com/file/5d06bd73f17afc5a5eb3e5497d0b6007?xcode=71a918e1fad4242470466f5cc4a869c8ec18245ec1f0d579&fid=604160625-250528-2002528139&time=1377831362&sign=FDTAXER-DCb740ccc5511e5e8fedcff06b081203-FsMzIz4cENbozwsto38a47bDc64%3D&to=qb&fm=Q,B,U&expires=8h&rt=pr&r=709102273&logid=1896966947&fn=dice.unity3d ";
	private WWW www;
	private bool isCompleted = false;
	
	void Start () 
	{
		this.www = new WWW(this.url);
	}
	
	void Update () 
	{
		if(www == null) return;
		if(!isCompleted && www.isDone)
		{
			print ("Download completed");
			isCompleted = true;
		}
	}
	
	/*
	private void WWWWithParameter(string url, string parameter)
	{
		WWWForm form = new WWWForm();
		form.AddField("Content", parameter);
		WWW www = new WWW(url, form);
	}
	*/
}

5、下載回來資源的使用

通過上面的操作,我們已經將資源下載到了本機,但是,大家也都看到了,我通過上面的方法的操作,說是下載完了資源,但我們場景中還是什麼都沒有啊,這個怎麼解釋?我用迅雷下完東東的時候,都在磁碟上有個檔案的。

這裡下載好的資源已經儲存在記憶體中了,我們只要取出來使用就好了:

void Update () 
{
	if(www == null) return;
	if(!isCompleted && www.isDone)
	{
		GameObject.Instantiate(www.assetBundle.mainAsset) as GameObject;
		isCompleted = true;
	}
}

只需這樣一句程式碼,你就在場景中可以看到這個令人興奮的小東西了,哈哈,是不是很簡單呢?

但是有沒有發現什麼問題呢?

我們當初匯出的可不僅僅這點東西啊,我們回過頭來看看:

起碼還有這些個貼圖怎麼不見了?當初匯出時可是明明放到Object[]一起匯出了的。莫著急,其實它們也都一起下載過來了,只是我們還沒有取來用罷了。

void Update () 
{
	if(www == null) return;
	if(!isCompleted && www.isDone)
	{
		GameObject.Instantiate(www.assetBundle.mainAsset) as GameObject;
		isCompleted = true;
		
		// 取回打包在資源內部的資料 由於我們當初放進去的全是Texture2D型別的,所以使用LoadAll的
		// 帶型別的過載方法,將Texture2D型別傳入進去,表示我只取出Texture2D型別的資料
		Object[] opticals = www.assetBundle.LoadAll(typeof(Texture2D));
		foreach(Object o in opticals)
		{
			Texture2D tmp = o as Texture2D;
			print("name : " + tmp.name);
		}
	}
}

這裡列印除了 6 條記錄我們當初打包到Object[]數組裡的是 4 張貼圖:black-dots、blue-dots、green-dots、yellow-dots。這裡明顯多出了red-dots和normal-dots,這不合適啊。細心的你也一定會發現,多出的那 2 張貼圖,正是剛剛匯出的模型上本身的一張漫反射貼圖和一張法線貼圖。原來,LoadAll()這個方法會將存在於下載過來的這個檔案中符合型別的所有資源都取過來的,這也很簡單處理,只要我們把不符合要求的剔除掉就好了。這裡實現起來很簡單,我就不說了,我這裡想說的是另一種方法,這個是開發中比較常用的。

我們使用的時候,一般都是取確定的某個物件,可以通過Load(string name)方法來取得,這個方法返回的是一個AssetBundleRequest型別的值,我們可以通過它裡面的asset屬性取到需要的資料:

AssetBundleRequest abr = www.assetBundle.LoadAsync("black-dots", typeof(Texture2D));

到現在,我們就下載過來了所有資料,並且可以取出需要的資料來使用了。接下來,我們完善一下這個小例子,把下載過來的資源充分的使用起來,就是給這個小東西換一個貼圖。

這裡完整的程式碼是這樣子的:

using UnityEngine;
using System.Collections;
/// <summary>
/// author : qyxls
/// </summary>
public class Downloader : MonoBehaviour 
{	
	private string url = "http://qd.baidupcs.com/file/5d06bd73f17afc5a5eb3e5497d0b6007?xcode=71a918e1fad4242470466f5cc4a869c8ec18245ec1f0d579&fid=604160625-250528-2002528139&time=1377831362&sign=FDTAXER-DCb740ccc5511e5e8fedcff06b081203-FsMzIz4cENbozwsto38a47bDc64%3D&to=qb&fm=Q,B,U&expires=8h&rt=pr&r=709102273&logid=1896966947&fn=dice.unity3d";
	private WWW www;
	private bool isCompleted = false;
		
	private GameObject dice;
	private Texture2D tex;
	
	void Start () 
	{
		this.www = new WWW(this.url);
	}
	
	void Update () 
	{
		if(www == null) return;
		if(!isCompleted && www.isDone)
		{
			dice = GameObject.Instantiate(www.assetBundle.mainAsset) as GameObject;
			isCompleted = true;
			
			/*
			// 取回打包在資源內部的資料 由於我們當初放進去的全是Texture2D型別的,所以使用LoadAll的//
			// 帶型別的過載方法,將Texture2D型別傳入進去,表示我只取出Texture2D型別的資料//
			Object[] opticals = www.assetBundle.LoadAll(typeof(Texture2D));
			foreach(Object o in opticals)
			{
				Texture2D tmp = o as Texture2D;
				print("name : " + tmp.name);
			}
			*/
			AssetBundleRequest abr = www.assetBundle.LoadAsync("black-dots", typeof(Texture2D));
			tex = abr.asset as Texture2D;			
		}
	}
	
	void OnGUI()
	{
		if(GUI.Button(new Rect(20, 20, 100, 40), "CHANGE"))
		{
			// 如果還沒下載完,這時候是不能執行替換功能的 //
			if(dice == null || tex == null) return;
			dice.renderer.material.mainTexture = tex;
		}
	}
}

執行結果:

開始未替換之前:

替換之後:

到現在我們這個的流程以及要求就基本實現了,這裡別忘了最後一步,清理使用完的無用資源,釋放記憶體。

下載完的資料都儲存在記憶體中,這時候它們都是一個AssetBundle的記憶體映象,我們在使用資料時,只是從記憶體映象裡取出資料,通過Instance方法新例項化出來的一個物件,當我們有了這樣一個物件,以後的操作都是針對這樣的一個,而記憶體中儲存的那塊映象已經沒有用處了,我們可以釋放掉:

AssetBundle.Unload(flase) : 是釋放AssetBundle檔案的記憶體映象,不包含Load建立的Asset記憶體物件。AssetBundle.Unload(true) : 是釋放那個AssetBundle檔案記憶體映象和並銷燬所有用Load建立的Asset記憶體物件。

這裡我們使用

www.assetBundle.Unload(false);

之所以不使用

www.assetBundle.Unload(true);
是因為我們不能銷燬掉例項化出來的Asset物件,我們還要繼續操作它(下面的換貼圖等)。否則,該物件會在場景裡消失,徹底銷燬掉了。

所有程式碼再給大家列一遍:

匯出工具的程式碼:

using System.IO;
using UnityEditor;
using UnityEngine;

/// <summary>
/// author : qyxls
/// </summary>
public class ExportTools : EditorWindow 
{
	[MenuItem("Tools/Export")]
	static void Execute () 
	{
		// 例項化一個Window視窗 //
		EditorWindow.GetWindow<ExportTools>(true, "Export Tools");
	}
	
	private string savePath;
	private GameObject exportObject;
	private int optionalCount = 0;
	private Texture2D[] optionalTexture = new Texture2D[0];
	
	void OnGUI()
	{
		/*
		 * ObjectField:
		 * 是這裡的第一個控制元件,它可以允許使用者拖拽將一個Object的物件賦給它。
		 * 如果要限制可接收的物件型別,可以通過第三個引數來限制類型這裡表示直接收GameObject型別
		 * 第四個bool型的引數標誌能否接受當前scene裡的物件,true表示接受
		 * 這個方法返回的是一個Object型別的值,最後要將它轉化為需要的型別
		 */
		exportObject = EditorGUILayout.ObjectField("Export Object", exportObject, 
													typeof(GameObject), true) 
													as GameObject;
		// 就相當於提供一個換行,用於格式化控制元件的 //
		EditorGUILayout.Space();
		// IntField:該控制元件只能輸入 int 型別的值//
		optionalCount = EditorGUILayout.IntField("Optional Count", optionalCount);
		for(int i=0; i<optionalCount; i++)
		{
			if(optionalTexture.Length != optionalCount)
			{
				optionalTexture = new Texture2D[optionalCount];
			}
			
			EditorGUILayout.Space();
			// 這裡將 ObjectField 限制只接受Texture2D型別的值 //
			optionalTexture[i] = EditorGUILayout.ObjectField("Optional Textures " + i, optionalTexture[i], 
															  typeof(Texture2D), false) 
															  as Texture2D;
		}
		
		EditorGUILayout.Space();
		EditorGUILayout.Space();
		
		EditorGUILayout.BeginHorizontal();
		EditorGUILayout.Space();
		// 匯出按鈕 //
		if(GUILayout.Button("EXPORT", GUILayout.Width(100), GUILayout.Height(20)))
		{
			if(Validate())
			{
				ExportAndSave(exportObject);
				Clear();//成功匯出資料後,清除匯出資訊//
			}
			else
			{
				//匯出資訊填寫有誤時,給出提示//
				EditorUtility.DisplayDialog("錯誤提示", "匯出資訊設定有誤,請返回檢查!", "確定");
			}
		}
		
		EditorGUILayout.EndHorizontal();
	}
	
	private void ExportAndSave(GameObject go)
	{
		//該方法將開啟儲存對話方塊,選擇匯出檔案的儲存位置。第二和第三個引數表示預設儲存位置和預設檔名//
		savePath = EditorUtility.SaveFilePanel("Save", @"E:\", go.name, "unity3d");
		Export(go, savePath);
	}
	
	private void Export(GameObject go, string filePath)
	{
		// IsPersistent 判斷傳入的物件是磁碟檔案還是場景檔案(即是否是Project檢視下的檔案,是返回true)//
		if(!EditorUtility.IsPersistent(go))
		{
			GameObject tmp = GameObject.Instantiate(go) as GameObject;
			go = GetPrefab(tmp, go.name) as GameObject;
		}
		//Texture2D本身就是磁碟檔案了,這裡就沒必要再轉化了//
		Object[] asset = optionalTexture;
		if(File.Exists(filePath)) File.Delete(filePath);
		/*
			BuildPipeline.BuildAssetBundle():該方法是將提供的物件匯出成Unity能識別的二進位制檔案
			第一個引數是提供一個要匯出的物件,第二個引數是一個Object[]型別,它可以將資料附加到第一個
			引數定義的主資料一起整體匯出.但是這兩個引數要求必須是磁碟檔案的格式,所以上面的if語句判斷
			是否是磁碟檔案型別,如果不是,先將其轉化為prefab,在Assets下臨時儲存一下。這個轉化就是要
			用到 PrefabUtility 類裡的方法。
		*/
		BuildPipeline.BuildAssetBundle(go, asset, filePath, BuildAssetBundleOptions.CollectDependencies, BuildTarget.StandaloneWindows);
		// 將暫時生成的prefab檔案使用完後刪除 //
		AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(go));
	}
	
	/// <summary>
	/// 該方法來產生臨時prefab檔案
	/// </param>
	private Object GetPrefab(GameObject go, string name)
	{
		Object result = PrefabUtility.CreateEmptyPrefab("Assets/" + name + ".prefab");
		result = PrefabUtility.ReplacePrefab(go, result);
		Object.DestroyImmediate(go);
		return result;
	}
	
	/// <summary>
	/// 資料驗證,如果匯出資訊填寫有誤,將給使用者錯誤提示
	/// </summary>
	private bool Validate()
	{
		bool b1 = (exportObject == null);
		bool b2 = false;
		
		foreach(Texture2D t in optionalTexture)
		{
			b2 = b2 || (t == null);
		}
		
		return !(b1 || b2);
	}
	
	/// <summary>
	/// 所有資料正確匯出後,清除填寫的匯出資訊,以便匯出下一條資料
	/// </summary>
	private void Clear()
	{
		exportObject = null;
		optionalCount = 0;
	}
}

下載實現程式碼:

using UnityEngine;
using System.Collections;
/// <summary>
/// author : qyxls
/// </summary>
public class Downloader : MonoBehaviour 
{	
	private string url = "http://qd.baidupcs.com/file/5d06bd73f17afc5a5eb3e5497d0b6007?xcode=71a918e1fad4242470466f5cc4a869c8ec18245ec1f0d579&fid=604160625-250528-2002528139&time=1377831362&sign=FDTAXER-DCb740ccc5511e5e8fedcff06b081203-FsMzIz4cENbozwsto38a47bDc64%3D&to=qb&fm=Q,B,U&expires=8h&rt=pr&r=709102273&logid=1896966947&fn=dice.unity3d";
	private WWW www;
	private bool isCompleted = false;
		
	private GameObject dice;
	private Texture2D tex;
	
	void Start () 
	{
		this.www = new WWW(this.url);
	}
	
	void Update () 
	{
		if(www == null) return;
		if(!isCompleted && www.isDone)
		{
			dice = GameObject.Instantiate(www.assetBundle.mainAsset) as GameObject;
			isCompleted = true;
			
			/*
			// 取回打包在資源內部的資料 由於我們當初放進去的全是Texture2D型別的,所以使用LoadAll的//
			// 帶型別的過載方法,將Texture2D型別傳入進去,表示我只取出Texture2D型別的資料//
			Object[] opticals = www.assetBundle.LoadAll(typeof(Texture2D));
			foreach(Object o in opticals)
			{
				Texture2D tmp = o as Texture2D;
				print("name : " + tmp.name);
			}
			*/
			AssetBundleRequest abr = www.assetBundle.LoadAsync("black-dots", typeof(Texture2D));
			tex = abr.asset as Texture2D;			
		}
	}
	
	void OnGUI()
	{
		if(GUI.Button(new Rect(20, 20, 100, 40), "CHANGE"))
		{
			// 如果還沒下載完,這時候是不能執行替換功能的 //
			if(dice == null || tex == null) return;
			dice.renderer.material.mainTexture = tex;
		}
	}
	
	/*
	private void WWWWithParameter(string url, string parameter)
	{
		WWWForm form = new WWWForm();
		form.AddField("Content", parameter);
		WWW www = new WWW(url, form);
	}
	*/
}




(請原諒這裡設定了下載需要積分,前段時間下載東東,把積分用完了大哭。再有就是有積分限制,強迫你自己去敲程式碼嘛大笑