导出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/二进制的存储方式。

时间: 2024-10-07 04:58:09

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

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

一.介绍 目的:在Unity场景中制作一个天空盒. 软件环境:Unity 2017.3.0f3,VS2013. 参考 skybox 二.自制一个天空盒 1,创建一个材质material 2,更改属性为Skybox/6 Sided,并且把六个面的图片都选好 三.修改天空盒 在菜单栏Window属性下,选中Lighting -> settings,会出现下面对话框 修改后效果如下 原文地址:https://www.cnblogs.com/OctoptusLian/p/8932428.html

unity场景导出

一.场景物件遍历所有物件旋转.位移,缩放     在重新摆放物件时,如果有光照贴图,需要恢复贴图的索引信息.      二.场景烘焙后会生成贴图和对应的LightmapSnapshot.asset文件     LightmapSnapshot.asset 只在 editor 模式下有效,是不能导出 assetbundle 的.(如果需要恢复光照和无效,也没必要保存这个) 光照信息和雾效的信息,需要保存到额外文件中. 三.不建议使用 Unity 自带的 Terrain,在将 TerrainData

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

问题导读:1.本文三个角色之间是什么关系?2.三个角色的作用是什么?3.如何代码实现这三个角色的作用? 在 zookeeper适用场景:zookeeper解决了哪些问题有关于分布式集群配置文件同步问题的描述,本文介绍如何把zk应用到配置文件分发的场景.假设有三个角色 trigger:发布最新的配置文件数据,发送指令和数据给zk_agent,实现是下面的trigger.py zk_agent:接收来自trigger.py触发的指令和数据,并且把数据更新到zk service上,从而触发zk_app

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

有时候,Unity中的场景里面,物体突然看不见了,可以这样做: 首先,在 Hierarchy 面板选择看不见的物体,按下快捷键 f.如果物体还是看不见,见下图: 看看图中圈红的地方.如果,如果物体要看得见,需要图标是 "眼睛" 的图案.鼠标点击一下,如果图标已经是 "眼睛" 的图案,场景中还是看不见物体,那就要看看是不是 layer 设置的问题了. OnDrawGizmos() 画的线看不到,可能也是 右上角的 Layer 设置问题: 让 Layer 设置为 Def

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

(一)适用情况 当游戏主角进入特定的场景或者关卡,每个关卡需要加载不同位置的模型,道具等.这些信息需要先在unity编辑器里面配置好,一般由策划干这事,然后把这些位置道具信息保存在文件,当游戏主角进入后根据保存的文件信息加载模型道具.如 跑酷场景的金币 赛车赛道的道具 (二)实例文件格式 Json 需要导入SimpleJson 具体使用方法可以看我另外一篇<Unity游戏数据用Json保存>,有详细介绍 http://www.cnblogs.com/July7th/p/4808095.html

Unity场景打AssetBundle包,加载后天空盒材质丢失问题及解决

环境:win10, Unity2018, vs2015 最近在研究把场景打成AB包进行加载但是发现之前设置的天空盒,这样加载场景后就一片粉了 其实就是材质shader丢失导致天空变粉如上图,如果是直接在Unity里运行这个场景当然是没问题的 那么要解决这个问题,需要几个步骤,还要借助代码(注:这只是我个人的解决办法) Graphics设定Unity菜单:Edit->Prject Settings->Graphics注意在Always Included Shaders选项里把这个Skybox用的

Unity场景、模型等资源转UE4

   共同点: 在世界空间和模型空间内,UE4.Unity均为左手系 不同点: 轴向 模型空间内,UE4.Unity轴向的对应关系如下: 轴向 UE4 Unity 向前 +x +z 向右 +y +x 向上 +z +y 旋转 欧拉角(Euler Angles)使用三个角度值来描述物体在三维空间的任意朝向 它的基本思想是让物体开始于“标准”方位(物体坐标轴和惯性坐标轴对齐),每次让其绕着物体坐标系某个轴进行旋转,通过三次旋转就可以达到最终朝向 如果从惯性坐标系到物体坐标系,欧拉角顺序为:m-n-p:

unity 场景加载和对象消失的几种方法

场景加载: Application.LoadLevel(Application.loadedLevel); // 重新调用场景 Application.LoadLevel(0); // 调用你在设置里摆放的场景,按你的摆放顺序调 场景加载后保留当前指定的对象,留到下一个场景: DontDestroyOnLoad (transform.gameObject); 结束游戏: Application.Quit() ; gameObject.renderer.enabled //是控制一个物体是否在屏幕

unity 场景贴图闪烁

问题:unity模型在scene界面显示正常,但在game界面显示确会闪烁 贴图闪烁因为有两个面距离太近,重合导致,在渲染的时候闪烁, 解决: 1.将两个贴图的面拉远(不过这有点不现实): 2.将两个贴图面合为一个(这交给美术就可以): 3.修改摄像机 clipping planes 的near的值调的稍微大一点到合适的值即可.