遊戲資源的製作和下載

最近在想怎樣的網絡地址可以打開就開始下載遊戲資源,並能夠被 unity WWW 類加載,找到這篇不錯的文章 , 轉載過來 , 希望對有同樣問題的朋友有所幫助.

原文地址:點擊打開鏈接

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

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

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

 

開發環境:

Windows 7

Unity3D 3.5.1f2

 

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

1、  UnityEditor命名空間

2、  Editor模式下窗口製作

3、 導出功能的具體實現

4、 資源的下載

5、 下載後使用

 

1、 UnityEditor命名空間

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

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

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

[csharp] view plaincopy
  1. [MenuItem(“Tools/Export”)]  

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

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

完成之後,先在Project下創建Editor文件夾並創建一個腳本文件,輸入以下內容:


[csharp] view plaincopy
  1. using UnityEditor;  
  2. using UnityEngine;  
  3. using System.Collections;  
  4.   
  5. /// <summary>  
  6. /// author : qyxls  
  7. /// </summary>  
  8. public class ExportTools : MonoBehaviour   
  9. {  
  10.     [MenuItem("Tools/Export")]  
  11.     static void Execute ()   
  12.     {  
  13.         Debug.Log("Menu is selected !!");  
  14.     }  
  15. }  
當我們點擊菜單欄上的對應菜單選項:ToolsàExport時,

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


這裏要注意兩點:

1、 引入UnityEditor命名空間。

2、 MenuItem要調用的方法需要是static的。

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

 

2、Editor模式下窗口製作

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


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

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

用例描述:

導出場景中的一個模型,並帶着默認材質,如果該模型有多個可替換的貼圖,也把這些貼圖作爲該模型的資源一併導出到一個資源包中。

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

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


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

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

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

[csharp] view plaincopy
  1. using UnityEditor;  
  2. using UnityEngine;  
  3.   
  4. /// <summary>  
  5. /// author : qyxls  
  6. /// </summary>  
  7. public class ExportTools :<strong> <span style="color:#ff0000;">EditorWindow</span> </strong>  
  8. {  
  9.     [MenuItem("Tools/Export")]  
  10.     static void Execute ()   
  11.     {  
  12. // 實例化一個Window窗口 //  
  13.         ExportTools windows = EditorWindow.GetWindow<ExportTools>(true"Export Tools");  
  14.     }  
  15.       
  16.     void OnGUI()  
  17.     {  
  18.           
  19.     }  
  20. }  

這裏要注意的就是將原來的腳本有繼承自MonoBehaviour 修改爲繼承自EditorWindow。並在Execute ()方法中對當前的Window實例化。這時我們就可以得到一個Window窗口了:

其次,就是向我們生成的窗口中添加不同的控件,這些控件的生成都是在OnGUI()方法中實現的。和MonoBehaviour的OnGUI方法一樣,EditorWindow的OnGUI()方法也主要是處理UI的,我們關於UI控件的生成處理都要寫在這個方法裏。OnGUI()這個方法每幀調用好幾次(每個事件一次),所以一些邏輯處理要避免在這裏調用。

[csharp] view plaincopy
  1. private string savePath;  
  2. private GameObject exportObject;  
  3. private int optionalCount = 0;  
  4. private Texture2D[] optionalTexture = new Texture2D[0];  
  5.   
  6. void OnGUI()  
  7. {  
  8.     /* 
  9.      * ObjectField: 
  10.      * 是這裏的第一個控件,它可以允許用戶拖拽將一個Object的對象賦給它。 
  11.      * 如果要限制可接收的對象類型,可以通過第三個參數來限制類型這裏表示直接收GameObject類型 
  12.      * 第四個bool型的參數標誌能否接受當前scene裏的對象,true表示接受 
  13.      * 這個方法返回的是一個Object類型的值,最後要將它轉化爲需要的類型 
  14.      */  
  15.     exportObject = EditorGUILayout.ObjectField("Export Object", exportObject,   
  16.                                                 typeof(GameObject), true)   
  17.                                                 as GameObject;  
  18.     // 就相當於提供一個換行,用於格式化控件的 //  
  19.     EditorGUILayout.Space();  
  20.     // IntField:該控件只能輸入 int 類型的值//  
  21.     optionalCount = EditorGUILayout.IntField("Optional Count", optionalCount);  
  22.     for(int i=0; i<optionalCount; i++)  
  23.     {  
  24.         if(optionalTexture.Length != optionalCount)  
  25.         {  
  26.             optionalTexture = new Texture2D[optionalCount];  
  27.         }  
  28.           
  29.         EditorGUILayout.Space();  
  30.         // 這裏將 ObjectField 限制只接受Texture2D類型的值 //  
  31.         optionalTexture[i] = EditorGUILayout.ObjectField("Optional Textures " + i, optionalTexture[i],   
  32.                                                           typeof(Texture2D), false)   
  33.                                                           as Texture2D;  
  34.     }  
  35.       
  36.     EditorGUILayout.Space();  
  37.     EditorGUILayout.Space();  
  38.       
  39.     EditorGUILayout.BeginHorizontal();  
  40.     EditorGUILayout.Space();  
  41.     // 導出按鈕 //  
  42.     if(GUILayout.Button("EXPORT", GUILayout.Width(100), GUILayout.Height(20)))  
  43.     {  
  44.           
  45.     }  
  46.       
  47.     EditorGUILayout.EndHorizontal();  
  48. }  

