1. 程式人生 > >Unity3d動態載入及生成配置檔案

Unity3d動態載入及生成配置檔案

本文大部分轉載,作者做了關於配置檔案生成工作,但是很遺憾,關於position和rotation資訊目前尚未自動生成,執行本例的朋友,需要自己手動新增位置和角度資訊,否則程式會報錯。
標準的json資料:
  1. {
  2.     "AssetList" : [{
  3.         "Name" : "Chair 1",
  4.         "Source" : "Prefabs/Chair001.unity3d",
  5.         "Position" : [2,0,-5],
  6.         "Rotation" : [0.0,60.0,0.0]
  7.     },
  8.     {
  9.         "Name" : "Chair 2",
  10.         "Source" : "Prefabs/Chair001.unity3d",
  11.         "Position" : [1,0,-5],
  12.         "Rotation" : [0.0,0.0,0.0]
  13.     },
  14.     {
  15.         "Name" : "Vanity",
  16.         "Source" : "Prefabs/vanity001.unity3d",
  17.         "Position" : [0,0,-4],
  18.         "Rotation" : [0.0,0.0,0.0]
  19.     }
  20.     }]
  21. }

      用Unity3D製作基於web的網路遊戲,不可避免的會用到一個技術-資源動態載入。比如想載入一個大場景的資源,不應該在遊戲的開始讓使用者長時間等待全部資源的載入完畢。應該優先載入使用者附近的場景資源,在遊戲的過程中,不影響操作的情況下,後臺載入剩餘的資源,直到所有載入完畢。
       在講述程式碼之前,先想象這樣一個網路遊戲的開發流程。首先美工製作場景資源的3D建模,遊戲設計人員把3D建模導進Unity3D,託託拽拽編輯場景,完成後把每個gameobject匯出成XXX.unity3d格式的資原始檔(參看BuildPipeline),並且把整個場景的資訊生成一個配置檔案,xml或者Json格式(本文使用Json)。最後還要把資原始檔和場景配置檔案上傳到伺服器,最好使用CMS管理。客戶端運行遊戲時,先讀取伺服器的場景配置檔案,再根據玩家的位置從伺服器下載相應的資原始檔並載入,然後開始遊戲,注意這裡並不是下載所有的場景資源。在遊戲的過程中,後臺繼續載入資源直到所有載入完畢。
json.txt:(注:當生成的json無法讀取時,記得改一下編碼格式 改成 ANSI)
{"AssetList":[{"Name":"Sphere","Source":"Prefabs/Sphere.unity3d"},{"Name":"cube","Source":"Prefabs/cube.unity3d"},{"Name":"Sphere","Source":"Prefabs/Sphere.unity3d"},{"Name":"cube","Source":"Prefabs/cube.unity3d"}]}

主程式:

