在处理很多人参与的项目时,很多时候在操作场景时,可能会牵扯到场景修改的冲突问题,这种时候,我们可以将场景以配置文件的形式存储下来(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/二进制的存储方式。

导出Unity场景为配置文件的更多相关文章

  1. 在Unity场景中更改天空盒的步骤

    一.介绍 目的:在Unity场景中制作一个天空盒. 软件环境:Unity 2017.3.0f3,VS2013. 参考 skybox 二.自制一个天空盒 1,创建一个材质material 2,更改属性为 ...

  2. 在Unity场景中控制日夜的轮转

    一.介绍 目的:通过在Unity场景中添加C#脚本完成日夜轮转的效果. 软件环境:Unity 2017.3.0f3,VS2013 二.操作过程 通过拖拽场景中的Directional Light我们知 ...

  3. zookeeper适用场景:配置文件同步

    问题导读:1.本文三个角色之间是什么关系?2.三个角色的作用是什么?3.如何代码实现这三个角色的作用? 在 zookeeper适用场景:zookeeper解决了哪些问题有关于分布式集群配置文件同步问题 ...

  4. Unity场景道具模型拓展自定义编辑器

    (一)适用情况 当游戏主角进入特定的场景或者关卡,每个关卡需要加载不同位置的模型,道具等.这些信息需要先在unity编辑器里面配置好,一般由策划干这事,然后把这些位置道具信息保存在文件,当游戏主角进入 ...

  5. 火炬之光模型导出(Unity载入火炬之光的模型)

    先说明几点.导出方案可行,測试通过. python和blender的版本号一定要用下文中所说的.新的Python或者是新的Blender版本号都无法完美导入. 导入导出脚本能够选择 (http://c ...

  6. Unity场景渲染相关实现的猜想

    如下,很简单的一个场景,一个Panel,二个Cube,一个camera,一个方向光,其中为了避免灯光阴影的影响,关掉阴影,而Panel和二个Cube都是默认的材质,没做修改,我原猜,这三个模型应该都动 ...

  7. Redis命令、数据结构场景、配置文件总结

    本文大纲 一.常用数据类型简介二.redis操作命令三.redis配置文件详解四.redis数据类型使用场景 一.常用数据类型简介 redis常用五种数据类型:string,hash,list,set ...

  8. SecureCRT导出服务器列表或配置文件

    说明:SecureCRT没有Xshell那么简单有直接导出的功能,但是可以通过技巧的方式来操作. 1.打开SecureCRT,点击菜单栏的[Opitions]->[Global Opitions ...

  9. Unity 场景中看不到物体或者OnDrawGizmos画的线看不到

    有时候,Unity中的场景里面,物体突然看不见了,可以这样做:     首先,在 Hierarchy 面板选择看不见的物体,按下快捷键 f.如果物体还是看不见,见下图: 看看图中圈红的地方.如果,如果 ...

随机推荐

  1. python 输出小数控制

    一.要求较小的精度 将精度高的浮点数转换成精度低的浮点数. 1.round()内置方法round()不是简单的四舍五入的处理方式. >>> round(2.5) 2 >> ...

  2. Entity Framework Code First 映射继承关系

    转载 http://www.th7.cn/Program/net/201301/122153.shtml Code First如何处理类之间的继承关系.Entity Framework Code Fi ...

  3. Intel项目Java小记

    cannot be cast to javax.servlet.Filter添加provided即可 install -X是什么意思? Unsupported major.minor version ...

  4. spring mvc 配置

    之前配置spring mvc 怎么都访不到对应的jsp,后来把prefix里面的jsp改为views,就能访问到了,然后再改回jsp也可以访问到 搞了两天,都崩溃了,不管怎样先把没问题的例子给记录下来 ...

  5. Metasploit渗透测试魔鬼训练营

    首本中文原创Metasploit渗透测试著作,国内信息安全领域布道者和资深Metasploit渗透测试专家领衔撰写,极具权威性.以实践为导向,既详细讲解了Metasploit渗透测试的技术.流程.方法 ...

  6. Visual Studio快捷键小结

    工欲善其事必先利其器,这句话相信大家都听说过.利其器,就是我们先得有个神器,神器就是VS(号称宇宙第一IDE),有了神奇不会用也是白搭,就像你有了倚天剑和屠龙刀你不会使,它也就是废铁(假设它们是铁做的 ...

  7. UITableView.m:8042 crash 崩溃

     CRASH : /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3600.6.21/UITableView.m:804 ...

  8. 【网络流24题】No.11(航空路线问题 最长不相交路径 最大费用流)

    [题意] 给定一张航空图, 图中顶点代表城市, 边代表 2 城市间的直通航线. 现要求找出一条满足下述限制条件的且途经城市最多的旅行路线.(1) 从最西端城市出发,单向从西向东途经若干城市到达最东端城 ...

  9. 可恶的QT隐式共享

    这个问题隐藏的很深,一般不容易察觉它造成的问题,而只是享受它提供的好处(节省内存,而且速度更快). 但我发现它现在至少造成两个问题: 1. 把大量的QString放到QMap里,使用完毕后清空QMap ...

  10. android Button 颜色的变化(点击,放开,点击不放)

    参考: http://endual.iteye.com/blog/1534258 总结: 定义res/drawable/button_style.xml文件 <?xml version=&quo ...