這裏一些必要的東西我都添加都註釋理了,就不重複了。

到這裏這個窗口就基本算是完成了。

3、導出功能的具體實現

以上只是實現出了這樣一個窗口,具體響應功能,以及必要的邏輯實現還都不具備,這裏我們將爲這個窗口添加具體的功能實現代碼。

[csharp] view plaincopy
  1. <span style="white-space:pre">  </span>private void ExportAndSave(GameObject go)  
  2.     {  
  3.         //該方法將打開保存對話框,選擇導出文件的保存位置//  
  4.         savePath = EditorUtility.SaveFilePanel("Save", @"E:\", go.name, "unity3d");  
  5.         Export(go, savePath);  
  6.     }  
  7.       
  8.     private void Export(GameObject go, string filePath)  
  9.     {  
  10.         // IsPersistent 判斷傳入的對象是磁盤文件還是場景文件(即是否是Project視圖下的文件,是返回true)//  
  11.         if(!EditorUtility.IsPersistent(go))  
  12.         {  
  13.             GameObject tmp = GameObject.Instantiate(go) as GameObject;  
  14.             go = GetPrefab(tmp, go.name) as GameObject;  
  15.         }  
  16.         Object[] asset = optionalTexture;  
  17.         if(File.Exists(filePath)) File.Delete(filePath);  
  18.         /* 
  19.             BuildPipeline.BuildAssetBundle():該方法是將提供的對象導出成Unity能識別的二進制文件 
  20.             第一個參數是提供一個要導出的對象,第二個參數是一個Object[]類型,它可以將數據附加到第一個 
  21.             參數定義的主數據一起整體導出.但是這兩個參數要求必須是磁盤文件的格式,所以上面的if語句判斷 
  22.             是否是磁盤文件類型,如果不是,先將其轉化爲prefab,在Assets下臨時保存一下。這個轉化就是要 
  23.             用到 PrefabUtility 類裏的方法。 
  24.         */  
  25.         BuildPipeline.BuildAssetBundle(go, asset, filePath, BuildAssetBundleOptions.CollectDependencies, BuildTarget.StandaloneWindows);  
  26.         // 將暫時生成的prefab文件使用完後刪除 //  
  27.         AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(go));  
  28.     }  
  29.       
  30.     /// <summary>  
  31.     /// 該方法來產生臨時prefab文件  
  32.     /// </param>  
  33.     private Object GetPrefab(GameObject go, string name)  
  34.     {  
  35.         Object result = PrefabUtility.CreateEmptyPrefab("Assets/" + name + ".prefab");  
  36.         result = PrefabUtility.ReplacePrefab(go, result);  
  37.         Object.DestroyImmediate(go);  
  38.         return result;  
  39.     }  

這裏我又新添加了三個方法來具體實現導出並保存的邏輯:

  •  private voidExportAndSave(GameObject go):

在這個方法裏只要關注一下怎麼打開一個保存對話框就可以了

[csharp] view plaincopy
  1. //該方法將打開保存對話框,選擇導出文件的保存位置。第二和第三個參數表示默認保存位置和默認文件名//  
  2. 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)

我們在導出前,如果導出信息設置的不正確,可能會致使導出的文件有問題或者不可用,所以在導出之前對信息有效性的驗證也是必要的:

