导出Unity场景为配置文件

在处理很多人参与的项目时,很多时候在操作场景时,可能会牵扯到场景修改的冲突问题,这种时候,我们可以将场景以配置文件的形式存储下来(cocos的场景、android的view保存思想),可以采用json/XML/二进制等多种方式进行存储,这时,需要我们将Scenes中的所有GameObject以Prefab的形式存储,并且考虑到Prefab上的组件引用问题,就需要将原来直接拖入持有的方式获取的游戏对象,以Find/FindTag这类方法来初始化(可能面临的一个问题是:原来持有的是List等类型?这个问题暂时还没有想到很好的解决方式)。

在存储时,主要使用了XML的方式来进行存储,对于每一个GameObject(Prefab),只需要存储其LocalPosition,LocalRotation,LocalScale,ParentPath等信息。主要的思想和设计主要就是上面提到的相关内容。下面直接上程序:

using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.IO;

//http://www.xuanyusong.com/archives/1919
public class ExportScene : Editor
{
//将所有游戏场景导出为XML格式
[MenuItem("GameObject/ExportScene2XML")]
static void ExportScene2XML()
{
string tFilePath = Application.dataPath + @"/Resources/XML/Scenes.xml";
if (!File.Exists(tFilePath))
{
File.Delete(tFilePath);
}
XmlDocument xmlDoc = new XmlDocument();
XmlElement root = xmlDoc.CreateElement("AllGameObjects");

IList<int> exportedGameObjectUniqueIDLst = new List<int>(); //加载过的prefab ID信息,防重复

//遍历所有的游戏场景
foreach (UnityEditor.EditorBuildSettingsScene S in UnityEditor.EditorBuildSettings.scenes)
{
if (S.enabled)
{
string tSceneName = S.path;
EditorApplication.OpenScene(tSceneName);
XmlElement tScenesRoot = xmlDoc.CreateElement("Scenes");
tScenesRoot.SetAttribute("SceneName", tSceneName);
List<GameObject> tLst = new List<GameObject>();
tLst.Adds((GameObject[])Object.FindObjectsOfType(typeof(GameObject)));
for(int i = tLst.Count - 1; i >= 0; i--)
{
GameObject obj = tLst[i]; //此处逻辑还未来得及优化
if (obj == null)
{
continue;
}
GameObject tPrefab = PrefabUtility.FindRootGameObjectWithSameParentPrefab(obj); //当前物体对应的Prefab整体
int tUniqueID = tPrefab.GetInstanceID();
if (exportedGameObjectUniqueIDLst.Contains(tUniqueID))
{
continue;
}
exportedGameObjectUniqueIDLst.Add(tUniqueID);

if (PrefabUtility.GetPrefabType(tPrefab) == PrefabType.None)
{
Debug.LogError("NonePrefab = " + tPrefab); //主动生成为Prefab(整体作为一个Prefab————>提示手动处理(存在Prefab和非Prefab的父子层次问题!!))
//PrefabUtility.CreatePrefab(@"Assets/Resources/ScenePrefabs/" + tPrefab.name + @".prefab", tPrefab);
}

Stack<GameObject> tStack = getParentPrefabQueue(tPrefab);
GameObject tObj = null;
if (tStack.Count > 0)
{
tObj = tStack.Pop();
}
XmlElement tParent = tScenesRoot;
while (tObj != null)
{
//先查找该节点是否存在
XmlElement tElement = getXmlElementByID(xmlDoc, tObj.GetInstanceID().ToString());
if (null == tElement)
{
tParent = generateNode(xmlDoc, ref tParent, tObj);
}
else
{
tParent = tElement;
}
if (tStack.Count > 0)
{
tObj = tStack.Pop();
}
else
{
tObj = null;
}
}
//XmlElement tChild = generateNode(xmlDoc, ref tScenesRoot, tPrefab);
root.AppendChild(tScenesRoot);
xmlDoc.AppendChild(root);
xmlDoc.Save(tFilePath);
}
}
}
//刷新Project视图
AssetDatabase.Refresh();
}

//子对象的xml结点放在父对象下面
private static XmlElement generateNode(XmlDocument _xmlDoc, ref XmlElement _parent, GameObject _prefab)
{
XmlElement tGameObject = _xmlDoc.CreateElement("GameObject");
tGameObject.SetAttribute("ID", _prefab.GetInstanceID().ToString());
tGameObject.SetAttribute("Name", _prefab.name);
//tGameObject.SetAttribute("Asset", _prefab.name + ".prefab");
string tRootPath = getRootPath(_prefab);
if (!tRootPath.Equals(""))
{
tGameObject.SetAttribute("RootPath", tRootPath);
}

#if true
Vector3 tPos = _prefab.transform.localPosition;
if (!Mathf.Approximately(tPos.x, 0)) //位置为0作为默认值,不必存储
{
tGameObject.SetAttribute("Px", tPos.x.ToString("0.###"));
}
if (!Mathf.Approximately(tPos.y, 0))
{
tGameObject.SetAttribute("Py", tPos.y.ToString("0.###"));
}
if (!Mathf.Approximately(tPos.z, 0))
{
tGameObject.SetAttribute("Pz", tPos.z.ToString("0.###"));
}
#endif

#if true
Vector3 tAngle = _prefab.transform.localRotation.eulerAngles;
if (!Mathf.Approximately(tAngle.x, 0)) //角度为0作为默认值,不必存储
{
tGameObject.SetAttribute("Rx", tAngle.x.ToString("0.###"));
}
if (!Mathf.Approximately(tAngle.y, 0))
{
tGameObject.SetAttribute("Ry", tAngle.y.ToString("0.###"));
}
if (!Mathf.Approximately(tAngle.z, 0))
{
tGameObject.SetAttribute("Rz", tAngle.z.ToString("0.###"));
}
#endif

#if true
Vector3 tScale = _prefab.transform.localScale;
if (!Mathf.Approximately(tScale.x, 1)) //Scale为1作为默认值,不必存储
{
tGameObject.SetAttribute("Sx", tScale.x.ToString("0.###"));
}
if (!Mathf.Approximately(tScale.y, 1))
{
tGameObject.SetAttribute("Sy", tScale.y.ToString("0.###"));
}
if (!Mathf.Approximately(tScale.z, 1))
{
tGameObject.SetAttribute("Sz", tScale.z.ToString("0.###"));
}
#endif
_parent.AppendChild(tGameObject);
return tGameObject;
}

//根据ID获取结点
private static XmlElement getXmlElementByID(XmlDocument _xmlDoc, string _id)
{
XmlNode tRoot = _xmlDoc.SelectSingleNode("AllGameObjects");
if (null != tRoot)
{
XmlNodeList nodeList = tRoot.ChildNodes;
XmlNode scene = null;
for (int i = 0; i < nodeList.Count; i++)
{
scene = nodeList[i];
XmlNode gameObject = null;
for (int j = 0; j < scene.ChildNodes.Count; j++)
{
gameObject = scene.ChildNodes[j];
XmlElement tRet = getXmlElementByID(_xmlDoc, gameObject, _id);
if (tRet != null)
{
return tRet;
}
}
}
}
return null;
}

private static XmlElement getXmlElementByID(XmlDocument _xmlDoc, XmlNode _gameObject, string _id)
{
XmlElement tRet = null;
if (((XmlElement)_gameObject).GetAttribute("ID").Equals(_id)) //判断当前结点
{
tRet = (XmlElement)_gameObject;
}
else if (_gameObject.ChildNodes.Count > 0)     //递归查找子节点
{
XmlNode gameObject = null;
for (int i = 0; i < _gameObject.ChildNodes.Count; i++)
{
gameObject = _gameObject.ChildNodes[i];
tRet = getXmlElementByID(_xmlDoc, gameObject, _id);
if (tRet != null)
{
break;
}
}
}
return tRet;
}

private static string getRootPath(GameObject _obj)
{
string tResult = "";
Transform tParent = _obj.transform.parent;
while (tParent != null)
{
if (tResult.Equals(""))
{
tResult = tParent.name;
}
else
{
tResult = tParent.name + "/" + tResult;
}
tParent = tParent.parent;
}
return tResult;
}

//先根据ID查找,结点是否存在
private static Stack<GameObject> getParentPrefabQueue(GameObject _obj)
{
IList<int> tUniqueIDLst = new List<int>();
Stack<GameObject> tStack = new Stack<GameObject>();
tStack.Push(_obj);
tUniqueIDLst.Add(_obj.GetInstanceID());
Transform tParent = _obj.transform.parent;
GameObject tPrefab = null;
while (tParent != null)
{
tPrefab = PrefabUtility.FindRootGameObjectWithSameParentPrefab(tParent.gameObject);
int tID = tPrefab.GetInstanceID();
if (!tUniqueIDLst.Contains(tID))
{
tStack.Push(tPrefab);
tUniqueIDLst.Add(tID);
}
tParent = tParent.parent;
}
return tStack;
}

}