usingUnityEngine; usingSystem.Collections; publicclassMainMonoBehavior:MonoBehaviour{ publicdelegatevoidMainEventHandler(GameObject dispatcher); publiceventMainEventHandlerStartEvent; publiceventMainEventHandlerUpdateEvent; publicvoidStart() { ResourceManager.getInstance().LoadSence("Scenes/json.txt"

);//json配置檔案 if(StartEvent!=null) { StartEvent(this.gameObject); } } publicvoidUpdate() { if(UpdateEvent!=null) { UpdateEvent(this.gameObject); } }  }

    這裡面用到了C#的事件機制,大家可以看看我以前翻譯過的國外一個牛人的文章。C# 事件和Unity3D在 start方法裡呼叫ResourceManager,先載入配置檔案。每一次呼叫update方法,MainMonoBehavior會把update 事件分發給ResourceManager,因為ResourceManager註冊了MainMonoBehavior的update事件。

ResourceManager.cs usingUnityEngine; usingSystem.Collections; usingSystem.Collections.Generic; usingLitJson; usingSystem.Net; publicclassResourceManager  { // Use this for initialization privateMainMonoBehavior mainMonoBehavior; privatestring mResourcePath; privateScene mScene; privateAsset mSceneAsset; privatestaticResourceManager resourceManager=newResourceManager();//如果沒有new 會出現沒有例項化的錯誤 privateDictionary<string,WWW > wwwCacheMap=newDictionary<string,WWW>();//這個新新增的原文程式碼中沒有定義它 應該是個字典 publicstaticResourceManager getInstance() { return resourceManager;//靜態函式中不能使用非靜態成員 } publicResourceManager() {                  mainMonoBehavior =GameObject.Find("Main Camera").GetComponent<MainMonoBehavior>();                  mResourcePath ="file://"+Application.dataPath+"/.."; } publicvoidLoadSence(string fileName) { //Debug.Log(fileName);                  mSceneAsset =newAsset();                  mSceneAsset.Type=Asset.TYPE_JSON;//後面型別判斷有用                  mSceneAsset.Source= fileName;                  mainMonoBehavior.UpdateEvent+=OnUpdate;//新增監聽函式 } publicvoidOnUpdate(GameObject dispatcher)//該函式是監聽函式,每幀都會被呼叫(它的新增是在MainMonoBehavior的start函式中通過呼叫本地LoadSence函式) { if(mSceneAsset !=null)//表示已經通過了new操作分配類記憶體空間但是資源還沒有載入完 { LoadAsset(mSceneAsset);//這個函式裡面會通過判斷,使www的new操作只執行一次 if(!mSceneAsset.isLoadFinished)//C#中 bool型別預設是false { return; }                          mScene =null;                          mSceneAsset =null; }                  mainMonoBehavior.UpdateEvent-=OnUpdate;//當所有資源被載入後,刪除監聽函式。 } //最核心的函式 privateAssetLoadAsset(Asset asset) { string fullFileName =mResourcePath+"/"+ asset.Source;// mResourcePath + "/" + asset.Source; Debug.Log("fullFileName="+ fullFileName); //if www resource is new, set into www cache if(!wwwCacheMap.ContainsKey(fullFileName)) {//自定義字典 檢視開頭的定義 if(asset.www ==null) {//表示www還沒有new操作                                  asset.www =new WWW(fullFileName); returnnull; } if(!asset.www.isDone) { returnnull; }                          wwwCacheMap.Add(fullFileName, asset.www);//該字典是作為快取的作用,如果之前已經載入過同樣的Unity3D格式檔案,那麼不需要在載入,直接拿來用就行了。  if(asset.Type==Asset.TYPE_JSON) {//Json 表示當txt檔案被首次載入時的處理 if(mScene ==null) {        string jsonTxt = mSceneAsset.www.text; Debug.Log("jsonTxt="+ jsonTxt);                              mScene =JsonMapper.ToObject<Scene>(jsonTxt);//mScene是個Asset物件列表,也就是Json檔案需要一個AssetList列表物件,注意名字的統一,列表中Asset物件中的成員名稱要和txt                //檔案中的相關名稱統一 不然JsonMapper無法找到 } //load scene foreach(Asset sceneAsset in mScene.AssetList) { if(sceneAsset.isLoadFinished) { continue; } else { LoadAsset(sceneAsset);//這裡的處理就是 下面 Asset.TYPE_GAMEOBJECT的處理方式,注意是遞迴函式的呼叫 if(!sceneAsset.isLoadFinished) { returnnull; } } } } elseif(asset.Type==Asset.TYPE_GAMEOBJECT)//處理檔案中具體資訊,巢狀關係或者直接是一個GameObject物件,與上面的程式碼有聯絡,Asset建立時候的建構函式中設定成 //TYPE_GAMEOBJECT型別 {//Gameobject if(asset.gameObject ==null)//如果不為null 表示已經通過wwwCacheMap載入了資源包中所包含的資源(fullFileName僅僅是一個檔案,資源包中資源是分散的GameObject物件),不需要在重新載入 {                                  wwwCacheMap[fullFileName].assetBundle.LoadAll();//已經通過new WWW操作完成了載入,該函式用來載入包含著資源包中的資源 GameObject go =(GameObject)GameObject.Instantiate(wwwCacheMap[fullFileName].assetBundle.mainAsset); UpdateGameObject(go, asset);                                  asset.gameObject = go; } if(asset.AssetList!=null)//有巢狀關係 { foreach(Asset assetChild in asset.AssetList) { if(assetChild.isLoadFinished) { continue; } else { Asset assetRet =LoadAsset(assetChild); if(assetRet !=null)//這個if else 語句是為了防止你的配置檔案中的GameObject物件路徑不正確,導致訪問空指標。 {                                                          assetRet.gameObject.transform.parent = asset.gameObject.transform; } else { returnnull; } } } } }                  asset.isLoadFinished =true; return asset; } privatevoidUpdateGameObject(GameObject go,Asset asset) { //name                  go.name = asset.Name; //position Vector3 vector3 =newVector3((float)asset.Position[0],(float)asset.Position[1],(float)asset.Position[2]);                  go.transform.position = vector3; //rotation                 vector3 =newVector3((float)asset.Rotation[0],(float)asset.Rotation[1],(float)asset.Rotation[2]);                  go.transform.eulerAngles = vector3; }  }

ViewCode Asset.cs類: usingUnityEngine; usingSystem.Collections.Generic; publicclassAsset  { publicconstbyte TYPE_JSON =1; publicconstbyte TYPE_GAMEOBJECT =2; publicAsset() { //default type is gameobject for json load Type= TYPE_GAMEOBJECT; } publicbyteType { get; set; } publicstringName { get; set; } publicstringSource { get; set; } publicdouble[]Bounds { get; set; } publicdouble[]Position { get; set; } publicdouble[]Rotation { get; set; } publicList<Asset>AssetList { get; set; } publicbool isLoadFinished { get; set; } public WWW www { get; set; } publicGameObject gameObject { get; set; }  } Scene.cs類: usingSystem.Collections.Generic; publicclassScene  { publicList<Asset>AssetList { get; set; }  }

生成.unity3d程式碼:

ViewCode usingUnityEngine; usingUnityEditor; usingSystem.IO; usingSystem; usingSystem.Text; usingSystem.Collections.Generic; usingLitJson; publicclassBuildAssetBundlesFromDirectory  { staticList<JsonResource> config=newList<JsonResource>(); staticDictionary<string,List<JsonResource>> assetList=newDictionary<string,List<JsonResource>>(); [@MenuItem("Asset/Build AssetBundles From Directory of Files")]//這裡不知道為什麼用"@",新增選單 staticvoidExportAssetBundles() {//該函式表示通過上面的點選響應的函式                     assetList.Clear(); string path =AssetDatabase.GetAssetPath(Selection.activeObject);//Selection表示你滑鼠選擇啟用的物件 Debug.Log("Selected Folder: "+ path); if(path.Length!=0) {                             path = path.Replace("Assets/","");//因為AssetDatabase.GetAssetPath得到的是型如Assets/資料夾名稱,且看下面一句,所以才有這一句。 Debug.Log("Selected Folder: "+ path); string[] fileEntries =Directory.GetFiles(Application.dataPath+"/"+path);//因為Application.dataPath得到的是型如 "工程名稱/Assets" string[] div_line =newstring[]{"Assets/"}; foreach(string fileName in fileEntries) {                                      j++; Debug.Log("fileName="+fileName); string[] sTemp = fileName.Split(div_line,StringSplitOptions.RemoveEmptyEntries); string filePath = sTemp[1]; Debug.Log(filePath);                                      filePath ="Assets/"+ filePath; Debug.Log(filePath); string localPath = filePath; UnityEngine.Object t =AssetDatabase.LoadMainAssetAtPath(localPath); //Debug.Log(t.name); if(t !=null) { Debug.Log(t.name); JsonResource jr=newJsonResource();                                              jr.Name=t.name;                                              jr.Source=path+"/"+t.name+".unity3d"; Debug.Log( t.name);                                              config.Add(jr);//例項化json物件 string bundlePath =Application.dataPath+"/../"+path; if(!File.Exists(bundlePath)) { Directory.CreateDirectory(bundlePath);//在Asset同級目錄下相應資料夾 }                                                bundlePath+="/"+ t.name +".unity3d"; Debug.Log("Building bundle at: "+ bundlePath); BuildPipeline.BuildAssetBundle(t,null, bundlePath,BuildAssetBundleOptions.CompleteAssets);//在對應的資料夾下生成.unity3d檔案 } }                              assetList.Add("AssetList",config); for(int i=0;i<config.Count;i++) { Debug.Log(config[i].Source); } } string data=JsonMapper.ToJson(assetList);//序列化資料 Debug.Log(data); string jsonInfoFold=Application.dataPath+"/../Scenes"; if(!Directory.Exists(jsonInfoFold)) { Directory.CreateDirectory(jsonInfoFold);//建立Scenes資料夾 } string fileName1=jsonInfoFold+"/json.txt"; if(File.Exists(fileName1)) { Debug.Log(fileName1 +"already exists"); return; } UnicodeEncoding uni=newUnicodeEncoding(); usingFileStream  fs=File.Create(fileName1))//向建立的檔案寫入資料 {                          fs.Write(uni.GetBytes(data),0,uni.GetByteCount(data));                          fs.Close(); } }  }