[csharp] view plaincopy
  1. <span style="white-space:pre">  </span>/// <summary>  
  2.     /// 數據驗證,如果導出信息填寫有誤,將給用戶錯誤提示  
  3.     /// </summary>  
  4.     private bool Validate()  
  5.     {  
  6.         bool b1 = (exportObject == null);  
  7.         bool b2 = false;  
  8.           
  9.         foreach(Texture2D t in optionalTexture)  
  10.         {  
  11.             b2 = b2 || (t == null);  
  12.         }  
  13.           
  14.         return !(b1 || b2);  
  15.     }  

如果用戶全部信息都填寫完整了,該方法會返回true,導出時可以根據返回值狀態來做相應的響應。

[csharp] view plaincopy
  1. <span style="white-space:pre">      </span>// 導出按鈕 //  
  2.         if(GUILayout.Button("EXPORT", GUILayout.Width(100), GUILayout.Height(20)))  
  3.         {  
  4.             if(Validate())  
  5.             {  
  6.                 ExportAndSave(exportObject);  
  7.                 Clear();//成功導出數據後,清除導出信息//  
  8.             }  
  9.             else  
  10.             {  
  11.                 //導出信息填寫有誤時,給出提示//  
  12.                 EditorUtility.DisplayDialog("錯誤提示""導出信息設置有誤,請返回檢查!""確定");  
  13.             }  
  14.         }  

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

[csharp] view plaincopy
  1. <span style="white-space:pre">  </span>/// <summary>  
  2.     /// 所有數據正確導出後,清除填寫的導出信息,以便導出下一條數據  
  3.     /// </summary>  
  4.     private void Clear()  
  5.     {  
  6.         exportObject = null;  
  7.         optionalCount = 0;  
  8.     }  

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