根据生成的XML配置文件和Resources下的Prefab信息,生成场景的逻辑如下:

public class GenerateSceneFromXml
{
#region
private static readonly object lockHelper = new object();
private GenerateSceneFromXml()
{
}
private static GenerateSceneFromXml instance = null;
public static GenerateSceneFromXml Instance
{
private set
{
}
get
{
if (null == instance)
{
lock (lockHelper)
{
if (null == instance)
{
instance = new GenerateSceneFromXml();
}
}
}
return instance;
}
}
#endregion singleton

public void Init()
{
TextAsset tAsset = Resources.Load<TextAsset>("XML/Scenes");
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(tAsset.text);

XmlNodeList nodeList = xmlDoc.SelectSingleNode("AllGameObjects").ChildNodes;
XmlNode scene = null;
for(int i = 0; i < nodeList.Count; i++)
{
scene = nodeList[i];
XmlNode gameObject = null;
for (int j = 0; j < scene.ChildNodes.Count; j++) //遍历场景下的所有游戏对象
{
gameObject = scene.ChildNodes[j];
generateRecursivily(gameObject);
}
}
Resources.UnloadAsset(tAsset);
}

//生成自身,并依次递归生成子物体
private GameObject generateRecursivily(XmlNode _node, Transform _parent = null)
{
Vector3 tPos = Vector3.zero;
Vector3 tRot = Vector3.zero;
Vector3 tScale = Vector3.one;

XmlElement tElem = ((XmlElement)_node);
tPos.x = float.Parse(tElem.GetAttributeValue("Px", "0"));
tPos.y = float.Parse(tElem.GetAttributeValue("Py", "0"));
tPos.z = float.Parse(tElem.GetAttributeValue("Pz", "0"));

tRot.x = float.Parse(tElem.GetAttributeValue("Rx", "0"));
tRot.y = float.Parse(tElem.GetAttributeValue("Ry", "0"));
tRot.z = float.Parse(tElem.GetAttributeValue("Rz", "0"));

tScale.x = float.Parse(tElem.GetAttributeValue("Sx", "1"));
tScale.y = float.Parse(tElem.GetAttributeValue("Sy", "1"));
tScale.z = float.Parse(tElem.GetAttributeValue("Sz", "1"));

string tAsset = "ScenePrefabs/" + tElem.GetAttribute("Name");
GameObject tObj = (GameObject)GameObject.Instantiate(Resources.Load(tAsset));
//tObj.transform.parent = _parent;
if (tElem.GetAttributeValue("RootPath", "").Equals(""))
{
tObj.transform.parent = null;
}
else
{
tObj.transform.parent = GameObject.Find(tElem.GetAttribute("RootPath")).transform;
}
tObj.transform.localPosition = tPos;
tObj.transform.localEulerAngles = tRot;
tObj.transform.localScale = tScale;
tObj.name = tElem.GetAttribute("Name"); //去掉"(Clone)"

if (_node.HasChildNodes)
{
for(int i = _node.ChildNodes.Count - 1; i >= 0; i--)
{
generateRecursivily(_node.ChildNodes[i], tObj.transform); 
}
}
return tObj;
}
}

当然,XML的存储效率没那么高,可以调整为json/二进制的存储方式。

上一篇:Ajax中的get和post两种请求方式的异同


下一篇:Identity Server 4 从入门到落地(四)—— 创建Web Api