我們的游戲制作完發(fā)布出去提供給玩家,,為了給玩家?guī)砀玫挠螒蝮w驗,要做各種的優(yōu)化以及設計,,首先,,游戲資源的加載就是一個非常重要的方面(尤其是網頁游戲)。由于我們的游戲資源比較大,,不能一下全部加載出來,,如果是這樣,,可能會造成玩家長時間的等待。所以我們應該采取動態(tài)加載的方式,,讓玩家在玩游戲的過程中來一點一點從服務器加載游戲資源,。要實現(xiàn)這樣的效果,首先就必須要制作用于一點點加載的游戲資源,。
(注:本文只是談及這些游戲資源的制作和下載,,關于游戲運行中的動態(tài)加載不做討論)
(再注:本文涉及到的代碼都是以C#語言來編寫的)
開發(fā)環(huán)境:
Windows 7
Unity3D 3.5.1f2
本文中將會涉及到以下的內容:
1、 UnityEditor命名空間
2,、 Editor模式下窗口制作
3,、 導出功能的具體實現(xiàn)
4、 資源的下載
5,、 下載后使用
1,、 UnityEditor命名空間
這個命名空間下的類是在Unity的編輯模式下使用的,我們可以用它來制作各種小工具來輔助開發(fā),,提高開發(fā)效率,。這里的所有的類都不能在Unity的運行時里使用。只能在編輯器下使用,,并且在使用他們的時候還必須要放到項目Project視圖下的Editor文件夾中。需要注意一點的就是,,我們的項目代碼里如果有使用到UnityEditor命名空間時,,在項目的最后編譯是不能通過的,必須要移除他們,。
我們來看一個我們即將使用到的一個Attribute:
MenuItem是UnityEditor命名空間下的一個屬性標志,,它可以定義出一個菜單條目,并添加在Unity編輯器的菜單欄中,,語法:
-
[MenuItem(“Tools/Export”)]
我們來新建一個工程看一下效果(具體創(chuàng)建步驟這里真的不說了)
(注:我的項目中加了很多裝飾性的東西,,這里就不一一說明怎么實現(xiàn)了哈)
完成之后,先在Project下創(chuàng)建Editor文件夾并創(chuàng)建一個腳本文件,,輸入以下內容:
-
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時,,
菜單項會調用靜態(tài)的Execute()方法,即可在Console面板中打印出”Menu is selected”,。
這里要注意兩點:
1,、 引入UnityEditor命名空間。
2,、 MenuItem要調用的方法需要是static的,。
關于UnityEditor的更多詳細內容,請參照官方文檔,,這里不做重點講解,。
2,、Editor模式下窗口制作
要制作一個小工具,提供出一個友好界面是很有必要的,。UnityEditor下的類可以很方便的完成這一需求,。我們通過這些類,可以實現(xiàn)各種不同的控件:
怎么樣,,還算豐富吧,?這些控件的具體實現(xiàn)我不想說,請自行查看API吧,。
這里我還是遵循本文的主旨,,圍繞本文的中心思想(本文我們是要導出資源到服務器,并在游戲中下載這個資源過來使用)實現(xiàn)一個界面,。
用例描述:
導出場景中的一個模型,,并帶著默認材質,如果該模型有多個可替換的貼圖,,也把這些貼圖作為該模型的資源一并導出到一個資源包中,。
按照這個需求,我猜想界面應該是這樣的:
一個導出模型的口,,一個提供可選貼圖數量的口,,根據用戶輸入的可選數量,給提供出對應的貼圖導出口,,最后填寫完畢之后有一個按鈕用于導出交互,。
,不好意思,,這哪里是猜想,,我其實早就寫好了。其實也沒騙你了,,我在寫之前是猜想的,!
要實現(xiàn)上面這個窗口,我該怎么做呢,?
首先,,定義一個繼承EditorWindow的類,然后,,重寫OnGUI方法即可,。我們這里在之前的代碼基礎上做修改添加:
-
using UnityEditor;
-
using UnityEngine;
-
-
/// <summary>
-
/// author : qyxls
-
/// </summary>
-
public class ExportTools :<strong> <span >"color:#ff0000;">EditorWindow</span> </strong>
-
{
-
[MenuItem("Tools/Export")]
-
static void Execute ()
-
{
-
// 實例化一個Window窗口 //
-
ExportTools windows = EditorWindow.GetWindow<ExportTools>(true, "Export Tools");
-
}
-
-
void OnGUI()
-
{
-
-
}
-
}
這里要注意的就是將原來的腳本有繼承自MonoBehaviour 修改為繼承自EditorWindow。并在Execute ()方法中對當前的Window實例化,。這時我們就可以得到一個Window窗口了:
其次,,就是向我們生成的窗口中添加不同的控件,這些控件的生成都是在OnGUI()方法中實現(xiàn)的,。和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、導出功能的具體實現(xiàn)
以上只是實現(xiàn)出了這樣一個窗口,,具體響應功能,,以及必要的邏輯實現(xiàn)還都不具備,這里我們將為這個窗口添加具體的功能實現(xiàn)代碼,。
-
<span >"white-space:pre"> </span>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;
-
}
這里我又新添加了三個方法來具體實現(xiàn)導出并保存的邏輯:
- private voidExportAndSave(GameObject go):
在這個方法里只要關注一下怎么打開一個保存對話框就可以了
-
//該方法將打開保存對話框,,選擇導出文件的保存位置,。第二和第三個參數表示默認保存位置和默認文件名//
-
savePath =EditorUtility.SaveFilePanel("Save", @"E:\", go.name,"unity3d");
- private void Export(GameObjectgo, string filePath)
這個方法具體實現(xiàn)了導出二進制文件的功能。這里需要說明的是 BuildPipeline.BuildAssetBundle(): 該方法是將提供的對象導出成Unity能識別的二進制文件第一個參數是提供一個要導出的對象,,第二個參數是一個Object[]類型,,它可以將數據附加到第一個參數定義的主數據一起整體導出.但是這兩個參數要求必須是磁盤文件的格式,所以上面的if語句判斷是否是磁盤文件類型,,如果不是,,先將其轉化為prefab,在Assets下臨時保存一下,。這個轉化就是要用到 PrefabUtility 類里的方法,。具體判斷是否是磁盤文件,,是通過
if(!EditorUtility.IsPersistent(go))這一句來判斷的:如果go不是磁盤文件,是場景對象,,則執(zhí)行該語句里的代碼來生成磁盤文件,,具體的是下面這個方法來實現(xiàn)的。
- private ObjectGetPrefab(GameObject go, string name)
我們在導出前,,如果導出信息設置的不正確,,可能會致使導出的文件有問題或者不可用,所以在導出之前對信息有效性的驗證也是必要的:
-
<span >"white-space:pre"> </span>/// <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,,導出時可以根據返回值狀態(tài)來做相應的響應,。
-
<span >"white-space:pre"> </span>// 導出按鈕 //
-
if(GUILayout.Button("EXPORT", GUILayout.Width(100), GUILayout.Height(20)))
-
{
-
if(Validate())
-
{
-
ExportAndSave(exportObject);
-
Clear();//成功導出數據后,清除導出信息//
-
}
-
else
-
{
-
//導出信息填寫有誤時,,給出提示//
-
EditorUtility.DisplayDialog("錯誤提示", "導出信息設置有誤,,請返回檢查!", "確定");
-
}
-
}
這里可以看到我還添加了一個Clear()方法,,該方法在用戶導出完畢時,,將導出工具面板的信息清除掉,以便開始導出其它資源:
-
<span >"white-space:pre"> </span>/// <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打開下載列表,,右擊正在下載的文件,選擇“復制鏈接地址”就可以取到該文件的完整地址了。
這個是我的:
http://bj./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
這里我們暫且先這樣用著,,在真正的項目開發(fā)中,,資源的地址肯定會直接或間接的給出來的,這里不必糾結,。
我們來具體看看下載,,這里下載要使用到的類是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./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之后,,會發(fā)現(xiàn)很快就會在Console視圖中打印出來了“Download completed”,而且還孜孜不倦的一直不肯停歇,,這里我們下載完了,,只要對下載完的資源處理一次就夠了,沒必要沒完沒了的處理,,多浪費感情啊,,所以我們該定義一個標志,來標記下載完成這么一個狀態(tài):
-
private bool isCompleted = false;
-
void Update ()
-
{
-
if(www == null) return;
-
if(!isCompleted && www.isDone)
-
{
-
print ("Download completed");
-
isCompleted = true;
-
}
-
}
現(xiàn)在是不是只有這么一條打印信息了,?
這段代碼是非常簡單的,,這里也沒有什么要多說的,就是提這么一點,,這里我們是直接根據資源的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訪問服務器就可以了,。
(本例中我們沒有采用帶參數的訪問是因為這樣的話,,我們還要加一個后臺處理程序,要根據請求參數來返回數據,,這樣我們就必須要在本機上安裝服務器,,書寫服務器代碼等等等等,這樣就得多做很多其它與我們這個話題相去深遠的工作了,。,。。。,。,。。,。(好吧,,我承認我不會配置服務器))
到此本節(jié)的全部代碼是這樣子的:
-
using UnityEngine;
-
using System.Collections;
-
/// <summary>
-
/// author : qyxls
-
/// </summary>
-
public class Downloader : MonoBehaviour
-
{
-
private string url = " http://qd./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;
-
}
-
}
只需這樣一句代碼,你就在場景中可以看到這個令人興奮的小東西了,,哈哈,,是不是很簡單呢?
但是有沒有發(fā)現(xiàn)什么問題呢,?
我們當初導出的可不僅僅這點東西啊,,我們回過頭來看看:
起碼還有這些個貼圖怎么不見了?當初導出時可是明明放到Object[]一起導出了的,。莫著急,,其實它們也都一起下載過來了,只是我們還沒有取來用罷了,。
再看www對象或者www.assetBundle里面有一個方法返回了Object[]數組:www.assetBundle.LoadAll(),。抓緊去試試:
-
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,,這不合適啊,。細心的你也一定會發(fā)現(xiàn),多出的那 2 張貼圖,,正是剛剛導出的模型上本身的一張漫反射貼圖和一張法線貼圖,。原來,LoadAll()這個方法會將存在于下載過來的這個文件中符合類型的所有資源都取過來的,,這也很簡單處理,,只要我們把不符合要求的剔除掉就好了。這里實現(xiàn)起來很簡單,,我就不說了,,我這里想說的是另一種方法,這個是開發(fā)中比較常用的,。
我們使用的時候,,一般都是取確定的某個對象,可以通過Load(string name)方法來取得,,這個方法返回的是一個AssetBundleRequest類型的值,,我們可以通過它里面的asset屬性取到需要的數據:
-
AssetBundleRequest abr = www.assetBundle.LoadAsync("black-dots", typeof(Texture2D));
到現(xiàn)在,我們就下載過來了所有數據,,并且可以取出需要的數據來使用了,。接下來,我們完善一下這個小例子,,把下載過來的資源充分的使用起來,,就是給這個小東西換一個貼圖。
這里完整的代碼是這樣子的:
-
using UnityEngine;
-
using System.Collections;
-
/// <summary>
-
/// author : qyxls
-
/// </summary>
-
public class Downloader : MonoBehaviour
-
{
-
private string url = "http://qd./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"))
-
{
-
// 如果還沒下載完,,這時候是不能執(zhí)行替換功能的 //
-
if(dice == null || tex == null) return;
-
dice.renderer.material.mainTexture = tex;
-
}
-
}
-
}
運行結果:
開始未替換之前:
替換之后:
到現(xiàn)在我們這個的流程以及要求就基本實現(xiàn)了,,這里別忘了最后一步,清理使用完的無用資源,,釋放內存,。
下載完的數據都保存在內存中,這時候它們都是一個AssetBundle的內存鏡像,,我們在使用數據時,,只是從內存鏡像里取出數據,通過Instance方法新實例化出來的一個對象,,當我們有了這樣一個對象,,以后的操作都是針對這樣的一個,而內存中保存的那塊鏡像已經沒有用處了,,我們可以釋放掉:
AssetBundle.Unload(flase) : 是釋放AssetBundle文件的內存鏡像,,不包含Load創(chuàng)建的Asset內存對象,。
AssetBundle.Unload(true) : 是釋放那個AssetBundle文件內存鏡像和并銷毀所有用Load創(chuàng)建的Asset內存對象。
這里我們使用
-
www.assetBundle.Unload(false);
之所以不使用
-
www.assetBundle.Unload(true);
是因為我們不能銷毀掉實例化出來的Asset對象,,我們還要繼續(xù)操作它(下面的換貼圖等),。否則,該對象會在場景里消失,,徹底銷毀掉了,。
所有代碼再給大家列一遍:
導出工具的代碼:
-
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;
-
}
-
}
下載實現(xiàn)代碼:
-
using UnityEngine;
-
using System.Collections;
-
/// <summary>
-
/// author : qyxls
-
/// </summary>
-
public class Downloader : MonoBehaviour
-
{
-
private string url = "http://qd./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"))
-
{
-
// 如果還沒下載完,,這時候是不能執(zhí)行替換功能的 //
-
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);
-
}
-
*/
-
}
附上項目下載地址:http://download.csdn.net/detail/qyxls/6039269
自己測試的代碼:從服務器上獲取模型資源動態(tài)加載
- using UnityEngine;
- using System.Collections;
-
- public class NewBehaviourScript : MonoBehaviour {
-
- private WWW www;
- private string url = @"http://www./file/02674ec2517acea386d30b31cc9920b1?xcode=842f4ac265ea0630cba454aa5bd13c58e4c1b64a40e54dd3&fid=1209145999-250528-2686658773&time=1382947909&sign=FDTAXER-DCb740ccc5511e5e8fedcff06b081203-kZwHL122gdsgehOwysY9B84Myyc%3D&to=wb&fm=N,B,T,t&expires=8h&rt=sh&r=631015269&logid=1425903517&sh=1&fn=is.unity3d
- ";
- private GameObject dice;
- // Use this for initialization
- void Start () {
- this.www = new WWW(this.url);
- }
-
- private bool isCompleted = false;
- // Update is called once per frame
- void Update () {
- if (www == null) return;
- if (!isCompleted && www.isDone)
- {
- print("Download completed");
- isCompleted = true;
- dice =GameObject.Instantiate(www.assetBundle.mainAsset) as GameObject;
- dice.transform.position = new Vector3(0, 1, 20);
- //Instantiate(www.assetBundle.mainAsset);
- // print(dice);
- }
- }
- }
|