[csharp] view plaincopy
  1. using System.IO;  
  2. using UnityEditor;  
  3. using UnityEngine;  
  4.   
  5. /// <summary>  
  6. /// author : qyxls  
  7. /// </summary>  
  8. public class ExportTools : EditorWindow   
  9. {  
  10.     [MenuItem("Tools/Export")]  
  11.     static void Execute ()   
  12.     {  
  13.         // 實例化一個Window窗口 //  
  14.         EditorWindow.GetWindow<ExportTools>(true"Export Tools");  
  15.     }  
  16.       
  17.     private string savePath;  
  18.     private GameObject exportObject;  
  19.     private int optionalCount = 0;  
  20.     private Texture2D[] optionalTexture = new Texture2D[0];  
  21.       
  22.     void OnGUI()  
  23.     {  
  24.         /* 
  25.          * ObjectField: 
  26.          * 是這裏的第一個控件,它可以允許用戶拖拽將一個Object的對象賦給它。 
  27.          * 如果要限制可接收的對象類型,可以通過第三個參數來限制類型這裏表示直接收GameObject類型 
  28.          * 第四個bool型的參數標誌能否接受當前scene裏的對象,true表示接受 
  29.          * 這個方法返回的是一個Object類型的值,最後要將它轉化爲需要的類型 
  30.          */  
  31.         exportObject = EditorGUILayout.ObjectField("Export Object", exportObject,   
  32.                                                     typeof(GameObject), true)   
  33.                                                     as GameObject;  
  34.         // 就相當於提供一個換行,用於格式化控件的 //  
  35.         EditorGUILayout.Space();  
  36.         // IntField:該控件只能輸入 int 類型的值//  
  37.         optionalCount = EditorGUILayout.IntField("Optional Count", optionalCount);  
  38.         for(int i=0; i<optionalCount; i++)  
  39.         {  
  40.             if(optionalTexture.Length != optionalCount)  
  41.             {  
  42.                 optionalTexture = new Texture2D[optionalCount];  
  43.             }  
  44.               
  45.             EditorGUILayout.Space();  
  46.             // 這裏將 ObjectField 限制只接受Texture2D類型的值 //  
  47.             optionalTexture[i] = EditorGUILayout.ObjectField("Optional Textures " + i, optionalTexture[i],   
  48.                                                               typeof(Texture2D), false)   
  49.                                                               as Texture2D;  
  50.         }  
  51.           
  52.         EditorGUILayout.Space();  
  53.         EditorGUILayout.Space();  
  54.           
  55.         EditorGUILayout.BeginHorizontal();  
  56.         EditorGUILayout.Space();  
  57.         // 導出按鈕 //  
  58.         if(GUILayout.Button("EXPORT", GUILayout.Width(100), GUILayout.Height(20)))  
  59.         {  
  60.             if(Validate())  
  61.             {  
  62.                 ExportAndSave(exportObject);  
  63.                 Clear();//成功導出數據後,清除導出信息//  
  64.             }  
  65.             else  
  66.             {  
  67.                 //導出信息填寫有誤時,給出提示//  
  68.                 EditorUtility.DisplayDialog("錯誤提示""導出信息設置有誤,請返回檢查!""確定");  
  69.             }  
  70.         }  
  71.           
  72.         EditorGUILayout.EndHorizontal();  
  73.     }  
  74.       
  75.     private void ExportAndSave(GameObject go)  
  76.     {  
  77.         //該方法將打開保存對話框,選擇導出文件的保存位置。第二和第三個參數表示默認保存位置和默認文件名//  
  78.         savePath = EditorUtility.SaveFilePanel("Save", @"E:\", go.name, "unity3d");  
  79.         Export(go, savePath);  
  80.     }  
  81.       
  82.     private void Export(GameObject go, string filePath)  
  83.     {  
  84.         // IsPersistent 判斷傳入的對象是磁盤文件還是場景文件(即是否是Project視圖下的文件,是返回true)//  
  85.         if(!EditorUtility.IsPersistent(go))  
  86.         {  
  87.             GameObject tmp = GameObject.Instantiate(go) as GameObject;  
  88.             go = GetPrefab(tmp, go.name) as GameObject;  
  89.         }  
  90.         //Texture2D本身就是磁盤文件了,這裏就沒必要再轉化了//  
  91.         Object[] asset = optionalTexture;  
  92.         if(File.Exists(filePath)) File.Delete(filePath);  
  93.         /* 
  94.             BuildPipeline.BuildAssetBundle():該方法是將提供的對象導出成Unity能識別的二進制文件 
  95.             第一個參數是提供一個要導出的對象,第二個參數是一個Object[]類型,它可以將數據附加到第一個 
  96.             參數定義的主數據一起整體導出.但是這兩個參數要求必須是磁盤文件的格式,所以上面的if語句判斷 
  97.             是否是磁盤文件類型,如果不是,先將其轉化爲prefab,在Assets下臨時保存一下。這個轉化就是要 
  98.             用到 PrefabUtility 類裏的方法。 
  99.         */  
  100.         BuildPipeline.BuildAssetBundle(go, asset, filePath, BuildAssetBundleOptions.CollectDependencies, BuildTarget.StandaloneWindows);  
  101.         // 將暫時生成的prefab文件使用完後刪除 //  
  102.         AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(go));  
  103.     }  
  104.       
  105.     /// <summary>  
  106.     /// 該方法來產生臨時prefab文件  
  107.     /// </param>  
  108.     private Object GetPrefab(GameObject go, string name)  
  109.     {  
  110.         Object result = PrefabUtility.CreateEmptyPrefab("Assets/" + name + ".prefab");  
  111.         result = PrefabUtility.ReplacePrefab(go, result);  
  112.         Object.DestroyImmediate(go);  
  113.         return result;  
  114.     }  
  115.       
  116.     /// <summary>  
  117.     /// 數據驗證,如果導出信息填寫有誤,將給用戶錯誤提示  
  118.     /// </summary>  
  119.     private bool Validate()  
  120.     {  
  121.         bool b1 = (exportObject == null);  
  122.         bool b2 = false;  
  123.           
  124.         foreach(Texture2D t in optionalTexture)  
  125.         {  
  126.             b2 = b2 || (t == null);  
  127.         }  
  128.           
  129.         return !(b1 || b2);  
  130.     }  
  131.       
  132.     /// <summary>  
  133.     /// 所有數據正確導出後,清除填寫的導出信息,以便導出下一條數據  
  134.     /// </summary>  
  135.     private void Clear()  
  136.     {  
  137.         exportObject = null;  
  138.         optionalCount = 0;  
  139.     }  
  140. }  
工具界面應該是這樣子:

到這裏我們通過這個小小的導出工具就可以製作出要需要的資源文件了,這些資源文件是存放在服務器上的,接下來我們一起看看關於這些資源文件的下載。

