為了盡可能加快從網絡加載場景,我們通常可以把場景先導出成 XML,把優先級高的資源優先加載並顯示(地形等),把可以進入場景之后再加載的對象放到最后(比如場景里面的怪物等),本篇一部分代碼引用自:http://www.xuanyusong.com/archives/1919,導出場景部分在原作者的代碼基礎進行了優化,並且整理成了更加方便,容易使用的類庫。
先來搭建測試場景(測試場景來源網絡),並整理場景中的對象,如圖:
然后把場景中的對象都設置成預設,方便打包成 assetbundle 文件(如何打包預設請查看),如圖:
接着我們編寫把場景打包成 XML 的代碼,取名 ExportSceneToXml.cs,大家可以先看這篇文章(http://www.xuanyusong.com/archives/1919),我在此基礎上面進行了優化,全部代碼如下:
using UnityEngine; using UnityEditor; using System.Collections; using System.Collections.Generic; using System.Xml; using System.IO; using System.Text; public class ExportSceneToXml : Editor { [MenuItem("Assets/Export Scene To XML From Selection")] static void ExportXML() { string path = EditorUtility.SaveFilePanel ("Save Resource", "", "New Resource", "xml"); if (path.Length != 0) { Object[] selectedAssetList = Selection.GetFiltered (typeof(Object), SelectionMode.DeepAssets); //遍歷所有的游戲對象 foreach (Object selectObject in selectedAssetList) { // 場景名稱 string sceneName = selectObject.name; // 場景路徑 string scenePath = AssetDatabase.GetAssetPath(selectObject); // 場景文件 //string xmlPath = path; //Application.dataPath + "/AssetBundles/Prefab/Scenes/" + sceneName + ".xml"; // 如果存在場景文件,刪除 if(File.Exists(path)) File.Delete(path); // 打開這個關卡 EditorApplication.OpenScene(scenePath); XmlDocument xmlDocument = new XmlDocument(); // 創建XML屬性 XmlDeclaration xmlDeclaration = xmlDocument.CreateXmlDeclaration("1.0", "utf-8", null); xmlDocument.AppendChild(xmlDeclaration); // 創建XML根標志 XmlElement rootXmlElement = xmlDocument.CreateElement("root"); // 創建場景標志 XmlElement sceneXmlElement = xmlDocument.CreateElement("scene"); sceneXmlElement.SetAttribute("sceneName", sceneName); foreach (GameObject sceneObject in Object.FindObjectsOfType(typeof(GameObject))) { // 如果對象是激活狀態 if (sceneObject.transform.parent == null && sceneObject.activeSelf) { // 判斷是否是預設 if(PrefabUtility.GetPrefabType(sceneObject) == PrefabType.PrefabInstance) { // 獲取引用預設對象 Object prefabObject = EditorUtility.GetPrefabParent(sceneObject); if(prefabObject != null) { XmlElement gameObjectXmlElement = xmlDocument.CreateElement("gameObject"); gameObjectXmlElement.SetAttribute("objectName", sceneObject.name); gameObjectXmlElement.SetAttribute("objectAsset", prefabObject.name); XmlElement transformXmlElement = xmlDocument.CreateElement("transform"); // 位置信息 XmlElement positionXmlElement = xmlDocument.CreateElement("position"); positionXmlElement.SetAttribute("x", sceneObject.transform.position.x.ToString()); positionXmlElement.SetAttribute("y", sceneObject.transform.position.y.ToString()); positionXmlElement.SetAttribute("z", sceneObject.transform.position.z.ToString()); // 旋轉信息 XmlElement rotationXmlElement = xmlDocument.CreateElement("rotation"); rotationXmlElement.SetAttribute("x", sceneObject.transform.rotation.eulerAngles.x.ToString()); rotationXmlElement.SetAttribute("y", sceneObject.transform.rotation.eulerAngles.y.ToString()); rotationXmlElement.SetAttribute("z", sceneObject.transform.rotation.eulerAngles.z.ToString()); // 縮放信息 XmlElement scaleXmlElement = xmlDocument.CreateElement("scale"); scaleXmlElement.SetAttribute("x", sceneObject.transform.localScale.x.ToString()); scaleXmlElement.SetAttribute("y", sceneObject.transform.localScale.y.ToString()); scaleXmlElement.SetAttribute("z", sceneObject.transform.localScale.z.ToString()); transformXmlElement.AppendChild(positionXmlElement); transformXmlElement.AppendChild(rotationXmlElement); transformXmlElement.AppendChild(scaleXmlElement); gameObjectXmlElement.AppendChild(transformXmlElement); sceneXmlElement.AppendChild(gameObjectXmlElement); } } } } rootXmlElement.AppendChild(sceneXmlElement); xmlDocument.AppendChild(rootXmlElement); // 保存場景數據 xmlDocument.Save(path); // 刷新Project視圖 AssetDatabase.Refresh(); } } } }
然后我們選中需要打包的場景,選擇把場景打包成 XML 的選項,如圖:
生成完成,我們可以查看生成出的 XML 內容,如圖:
這兒為什么說是對原作者的代碼進行了優化,下面我們可以把場景中的一個對象名稱改成與預設名稱不同,如圖:
然后再次導出成 XML 文件,查看 XML 生成的內容我們可以發現,我們可以正確找到預設的名稱,如圖:
另外,我們還可以選擇場景中的哪些文件不用導出,方法很簡單,我們可以先把場景中的對象禁用,再導出,如圖:
再次查看新導出的 XML 文件,我們會發現 XML 中已經不包括了被禁用對象的配置信息,如圖:
以上兩點是對原作者代碼的優化,而且我也改成了使用右鍵導出,個人感覺這樣更加方便、實用。
現在回到場景中,我們可以把場景里面的對象全部刪除,因為場景中已經不需要這些對象了,我們需要通過代碼創建這些對象,如圖:
下面我們來看如何還原場景,有了 XML,我們解析 XML 就可以了,資源的加載可以看這篇文章(查看詳情),加載場景以及預設資源(assetbundle)的代碼如下:
using UnityEngine; using System.Collections.Generic; public class LoaderScene : MonoBehaviour { public UISlider progressBar; public UILabel lblStatus; private string scenePath; void Awake() { string prefabPath = "file:///" + Application.dataPath + "/Assets/{0}.assetbundle"; this.scenePath = "file:///" + Application.dataPath + "/Assets/MainScene.unity3d"; IList<WwwLoaderPath> pathList = new List<WwwLoaderPath> (); pathList.Add (new WwwLoaderPath (this.scenePath, Random.Range (0, 100), WwwLoaderTypeEnum.UNITY_3D)); pathList.Add (new WwwLoaderPath (string.Format(prefabPath, "Lights"), Random.Range (0, 100), WwwLoaderTypeEnum.ASSET_BUNDLE)); pathList.Add (new WwwLoaderPath (string.Format(prefabPath, "Particles"), Random.Range (0, 100), WwwLoaderTypeEnum.ASSET_BUNDLE)); pathList.Add (new WwwLoaderPath (string.Format(prefabPath, "PhysicsCube"), Random.Range (0, 100), WwwLoaderTypeEnum.ASSET_BUNDLE)); pathList.Add (new WwwLoaderPath (string.Format(prefabPath, "Player"), Random.Range (0, 100), WwwLoaderTypeEnum.ASSET_BUNDLE)); pathList.Add (new WwwLoaderPath (string.Format(prefabPath, "Stamps"), Random.Range (0, 100), WwwLoaderTypeEnum.ASSET_BUNDLE)); pathList.Add (new WwwLoaderPath (string.Format(prefabPath, "Statics"), Random.Range (0, 100), WwwLoaderTypeEnum.ASSET_BUNDLE)); pathList.Add (new WwwLoaderPath (string.Format(prefabPath, "Terrain"), Random.Range (0, 100), WwwLoaderTypeEnum.ASSET_BUNDLE)); pathList.Add (new WwwLoaderPath (string.Format(prefabPath, "Trees"), Random.Range (0, 100), WwwLoaderTypeEnum.ASSET_BUNDLE)); this.lblStatus.text = "場景加載中,請稍候。。。"; WwwLoaderManager.instance.Loader (pathList, onLoaderProgress, onLoaderComplete, "MainScene"); } private void onLoaderProgress(string path, float currentValue, float totalValue) { this.progressBar.value = currentValue; } private void onLoaderComplete() { this.lblStatus.text = "場景正在初始化,請等待。。。"; Application.LoadLevelAsync("MainScene"); } }
然后新建立一個 C# 文件,取名:InitObject.cs,代碼如下:
using UnityEngine; using System.Collections; using System.Xml; public class InitObject : MonoBehaviour { void Awake() { string xmlPath = Application.dataPath + "/Assets/MainScene.xml"; string prefabPath = "file:///" + Application.dataPath + "/Assets/{0}.assetbundle"; XmlDocument xmlDocument = new XmlDocument(); xmlDocument.Load (xmlPath); // 使用 XPATH 獲取所有 gameObject 節點 XmlNodeList xmlNodeList = xmlDocument.SelectNodes("//gameObject"); foreach(XmlNode xmlNode in xmlNodeList) { string gameObjectName = xmlNode.Attributes["objectName"].Value; string prefabName = xmlNode.Attributes["objectAsset"].Value; AssetBundle assetBundle = WwwDataManager.instance.GetDataAssetBundle(string.Format(prefabPath, prefabName)); if(assetBundle != null) { GameObject assetObject = (GameObject)assetBundle.Load(prefabName, typeof(GameObject)); if(assetObject != null) { GameObject gameObject = (GameObject)Instantiate(assetObject); // 使用 XPATH 獲取 位置、旋轉、縮放數據 XmlNode positionXmlNode = xmlNode.SelectSingleNode("descendant::position"); XmlNode rotationXmlNode = xmlNode.SelectSingleNode("descendant::rotation"); XmlNode scaleXmlNode = xmlNode.SelectSingleNode("descendant::scale"); if(positionXmlNode != null && rotationXmlNode != null && scaleXmlNode != null) { gameObject.transform.position = new Vector3(float.Parse(positionXmlNode.Attributes["x"].Value), float.Parse(positionXmlNode.Attributes["y"].Value), float.Parse(positionXmlNode.Attributes["z"].Value)); gameObject.transform.rotation = Quaternion.Euler(new Vector3(float.Parse(rotationXmlNode.Attributes["x"].Value), float.Parse(rotationXmlNode.Attributes["y"].Value), float.Parse(rotationXmlNode.Attributes["z"].Value))); gameObject.transform.localScale = new Vector3(float.Parse(scaleXmlNode.Attributes["x"].Value), float.Parse(scaleXmlNode.Attributes["y"].Value), float.Parse(scaleXmlNode.Attributes["z"].Value)); } } // 卸載引用的加載資源,釋放內存 assetBundle.Unload(false); } } xmlDocument = null; } }
然后我們在空的場景中新建立一個空對象,並且把代碼掛載到這個空對象上面,如圖:
再然后我們把這個場景打包成 .unity3d 文件,方便從網絡上面加載(詳情可以查看這篇文章),這樣所有的准備工作都已經做好了,全部的配置文件以及資源文件如下:
我們從加載場景運行項目,我們可以先看到依次在加載主場景資源,加載完成之后進入主場景,根據 XML 的內容,原場景被還原了回來,如圖: