【转】Unity利用Xml和Json保存场景
原文:http://blog.****.net/y1196645376/article/details/52549082
1.首先你得有一个场景。这个迷宫地形我搭了好久。。
可以看到地图中有个环境Env,人物Player,灯光Light,UGUI事件系统EventSystem,画布Canvas。Init不用管,先删掉。我们把这几个物品全部做成Prefab。我放在了Prefab的文件夹里面。
值得注意的是,如果这些Prefab或者其子物体中身上所挂脚本有pubulic变量添加的是其他物体或者组件的引用需要将这些变量全部私有化然后放到Awake函数里面通过Game.FindWithTag()或者Transform.Find等函数动态获取。
当然,以上的解决方案可能让你很不习惯,没有更好的解决方法了吗?当然有,这个教程中我们只是对资源设置了Prefab。后面我们用篇文章来讲述AssetBundle的使用,而这个技术不仅实现了资源的打包,而且很好的处理了这些资源依赖关系。这也就是后话了。。。这篇文章不涉及。
2.做完以上步骤之后,我们对资源的处理就完毕了,剩下我们就来编写代码实现对场景信息记录。大概的思路就是:读取所有场景信息,然后对于每个场景遍历其所有的Prefab物体,然后记录其坐标,旋转,大小。( 注意:下面代码中涉及到LitJsond包的使用,请将LitJson.dll放到工程的Plugin目录下 )
- using UnityEngine;
- using System.Collections;
- using UnityEditor;
- using System.Collections.Generic;
- using System.Xml;
- using System.IO;
- using System.Text;
- using LitJson;
- public class ScencePack : Editor
- {
- //将所有游戏场景导出为XML格式
- [MenuItem("GameObject/ExportXML")]
- static void ExportXML()
- {
- string filepath = Application.dataPath + @"/StreamingAssets/my.xml";
- if (!File.Exists(filepath))
- {
- File.Delete(filepath);
- }
- XmlDocument xmlDoc = new XmlDocument();
- XmlElement root = xmlDoc.CreateElement("gameObjects");
- //遍历所有的游戏场景
- foreach (UnityEditor.EditorBuildSettingsScene S in UnityEditor.EditorBuildSettings.scenes)
- {
- //当关卡启用
- if (S.enabled)
- {
- //得到关卡的名称
- string name = S.path;
- //打开这个关卡
- EditorApplication.OpenScene(name);
- XmlElement scenes = xmlDoc.CreateElement("scenes");
- scenes.SetAttribute("name", name);
- foreach (GameObject obj in Object.FindObjectsOfType(typeof(GameObject)))
- {
- if (obj.transform.parent == null)
- {
- XmlElement gameObject = xmlDoc.CreateElement("gameObjects");
- gameObject.SetAttribute("name", obj.name);
- gameObject.SetAttribute("asset", obj.name + ".prefab");
- XmlElement transform = xmlDoc.CreateElement("transform");
- XmlElement position = xmlDoc.CreateElement("position");
- XmlElement position_x = xmlDoc.CreateElement("x");
- position_x.InnerText = obj.transform.position.x + "";
- XmlElement position_y = xmlDoc.CreateElement("y");
- position_y.InnerText = obj.transform.position.y + "";
- XmlElement position_z = xmlDoc.CreateElement("z");
- position_z.InnerText = obj.transform.position.z + "";
- position.AppendChild(position_x);
- position.AppendChild(position_y);
- position.AppendChild(position_z);
- XmlElement rotation = xmlDoc.CreateElement("rotation");
- XmlElement rotation_x = xmlDoc.CreateElement("x");
- rotation_x.InnerText = obj.transform.rotation.eulerAngles.x + "";
- XmlElement rotation_y = xmlDoc.CreateElement("y");
- rotation_y.InnerText = obj.transform.rotation.eulerAngles.y + "";
- XmlElement rotation_z = xmlDoc.CreateElement("z");
- rotation_z.InnerText = obj.transform.rotation.eulerAngles.z + "";
- rotation.AppendChild(rotation_x);
- rotation.AppendChild(rotation_y);
- rotation.AppendChild(rotation_z);
- XmlElement scale = xmlDoc.CreateElement("scale");
- XmlElement scale_x = xmlDoc.CreateElement("x");
- scale_x.InnerText = obj.transform.localScale.x + "";
- XmlElement scale_y = xmlDoc.CreateElement("y");
- scale_y.InnerText = obj.transform.localScale.y + "";
- XmlElement scale_z = xmlDoc.CreateElement("z");
- scale_z.InnerText = obj.transform.localScale.z + "";
- scale.AppendChild(scale_x);
- scale.AppendChild(scale_y);
- scale.AppendChild(scale_z);
- transform.AppendChild(position);
- transform.AppendChild(rotation);
- transform.AppendChild(scale);
- gameObject.AppendChild(transform);
- scenes.AppendChild(gameObject);
- root.AppendChild(scenes);
- xmlDoc.AppendChild(root);
- xmlDoc.Save(filepath);
- }
- }
- }
- }
- //刷新Project视图, 不然需要手动刷新哦
- AssetDatabase.Refresh();
- }
- //将所有游戏场景导出为JSON格式
- [MenuItem("GameObject/ExportJSON")]
- static void ExportJSON()
- {
- string filepath = Application.dataPath + @"/StreamingAssets/json.txt";
- FileInfo t = new FileInfo(filepath);
- if (!File.Exists(filepath))
- {
- File.Delete(filepath);
- }
- StreamWriter sw = t.CreateText();
- StringBuilder sb = new StringBuilder();
- JsonWriter writer = new JsonWriter(sb);
- writer.WriteObjectStart();
- writer.WritePropertyName("GameObjects");
- writer.WriteArrayStart();
- foreach (UnityEditor.EditorBuildSettingsScene S in UnityEditor.EditorBuildSettings.scenes)
- {
- if (S.enabled)
- {
- string name = S.path;
- EditorApplication.OpenScene(name);
- writer.WriteObjectStart();
- writer.WritePropertyName("scenes");
- writer.WriteArrayStart();
- writer.WriteObjectStart();
- writer.WritePropertyName("name");
- writer.Write(name);
- writer.WritePropertyName("gameObject");
- writer.WriteArrayStart();
- foreach (GameObject obj in Object.FindObjectsOfType(typeof(GameObject)))
- {
- if (obj.transform.parent == null)
- {
- writer.WriteObjectStart();
- writer.WritePropertyName("name");
- writer.Write(obj.name);
- writer.WritePropertyName("asset");
- writer.Write(obj.name + ".prefab");
- writer.WritePropertyName("position");
- writer.WriteObjectStart();
- writer.WritePropertyName("x");
- writer.Write(obj.transform.position.x.ToString("F5"));
- writer.WritePropertyName("y");
- writer.Write(obj.transform.position.y.ToString("F5"));
- writer.WritePropertyName("z");
- writer.Write(obj.transform.position.z.ToString("F5"));
- writer.WriteObjectEnd();
- writer.WritePropertyName("rotation");
- writer.WriteObjectStart();
- writer.WritePropertyName("x");
- writer.Write(obj.transform.rotation.eulerAngles.x.ToString("F5"));
- writer.WritePropertyName("y");
- writer.Write(obj.transform.rotation.eulerAngles.y.ToString("F5"));
- writer.WritePropertyName("z");
- writer.Write(obj.transform.rotation.eulerAngles.z.ToString("F5"));
- writer.WriteObjectEnd();
- writer.WritePropertyName("scale");
- writer.WriteObjectStart();
- writer.WritePropertyName("x");
- writer.Write(obj.transform.localScale.x.ToString("F5"));
- writer.WritePropertyName("y");
- writer.Write(obj.transform.localScale.y.ToString("F5"));
- writer.WritePropertyName("z");
- writer.Write(obj.transform.localScale.z.ToString("F5"));
- writer.WriteObjectEnd();
- writer.WriteObjectEnd();
- }
- }
- writer.WriteArrayEnd();
- writer.WriteObjectEnd();
- writer.WriteArrayEnd();
- writer.WriteObjectEnd();
- }
- }
- writer.WriteArrayEnd();
- writer.WriteObjectEnd();
- sw.WriteLine(sb.ToString());
- sw.Close();
- sw.Dispose();
- AssetDatabase.Refresh();
- }
- }
创建好脚本保存后,你会发现在Unity工具栏上GameObject那栏下拉列表中会出现 ExportXML 和 ExportJSON。保存场景。
然后在工程中创建一个文件夹命名StreamingAssets。然后点击以上两个选项其一。然后就会在该目录中生成对应的记录文件。
我们可以打开这两个文件进行查看,记录的就是工程的所有场景信息和每个场景中物体的Transform信息。
3.加载场景。大概思路:我们就可以通过解析这些文件,然后得到自身场景的Prefab的Transform信息,然后根据asset路径信息去加载prefab然后实例到到场景中,再调整坐标,大小,旋转。即可复原原来的场景。
下面是加载场景的脚本:
- using UnityEngine;
- using System.Collections;
- using System.Xml;
- using System.IO;
- using UnityEditor;
- using LitJson;
- public class LoadScene: MonoBehaviour
- {
- [MenuItem("GameObject/ImprotXML")]
- public static void LoadSenceXML()
- {
- //电脑和iphong上的路径是不一样的,这里用标签判断一下。
- #if UNITY_EDITOR
- string filepath = Application.dataPath + "/StreamingAssets" + "/my.xml";
- #elif UNITY_IPHONE
- string filepath = Application.dataPath +"/Raw"+"/my.xml";
- #endif
- //如果文件存在话开始解析。
- if (File.Exists(filepath))
- {
- XmlDocument xmlDoc = new XmlDocument();
- xmlDoc.Load(filepath);
- XmlNodeList nodeList = xmlDoc.SelectSingleNode("gameObjects").ChildNodes;
- foreach (XmlElement scene in nodeList)
- {
- //因为我的XML是把所有游戏对象全部导出, 所以这里判断一下只解析需要的场景中的游戏对象
- //JSON和它的原理类似
- string path = scene.GetAttribute("name");
- string name = path.Substring(path.LastIndexOf('/')+1,path.LastIndexOf('.')-path.LastIndexOf('/')-1);
- Debug.Log(name);
- if (!name.Equals(Application.loadedLevelName))
- {
- continue;
- }
- foreach (XmlElement gameObjects in scene.ChildNodes)
- {
- string asset = "Assets/Prefab/" + gameObjects.GetAttribute("asset");
- Vector3 pos = Vector3.zero;
- Vector3 rot = Vector3.zero;
- Vector3 sca = Vector3.zero;
- foreach (XmlElement transform in gameObjects.ChildNodes)
- {
- foreach (XmlElement prs in transform.ChildNodes)
- {
- if (prs.Name == "position")
- {
- foreach (XmlElement position in prs.ChildNodes)
- {
- switch (position.Name)
- {
- case "x":
- pos.x = float.Parse(position.InnerText);
- break;
- case "y":
- pos.y = float.Parse(position.InnerText);
- break;
- case "z":
- pos.z = float.Parse(position.InnerText);
- break;
- }
- }
- }
- else if (prs.Name == "rotation")
- {
- foreach (XmlElement rotation in prs.ChildNodes)
- {
- switch (rotation.Name)
- {
- case "x":
- rot.x = float.Parse(rotation.InnerText);
- break;
- case "y":
- rot.y = float.Parse(rotation.InnerText);
- break;
- case "z":
- rot.z = float.Parse(rotation.InnerText);
- break;
- }
- }
- }
- else if (prs.Name == "scale")
- {
- foreach (XmlElement scale in prs.ChildNodes)
- {
- switch (scale.Name)
- {
- case "x":
- sca.x = float.Parse(scale.InnerText);
- break;
- case "y":
- sca.y = float.Parse(scale.InnerText);
- break;
- case "z":
- sca.z = float.Parse(scale.InnerText);
- break;
- }
- }
- }
- }
- //拿到 旋转 缩放 平移 以后克隆新游戏对象
- Debug.Log(asset);
- Object obj = UnityEditor.AssetDatabase.LoadAssetAtPath(asset, typeof(GameObject));
- GameObject ob = (GameObject)Instantiate(obj, pos, Quaternion.Euler(rot));
- ob.transform.localScale = sca;
- ob.name = obj.name;
- }
- }
- }
- }
- }
- [MenuItem("GameObject/ImprotJSON")]
- public static void LoadSenceJSON()
- {
- #if UNITY_EDITOR
- string filepath = Application.dataPath + "/StreamingAssets" + "/json.txt";
- #elif UNITY_IPHONE
- string filepath = Application.dataPath +"/Raw"+"/json.txt";
- #endif
- StreamReader sr = File.OpenText(filepath);
- string strLine = sr.ReadToEnd();
- JsonData jd = JsonMapper.ToObject(strLine);
- JsonData gameObjectArray = jd["GameObjects"];
- int i, j, k;
- for (i = 0; i < gameObjectArray.Count; i++)
- {
- JsonData senseArray = gameObjectArray[i]["scenes"];
- for (j = 0; j < senseArray.Count; j++)
- {
- string path = (string)senseArray[j]["name"];
- string name = path.Substring(path.LastIndexOf('/') + 1, path.LastIndexOf('.') - path.LastIndexOf('/') - 1);
- if (!name.Equals(Application.loadedLevelName))
- {
- continue;
- }
- JsonData gameObjects = senseArray[j]["gameObject"];
- for (k = 0; k < gameObjects.Count; k++)
- {
- string asset = "Assets/Prefab/" + (string)gameObjects[k]["asset"];
- Vector3 pos = Vector3.zero;
- Vector3 rot = Vector3.zero;
- Vector3 sca = Vector3.zero;
- JsonData position = gameObjects[k]["position"];
- JsonData rotation = gameObjects[k]["rotation"];
- JsonData scale = gameObjects[k]["scale"];
- pos.x = float.Parse((string)position["x"]);
- pos.y = float.Parse((string)position["y"]);
- pos.z = float.Parse((string)position["z"]);
- rot.x = float.Parse((string)rotation["x"]);
- rot.y = float.Parse((string)rotation["y"]);
- rot.z = float.Parse((string)rotation["z"]);
- sca.x = float.Parse((string)scale["x"]);
- sca.y = float.Parse((string)scale["y"]);
- sca.z = float.Parse((string)scale["z"]);
- Debug.Log(asset);
- Object obj = UnityEditor.AssetDatabase.LoadAssetAtPath(asset, typeof(GameObject));
- GameObject ob = (GameObject)Instantiate(obj, pos, Quaternion.Euler(rot));
- ob.transform.localScale = sca;
- ob.name = obj.name;
- }
- }
- }
- }
- }
有了以上代码我们就可以对场景的复原了。
首先:我们先把场景里面的物品全部删了。( 注意:要先通过ExportXML or ExportJSON 导出场景记录 )
然后:创建一个名为Init的空物体。挂上一个脚本TestData. 然后只留Start函数然后执行 LoadScene.LoadSenceJSON() 或者 LoadScene.LoadSenceXML();
最后:运行游戏。你会发现所有物体都被实例化出来了。
基本上xml和json的对场景保存和加载的方式如上。最后记录信息文件也不是特别大:
不过你觉得你的工程物品实在有点多,信息文件太大,影响效率的话。可以使用二进制数据格式。主要就是使用BinaryWriter写出二进制流,用BinaryRendeader读入二进制流。这样可以大大压缩信息文件的大小。不过这种方式的坏处是解析的时候。必须要完全了解写入信息的顺序。也就是说如果是xml和json的话只需要知道属性名就可以获取了。而二进制没有这些用于索引的字符,所以只能严格按照字节位数来控制每个变量的所占位置。同时也必须严格按照字节对齐。不然一个变量范围错了,后面的跟着都错了。
Demo工程地址:https://git.oschina.net/Wahh_1314/XmlAndJsonSceneDemo