4、獲取資源文件

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

第一步:將文件上傳到服務器。

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

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

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

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



這個是我的:

http://bj.baidupcs.com/file/8752f8cf08e92dded7127aa4dc0489f7?xcode=28baeb9afc859429429dd4c38dda1979442b8d6833d75b4f&fid=604160625-250528-2314552676&time=1377828806&sign=FDTAXER-DCb740ccc5511e5e8fedcff06b081203-cULgvhDQRRDEe32IavH35RKmn1Y%3D&to=bb&fm=N,B,U&expires=8h&rt=pr&r=392549107&logid=3993021536&fn=dice.unity3d

 

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

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

[csharp] view plaincopy
  1. using UnityEngine;  
  2. using System.Collections;  
  3. /// <summary>  
  4. /// author : qyxls  
  5. /// </summary>  
  6. public class Downloader : MonoBehaviour   
  7. {     
  8.     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 ";  
  9.     private WWW www;  
  10.       
  11.     void Start ()   
  12.     {  
  13.         this.www = new WWW(this.url);  
  14.     }  
  15.       
  16.     void Update ()   
  17.     {  
  18.         if(www == nullreturn;  
  19.         if(www.isDone)  
  20.         {  
  21.             print ("Download completed");  
  22.         }  
  23.     }  
  24. }  

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

[csharp] view plaincopy
  1. private bool isCompleted = false;  
  2. void Update ()   
  3. {  
  4.     if(www == nullreturn;  
  5.     if(!isCompleted && www.isDone)  
  6.     {  
  7.         print ("Download completed");  
  8.         isCompleted = true;  
  9.     }  
  10. }  

現在是不是隻有這麼一條打印信息了?

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

[csharp] view plaincopy
  1. private void WWWWithParameter(string url, string parameter)  
  2. {  
  3.     WWWForm form = new WWWForm();  
  4.     form.AddField("Content", parameter);  
  5.     WWW www = new WWW(url, form);  
  6. }  

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

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

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

[csharp] view plaincopy
  1. using UnityEngine;  
  2. using System.Collections;  
  3. /// <summary>  
  4. /// author : qyxls  
  5. /// </summary>  
  6. public class Downloader : MonoBehaviour   
  7. {     
  8.     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 ";  
  9.     private WWW www;  
  10.     private bool isCompleted = false;  
  11.       
  12.     void Start ()   
  13.     {  
  14.         this.www = new WWW(this.url);  
  15.     }  
  16.       
  17.     void Update ()   
  18.     {  
  19.         if(www == nullreturn;  
  20.         if(!isCompleted && www.isDone)  
  21.         {  
  22.             print ("Download completed");  
  23.             isCompleted = true;  
  24.         }  
  25.     }  
  26.       
  27.     /* 
  28.     private void WWWWithParameter(string url, string parameter) 
  29.     { 
  30.         WWWForm form = new WWWForm(); 
  31.         form.AddField("Content", parameter); 
  32.         WWW www = new WWW(url, form); 
  33.     } 
  34.     */  
  35. }  

5、下載回來資源的使用

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

這裏下載好的資源已經保存在內存中了,我們只要取出來使用就好了:

[csharp] view plaincopy
  1. void Update ()   
  2. {  
  3.     if(www == nullreturn;  
  4.     if(!isCompleted && www.isDone)  
  5.     {  
  6.         GameObject.Instantiate(www.assetBundle.mainAsset) as GameObject;  
  7.         isCompleted = true;  
  8.     }  
  9. }  

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

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

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

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

再看www對象或者www.assetBundle裏面有一個方法返回了Object[]數組:www.assetBundle.LoadAll()。抓緊去試試:

[csharp] view plaincopy
  1. void Update ()   
  2. {  
  3.     if(www == nullreturn;  
  4.     if(!isCompleted && www.isDone)  
  5.     {  
  6.         GameObject.Instantiate(www.assetBundle.mainAsset) as GameObject;  
  7.         isCompleted = true;  
  8.           
  9.         // 取回打包在資源內部的數據 由於我們當初放進去的全是Texture2D類型的,所以使用LoadAll的  
  10.         // 帶類型的重載方法,將Texture2D類型傳入進去,表示我只取出Texture2D類型的數據  
  11.         Object[] opticals = www.assetBundle.LoadAll(typeof(Texture2D));  
  12.         foreach(Object o in opticals)  
  13.         {  
  14.             Texture2D tmp = o as Texture2D;  
  15.             print("name : " + tmp.name);  
  16.         }  
  17.     }  
  18. }  

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

 

我們使用的時候,一般都是取確定的某個對象,可以通過Load(string name)方法來取得,這個方法返回的是一個AssetBundleRequest類型的值,我們可以通過它裏面的asset屬性取到需要的數據:

[csharp] view plaincopy
  1. AssetBundleRequest abr = www.assetBundle.LoadAsync("black-dots"typeof(Texture2D));  

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

這裏完整的代碼是這樣子的:

[csharp] view plaincopy
  1. using UnityEngine;  
  2. using System.Collections;  
  3. /// <summary>  
  4. /// author : qyxls  
  5. /// </summary>  
  6. public class Downloader : MonoBehaviour   
  7. {     
  8.     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";  
  9.     private WWW www;  
  10.     private bool isCompleted = false;  
  11.           
  12.     private GameObject dice;  
  13.     private Texture2D tex;  
  14.       
  15.     void Start ()   
  16.     {  
  17.         this.www = new WWW(this.url);  
  18.     }  
  19.       
  20.     void Update ()   
  21.     {  
  22.         if(www == nullreturn;  
  23.         if(!isCompleted && www.isDone)  
  24.         {  
  25.             dice = GameObject.Instantiate(www.assetBundle.mainAsset) as GameObject;  
  26.             isCompleted = true;  
  27.               
  28.             /* 
  29.             // 取回打包在資源內部的數據 由於我們當初放進去的全是Texture2D類型的,所以使用LoadAll的// 
  30.             // 帶類型的重載方法,將Texture2D類型傳入進去,表示我只取出Texture2D類型的數據// 
  31.             Object[] opticals = www.assetBundle.LoadAll(typeof(Texture2D)); 
  32.             foreach(Object o in opticals) 
  33.             { 
  34.                 Texture2D tmp = o as Texture2D; 
  35.                 print("name : " + tmp.name); 
  36.             } 
  37.             */  
  38.             AssetBundleRequest abr = www.assetBundle.LoadAsync("black-dots"typeof(Texture2D));  
  39.             tex = abr.asset as Texture2D;             
  40.         }  
  41.     }  
  42.       
  43.     void OnGUI()  
  44.     {  
  45.         if(GUI.Button(new Rect(20, 20, 100, 40), "CHANGE"))  
  46.         {  
  47.             // 如果還沒下載完,這時候是不能執行替換功能的 //  
  48.             if(dice == null || tex == nullreturn;  
  49.             dice.renderer.material.mainTexture = tex;  
  50.         }  
  51.     }  
  52. }  

運行結果:

開始未替換之前:

替換之後:

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

下載完的數據都保存在內存中,這時候它們都是一個AssetBundle的內存鏡像,我們在使用數據時,只是從內存鏡像裏取出數據,通過Instance方法新實例化出來的一個對象,當我們有了這樣一個對象,以後的操作都是針對這樣的一個,而內存中保存的那塊鏡像已經沒有用處了,我們可以釋放掉:

AssetBundle.Unload(flase) : 是釋放AssetBundle文件的內存鏡像,不包含Load創建的Asset內存對象。
AssetBundle.Unload(true) : 是釋放那個AssetBundle文件內存鏡像和並銷燬所有用Load創建的Asset內存對象。

這裏我們使用

[csharp] view plaincopy
  1. www.assetBundle.Unload(false);  

之所以不使用

[csharp] view plaincopy
  1. www.assetBundle.Unload(true);  
是因爲我們不能銷燬掉實例化出來的Asset對象,我們還要繼續操作它(下面的換貼圖等)。否則,該對象會在場景裏消失,徹底銷燬掉了。

所有代碼再給大家列一遍:

導出工具的代碼:

[csharp] view plaincopy
  1. using System.IO;  
  2. using UnityEditor;  
  3. using UnityEngine;  
  4.   
  5. /// <summary>  
  6. /// author : qyxls  
  7. /// </summary>  
  8. public class ExportTools : EditorWindow   
  9. {  
  10.     [MenuItem("Tools/Export")]  
  11.     static void Execute ()   
  12.     {  
  13.         // 實例化一個Window窗口 //  
  14.         EditorWindow.GetWindow<ExportTools>(true"Export Tools");  
  15.     }  
  16.       
  17.     private string savePath;  
  18.     private GameObject exportObject;  
  19.     private int optionalCount = 0;  
  20.     private Texture2D[] optionalTexture = new Texture2D[0];  
  21.       
  22.     void OnGUI()  
  23.     {  
  24.         /* 
  25.          * ObjectField: 
  26.          * 是這裏的第一個控件,它可以允許用戶拖拽將一個Object的對象賦給它。 
  27.          * 如果要限制可接收的對象類型,可以通過第三個參數來限制類型這裏表示直接收GameObject類型 
  28.          * 第四個bool型的參數標誌能否接受當前scene裏的對象,true表示接受 
  29.          * 這個方法返回的是一個Object類型的值,最後要將它轉化爲需要的類型 
  30.          */  
  31.         exportObject = EditorGUILayout.ObjectField("Export Object", exportObject,   
  32.                                                     typeof(GameObject), true)   
  33.                                                     as GameObject;  
  34.         // 就相當於提供一個換行,用於格式化控件的 //  
  35.         EditorGUILayout.Space();  
  36.         // IntField:該控件只能輸入 int 類型的值//  
  37.         optionalCount = EditorGUILayout.IntField("Optional Count", optionalCount);  
  38.         for(int i=0; i<optionalCount; i++)  
  39.         {  
  40.             if(optionalTexture.Length != optionalCount)  
  41.             {  
  42.                 optionalTexture = new Texture2D[optionalCount];  
  43.             }  
  44.               
  45.             EditorGUILayout.Space();  
  46.             // 這裏將 ObjectField 限制只接受Texture2D類型的值 //  
  47.             optionalTexture[i] = EditorGUILayout.ObjectField("Optional Textures " + i, optionalTexture[i],   
  48.                                                               typeof(Texture2D), false)   
  49.                                                               as Texture2D;  
  50.         }  
  51.           
  52.         EditorGUILayout.Space();  
  53.         EditorGUILayout.Space();  
  54.           
  55.         EditorGUILayout.BeginHorizontal();  
  56.         EditorGUILayout.Space();  
  57.         // 導出按鈕 //  
  58.         if(GUILayout.Button("EXPORT", GUILayout.Width(100), GUILayout.Height(20)))  
  59.         {  
  60.             if(Validate())  
  61.             {  
  62.                 ExportAndSave(exportObject);  
  63.                 Clear();//成功導出數據後,清除導出信息//  
  64.             }  
  65.             else  
  66.             {  
  67.                 //導出信息填寫有誤時,給出提示//  
  68.                 EditorUtility.DisplayDialog("錯誤提示""導出信息設置有誤,請返回檢查!""確定");  
  69.             }  
  70.         }  
  71.           
  72.         EditorGUILayout.EndHorizontal();  
  73.     }  
  74.       
  75.     private void ExportAndSave(GameObject go)  
  76.     {  
  77.         //該方法將打開保存對話框,選擇導出文件的保存位置。第二和第三個參數表示默認保存位置和默認文件名//  
  78.         savePath = EditorUtility.SaveFilePanel("Save", @"E:\", go.name, "unity3d");  
  79.         Export(go, savePath);  
  80.     }  
  81.       
  82.     private void Export(GameObject go, string filePath)  
  83.     {  
  84.         // IsPersistent 判斷傳入的對象是磁盤文件還是場景文件(即是否是Project視圖下的文件,是返回true)//  
  85.         if(!EditorUtility.IsPersistent(go))  
  86.         {  
  87.             GameObject tmp = GameObject.Instantiate(go) as GameObject;  
  88.             go = GetPrefab(tmp, go.name) as GameObject;  
  89.         }  
  90.         //Texture2D本身就是磁盤文件了,這裏就沒必要再轉化了//  
  91.         Object[] asset = optionalTexture;  
  92.         if(File.Exists(filePath)) File.Delete(filePath);  
  93.         /* 
  94.             BuildPipeline.BuildAssetBundle():該方法是將提供的對象導出成Unity能識別的二進制文件 
  95.             第一個參數是提供一個要導出的對象,第二個參數是一個Object[]類型,它可以將數據附加到第一個 
  96.             參數定義的主數據一起整體導出.但是這兩個參數要求必須是磁盤文件的格式,所以上面的if語句判斷 
  97.             是否是磁盤文件類型,如果不是,先將其轉化爲prefab,在Assets下臨時保存一下。這個轉化就是要 
  98.             用到 PrefabUtility 類裏的方法。 
  99.         */  
  100.         BuildPipeline.BuildAssetBundle(go, asset, filePath, BuildAssetBundleOptions.CollectDependencies, BuildTarget.StandaloneWindows);  
  101.         // 將暫時生成的prefab文件使用完後刪除 //  
  102.         AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(go));  
  103.     }  
  104.       
  105.     /// <summary>  
  106.     /// 該方法來產生臨時prefab文件  
  107.     /// </param>  
  108.     private Object GetPrefab(GameObject go, string name)  
  109.     {  
  110.         Object result = PrefabUtility.CreateEmptyPrefab("Assets/" + name + ".prefab");  
  111.         result = PrefabUtility.ReplacePrefab(go, result);  
  112.         Object.DestroyImmediate(go);  
  113.         return result;  
  114.     }  
  115.       
  116.     /// <summary>  
  117.     /// 數據驗證,如果導出信息填寫有誤,將給用戶錯誤提示  
  118.     /// </summary>  
  119.     private bool Validate()  
  120.     {  
  121.         bool b1 = (exportObject == null);  
  122.         bool b2 = false;  
  123.           
  124.         foreach(Texture2D t in optionalTexture)  
  125.         {  
  126.             b2 = b2 || (t == null);  
  127.         }  
  128.           
  129.         return !(b1 || b2);  
  130.     }  
  131.       
  132.     /// <summary>  
  133.     /// 所有數據正確導出後,清除填寫的導出信息,以便導出下一條數據  
  134.     /// </summary>  
  135.     private void Clear()  
  136.     {  
  137.         exportObject = null;  
  138.         optionalCount = 0;  
  139.     }  
  140. }  

下載實現代碼:

[csharp] view plaincopy
  1. using UnityEngine;  
  2. using System.Collections;  
  3. /// <summary>  
  4. /// author : qyxls  
  5. /// </summary>  
  6. public class Downloader : MonoBehaviour   
  7. {     
  8.     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";  
  9.     private WWW www;  
  10.     private bool isCompleted = false;  
  11.           
  12.     private GameObject dice;  
  13.     private Texture2D tex;  
  14.       
  15.     void Start ()   
  16.     {  
  17.         this.www = new WWW(this.url);  
  18.     }  
  19.       
  20.     void Update ()   
  21.     {  
  22.         if(www == nullreturn;  
  23.         if(!isCompleted && www.isDone)  
  24.         {  
  25.             dice = GameObject.Instantiate(www.assetBundle.mainAsset) as GameObject;  
  26.             isCompleted = true;  
  27.               
  28.             /* 
  29.             // 取回打包在資源內部的數據 由於我們當初放進去的全是Texture2D類型的,所以使用LoadAll的// 
  30.             // 帶類型的重載方法,將Texture2D類型傳入進去,表示我只取出Texture2D類型的數據// 
  31.             Object[] opticals = www.assetBundle.LoadAll(typeof(Texture2D)); 
  32.             foreach(Object o in opticals) 
  33.             { 
  34.                 Texture2D tmp = o as Texture2D; 
  35.                 print("name : " + tmp.name); 
  36.             } 
  37.             */  
  38.             AssetBundleRequest abr = www.assetBundle.LoadAsync("black-dots"typeof(Texture2D));  
  39.             tex = abr.asset as Texture2D;             
  40.         }  
  41.     }  
  42.       
  43.     void OnGUI()  
  44.     {  
  45.         if(GUI.Button(new Rect(20, 20, 100, 40), "CHANGE"))  
  46.         {  
  47.             // 如果還沒下載完,這時候是不能執行替換功能的 //  
  48.             if(dice == null || tex == nullreturn;  
  49.             dice.renderer.material.mainTexture = tex;  
  50.         }  
  51.     }  
  52.       
  53.     /* 
  54.     private void WWWWithParameter(string url, string parameter) 
  55.     { 
  56.         WWWForm form = new WWWForm(); 
  57.         form.AddField("Content", parameter); 
  58.         WWW www = new WWW(url, form); 
  59.     } 
  60.     */  
  61. }  




附上項目下載地址:http://download.csdn.net/detail/qyxls/6039269

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


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