本篇接着上一篇繼續和大家分享場景資源這一主題,主要包括兩個方面:
(1)加載場景
場景異步加載的代碼比較簡單,如下所示:
private IEnumerator LoadLevelCoroutine() { string url = "ftp://127.0.0.1/TestScene.unity3d"; int verNum = 1; WWW wwwForScene = WWW.LoadFromCacheOrDownload(url, verNum); while (wwwForScene.isDone == false) yield return null; AssetBundle bundle = wwwForScene.assetBundle; yield return Application.LoadLevelAsync("TestScene"); wwwForScene.assetBundle.Unload(false); }
(2)加載場景物件
主要包含以下細分步驟:
a、下載並解析場景配表,得到場景物件信息。場景物件的數據結構如下所示:
public class XmlSceneGameobjectProp { // Mesh信息 public class MeshInfo { public string name; public string shader; public bool hasColor = false; public Vector4 color; public bool isStatic = true; public int lightmapIndex; public Vector4 lightmapTilingOffset; } public string name; public string group; // Transform信息 public float posX, posY, posZ; public float rotX, rotY, rotZ; public float scaleX, scaleY, scaleZ; // Mesh列表,一個模型可以包含多個MeshRenderer public List<MeshInfo> LstMesh = new List<MeshInfo>(); }
xml解析的主體代碼如下所示:
private void ParseChildNode(XmlElement xmlGroup, XmlElement xmlChild) { SceneGameobjectProp newChild = new SceneGameobjectProp(); newChild.group = xmlGroup.GetAttribute("name"); newChild.name = xmlChild.GetAttribute("name"); // 注冊資源名字 if (lstRes.Contains(newChild.name) == false) { lstRes.Add(newChild.name); } // Tranform節點 XmlNode xmlTransform = xmlChild.SelectSingleNode("Transform"); // MeshRenderer節點 XmlNode xmlMeshRenderer = xmlChild.SelectSingleNode("MeshRenderer"); if (xmlTransform != null && xmlTransform is XmlElement) { CXmlRead goReader = new CXmlRead(xmlTransform as XmlElement); newChild.posX = goReader.Float("posX", 0f); newChild.posY = goReader.Float("posY", 0f); newChild.posZ = goReader.Float("posZ", 0f); newChild.rotX = goReader.Float("rotX", 0f); newChild.rotY = goReader.Float("rotY", 0f); newChild.rotZ = goReader.Float("rotZ", 0f); newChild.scaleX = goReader.Float("scaleX", 1f); newChild.scaleY = goReader.Float("scaleY", 1f); newChild.scaleZ = goReader.Float("scaleZ", 1f); } if (xmlMeshRenderer != null && xmlMeshRenderer is XmlElement) { foreach (XmlNode node in xmlMeshRenderer.ChildNodes) { if ((node is XmlElement) == false) continue; SceneGameobjectProp.MeshInfo mesh = new SceneGameobjectProp.MeshInfo(); mesh.name = (node as XmlElement).GetAttribute("Mesh"); mesh.shader = (node as XmlElement).GetAttribute("Shader"); XmlNode xmlLightmap = node.SelectSingleNode("Lightmap"); if (xmlLightmap != null && xmlLightmap is XmlElement) { CXmlRead reader = new CXmlRead(xmlLightmap as XmlElement); mesh.isStatic = reader.Bool("IsStatic", true); mesh.lightmapIndex = reader.Int("LightmapIndex", -1); mesh.lightmapTilingOffset = new Vector4(reader.Float("OffsetX", 0f), reader.Float("OffsetY", 0f), reader.Float("OffsetZ", 0f), reader.Float("OffsetW", 0f)); } XmlNode xmlColor = node.SelectSingleNode("Color"); if (xmlColor != null && xmlColor is XmlElement) { CXmlRead reader = new CXmlRead(xmlColor as XmlElement); mesh.hasColor = reader.Bool("hasColor", false); mesh.color = new Vector4(reader.Float("r", 0f), reader.Float("g", 0f), reader.Float("b", 0f), reader.Float("a", 0f)); } newChild.LstMesh.Add(mesh); } } lstGameObjectProp.Add(newChild); }
b、加載場景物件asset
同時開啟多個Coroutine進行WWW的LoadFromCacheOrDownload操作,經測試開啟的WWW線程越多,速度會越快,但是需要考慮實際的機器或平台的承載能力。
注意,我這兒的WWW操作是直接從緩存里面載入內存,而不是從網上下載。所有更新的游戲物件,可以在游戲開始的時候一次從網上Download到Cache,這樣,在游戲過程中就不需要從網上Download資源了,wifi下載3G玩,爽歪歪~
如果一定要在此處從網上Download資源的話,線程數最好設為5個,很多平台有自己的限制,比如有的網頁瀏覽器只能同時開6個等等......
// 同時開啟的Coroutine的數目 private const int ThreadNum = 100; // 記錄每個加載線程的進度,只有每個線程都加在結束了,場景加載才算完成 private int[] arrThreadProggress = new int[ThreadNum]; // 加載完成后的回掉 public delegate void LoadFinishDelegate(); public LoadFinishDelegate OnLoadFinish = null; // 需要下載的資源列表 private List<string> lstRes = new List<string>(); // 是否加載完畢的標記 private bool hasFinished = false; private void LoadAsset() {for (int i = 0; i < ThreadNum; ++i) { CoroutineProvider.Instance().StartCoroutine(LoadAssetCoroutine(i)); } } private IEnumerator LoadAssetCoroutine(int threadIndex) { while (arrThreadProggress[threadIndex] < lstRes.Count) { // 載入資源 string name = lstRes[arrThreadProggress[threadIndex]]; GameApp.GetResourceManager().LoadAsync(GlobalSetting.SceneAssetPath + name, typeof(GameObject)); while (GameApp.GetResourceManager().IsResLoaded(GlobalSetting.SceneAssetPath + name) == false) { yield return null; } arrThreadProggress[threadIndex] += ThreadNum; } // 線程資源下載完畢,進行加載回掉 if (IsLoadFinished() && hasFinished == false) { hasFinished = true;if (OnLoadFinish != null) { OnLoadFinish(); } } }
上面的黑體標出的代碼是是實際的加載代碼,具體實現已經在帖子“AssetBundle系列——資源的加載、簡易的資源管理器”中講解過了,此處不再贅述。
c、實例化場景物件
// 實例化 GameObject goIns = GameObject.Instantiate(asset) as GameObject; goIns.name = goProp.name; // 設置父節點 GameObject goGroup = null; dicGroupGameobject.TryGetValue(goProp.group, out goGroup); if (goGroup != null) goIns.transform.parent = goGroup.transform; else goIns.transform.parent = goRoot.transform; // 設置Transform goIns.transform.position = new Vector3(goProp.posX, goProp.posY, goProp.posZ); goIns.transform.eulerAngles = new Vector3(goProp.rotX, goProp.rotY, goProp.rotZ); goIns.transform.localScale = new Vector3(goProp.scaleX, goProp.scaleY, goProp.scaleZ); // 設置Shader、Lightmap int index = 0; int meshCount = goProp.LstMesh.Count; foreach (MeshRenderer mr in goIns.gameObject.GetComponentsInChildren<MeshRenderer>(true)) { if (mr.sharedMaterial != null) { if (index < meshCount) { SceneGameobjectProp.MeshInfo meshProp = goProp.LstMesh[index]; mr.sharedMaterial.shader = Shader.Find(meshProp.shader); if (meshProp.hasColor) mr.sharedMaterial.color = meshProp.color; bool isStatic = meshProp.isStatic; mr.gameObject.isStatic = isStatic; if (isStatic) { mr.lightmapIndex = meshProp.lightmapIndex; mr.lightmapTilingOffset = meshProp.lightmapTilingOffset; } } index++; } }
本帖主要是關於assetbundle的處理方法,關於物體材質的實例化邏輯,有很多種做法,我這只是提供了其中一種做法。其中有一點需要注意的,就是material和sharedMaterial的區別,上面實例化中的代碼,我用的是sharedMaterial來設置,使用material是有問題的,因為每一次對material的賦值會導致生成一個materil的instance產生。