我将一些 Unity 场景对象保存到 XML 文件中,以便随时加载此状态并继续游戏。保存到文件没有问题,但读取很糟糕 - 代码将 XML 读取到结构中的第一个结束对象,然后完成读取,尽管这还不到整个文件的 1%。这是一个没有组件等的示例文件:
<?xml version="1.0"?>
<ArrayOfSerializableObject xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SerializableObject>
<Name>1</Name>
<Tag></Tag>
<Position></Position>
<Rotation></Rotation>
<Scale></Scale>
<Children>
<SerializableObject>
<Name>2</Name>
<Tag></Tag>
<Position></Position>
<Rotation></Rotation>
<Scale></Scale>
<Children>
<SerializableObject>
<Name>3</Name>
<Tag></Tag>
<Position></Position>
<Rotation></Rotation>
<Scale></Scale>
<Children>
<SerializableObject>
<Name>4</Name>
<Tag></Tag>
<Position></Position>
<Rotation></Rotation>
<Scale></Scale>
<Children />
<Components></Components>
</Children>
<Components></Components>
</SerializableObject>
<SerializableObject>
<Name>5</Name>
<Tag></Tag>
<Position></Position>
<Rotation></Rotation>
<Scale></Scale>
<Children />
<Components></Components>
</SerializableObject>
</Children>
<Components></Components>
</SerializableObject>
</Children>
<Components />
</SerializableObject>
</ArrayOfSerializableObject>
结果,我将收到以下结构中的对象:
-1
--2
---3
----4
5 及以后将不再被读取,因为代码应该到达文件末尾。
事实上,这里是保存和读取代码本身(或者更正确地说,是它的一个片段):
using System.Collections.Generic;
using System;
using System.IO;
using System.Xml.Serialization;
using UnityEngine;
using System.Collections;
[Serializable]
public class SerializableObject
{
public string Name;
public string Tag;
public Vector3 Position;
public Quaternion Rotation;
public Vector3 Scale;
public List<SerializableObject> Children = new List<SerializableObject>();
public List<SerializableComponent> Components = new List<SerializableComponent>();
}
[Serializable]
public class SerializableComponent
{
public string TypeName;
public SerializableDictionary Properties = new SerializableDictionary();
}
[Serializable]
public class SerializableDictionary : IXmlSerializable
{
public Dictionary<string, string> Dictionary = new Dictionary<string, string>();
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
reader.Read();
while(reader.NodeType != System.Xml.XmlNodeType.EndElement)
{
string key = reader.GetAttribute("Key");
string value = reader.GetAttribute("Value");
Dictionary.Add(key, value);
reader.Read();
}
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
foreach(var kvp in Dictionary)
{
writer.WriteStartElement("Item");
writer.WriteAttributeString("Key", kvp.Key);
writer.WriteAttributeString("Value", kvp.Value);
writer.WriteEndElement();
}
}
}
public class SaveLoadManager : MonoBehaviour
{
public Transform rootObject;
public string path, sceneName;
private List<SerializableObject> loadedData;
void Awake()
{
#if UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX
path = System.IO.Path.GetFullPath(System.IO.Path.Combine(Application.dataPath, "..", "..", "Files", "Saves", sceneName, "savefile.xml"));
#else
string documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);
path = System.IO.Path.GetFullPath(System.IO.Path.Combine(documentsPath, "MTS", "Files", "Saves", sceneName, "savefile.xml"));
#endif
}
// Сохранение
public void SaveToXML()
{
List<SerializableObject> data = new List<SerializableObject>();
foreach(Transform child in rootObject)
{
data.Add(SaveObject(child));
}
XmlSerializer serializer = new XmlSerializer(typeof(List<SerializableObject>));
using(FileStream stream = new FileStream(path, FileMode.Create))
{
serializer.Serialize(stream, data);
}
}
private SerializableObject SaveObject(Transform obj)
{
SerializableObject serializableObject = new SerializableObject
{
Name = obj.name,
Tag = obj.tag,
Position = obj.localPosition,
Rotation = obj.localRotation,
Scale = obj.localScale
};
foreach(Component component in obj.GetComponents<Component>())
{
if(component.GetType() != typeof(Transform))
{
serializableObject.Components.Add(SaveComponent(component));
}
}
foreach(Transform child in obj)
{
serializableObject.Children.Add(SaveObject(child));
}
return serializableObject;
}
private SerializableComponent SaveComponent(Component component)
{
SerializableComponent serializableComponent = new SerializableComponent
{
TypeName = component.GetType().AssemblyQualifiedName
};
foreach(var field in component.GetType().GetFields())
{
if(field.IsPublic)
{
serializableComponent.Properties.Dictionary[field.Name] = field.GetValue(component)?.ToString();
}
}
return serializableComponent;
}
// Загрузка
public void LoadFromXML()
{
XmlSerializer serializer = new XmlSerializer(typeof(List<SerializableObject>));
using(FileStream stream = new FileStream(path, FileMode.Open))
{
loadedData = (List<SerializableObject>)serializer.Deserialize(stream);
}
foreach(Transform child in rootObject)
{
Destroy(child.gameObject);
}
foreach(var data in loadedData)
{
LoadObject(data, rootObject);
}
}
private void LoadObject(SerializableObject data, Transform parent)
{
GameObject obj = new GameObject(data.Name);
obj.tag = data.Tag;
obj.transform.SetParent(parent);
obj.transform.localPosition = data.Position;
obj.transform.localRotation = data.Rotation;
obj.transform.localScale = data.Scale;
foreach(var childData in data.Children)
{
LoadObject(childData, obj.transform);
}
}
}
检查下载进度。
UPD:我检查了.xml 文件
完整内容的嵌套结构
输出:
旧的工作方式是,它保存指定对象的所有子对象以及所有组件、组件值等,这就是为什么保存的文件平均大小约为 25-30 MB,这对于XML(虽然这不由我来判断)。但是,由于游戏中所有可保存的对象都已经有自己的预制件,此外,玩家从预制件中生成所有这些对象,因此我决定仅保存基本参数,例如对象的名称、位置、路线等。
为了加载,我决定使用这些对象的完全相同的生成器:首先,我将XML中的所有对象加载到一个单独的对象中
List<>,然后从该列表中调用检查以查看对象名称是否与生成器中的内容匹配,如果一切正确,该对象将在指定的对象(保存该对象的对象)内生成,并根据所需的路线和其他详细信息转移到所需的位置。这是最终的代码:
结果,平均XML保存文件如下所示,大小为几千字节(这就是我所理解的优化):
尽管我无法让原始代码运行,但无论如何还是感谢所有试图提供帮助的人!