游戏项目中会有很多配置表,配置格式多种多样,有xml、json、yml等等
以xml解析为例,可能你会看到别人这样的写法(或者你也在用这样的方法挨个字段解析)
locationCamera.Name = node.Attributes["Name"].Value.ToString();
locationCamera.Roll = System.Convert.ToDouble(node.Attributes["roll"].Value.ToString());
locationCamera.Tilt = System.Convert.ToDouble(node.Attributes["tilt"].Value.ToString());
locationCamera.Heading = System.Convert.ToDouble(node.Attributes["heading"].Value.ToString());
locationCamera.X = System.Convert.ToDouble(node.Attributes["x"].Value.ToString());
locationCamera.Y = System.Convert.ToDouble(node.Attributes["y"].Value.ToString());
locationCamera.Z = System.Convert.ToDouble(node.Attributes["z"].Value.ToString());
看起来真是又臭又长,一点也不简洁,我们要解放双手
本文教你一种自动解析xml字段的方法
文件结构如下
audioConfig.xml配置如下
<items>
<item id="0" name="login.ogg" volume="1"/>
<item id="1" name="game.ogg" volume="1" />
items>
封装自动解析逻辑
ConfigItem.cs
ConfigFile.cs
ObjectParser.cs
原理就是通过反射来自动解析(ObjectParser类),具体代码见文章末尾
现在要解析audioConfig.xml
,我们需要先定义一个继承ConfigItem的类,因为是通过反射来自动解析,所以字段名字需要与xml的字段名字相同
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Text;
// 继承ConfigItem,字段名字需要与xml的字段名字相同
public class AudioConfigItem : ConfigItem
{
public int id;
public string name;
public string volume;
public override string GetKey()
{
return id.ToString();
}
public override string ToString()
{
string str = string.Format("id: {0}, name: {1}, volume: {2}", id, name, volume);
return str;
}
}
public class AudioConfig
{
// 加载配置
public void LoadCfg()
{
m_cfgFile = new ConfigFile<AudioConfigItem>(Application.streamingAssetsPath + "/config/audioConfig.bytes");
}
// 打印配置文件
public void PrintCfg()
{
var items = m_cfgFile.GetAllItems();
foreach(var cfgItem in items.Values)
{
Debug.Log(cfgItem.ToString());
}
}
private ConfigFile<AudioConfigItem> m_cfgFile;
// 单例
private static AudioConfig s_instance;
public static AudioConfig Instance
{
get
{
if (null == s_instance)
s_instance = new AudioConfig();
return s_instance;
}
}
}
测试
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using System.IO;
public class Test : MonoBehaviour
{
void Start()
{
AudioConfig.Instance.LoadCfg();
AudioConfig.Instance.PrintCfg();
}
}
如果你把配置表打成了AssetBundle,则需要从AssetBundle读取获得XmlDocument对象
using System;
using System.Xml;
using UnityEngine;
using System.IO;
public class XmlUtils
{
public static XmlDocment GetXmlDocFromAssetBundle(AssetBundle ab, string xmlName)
{
TextAsset textAsset = ab.LoadAsset<TextAsset>(xmlName);
if(null == textAsset)
{
Debug.LogError("null == textAsset, xmlName: " + xmlName);
return null;
}
XmlDocument doc = new XmlDocument();
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreComments = true;
settings.IgnoreWhitespace = true;
TextReader textReader = new StringReader(textAsset.text);
XmlReader xmlReader = XmlReader.Create(textReader, settings);
doc.Load(xmlReader);
return doc;
}
}
// ConfigItem.cs
using System.Xml;
public interface IConfigItem
{
string GetKey();
bool Parse(XmlNode node);
}
public abstract class ConfigItem
{
public const string KeySpan = "_";
public abstract string GetKey();
public virtual void OnItemParsed()
{
}
public virtual bool Parse(XmlNode node)
{
object self = this;
var result = ObjectParser.Parse(node, ref self, this.GetType());
OnItemParsed();
return result == ObjectParser.Result.OK;
}
}
// ConfigFile.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Xml;
using System;
public class ConfigFile<T> where T : ConfigItem
{
protected Dictionary<string, T> m_items = new Dictionary<string, T>();
public ConfigFile(string filePath)
{
// 如果是把xml打成AssetBundle,则是从AssetBundle中读取,这里就要改造一下////////
XmlDocument doc = new XmlDocument();
doc.Load(filePath);
////////////////////////////////////////////////////////////////////////////
if (!InitFile(doc))
{
Debug.LogError("Init Config File Error filePath = " + filePath);
}
}
public bool InitFile(XmlDocument doc)
{
XmlNodeList nodeList = doc.GetElementsByTagName("item");
string errorStr = "";
int count = nodeList.Count;
for (int i = 0; i < count; i++)
{
XmlNode node = nodeList[i];
T obj = (T)Activator.CreateInstance(typeof(T));
if (!obj.Parse(node))
{
errorStr += ObjectParser.lastError;
}
var key = obj.GetKey();
if (!m_items.ContainsKey(key))
m_items.Add(key, obj);
else
Debug.LogError(string.Format("cfg has contains the same key,key:{0}", key));
}
if (!string.IsNullOrEmpty(errorStr))
{
Debug.LogError("InitFile error " + errorStr);
return false;
}
else if (count == 0)
{
string baseUrl = doc.BaseURI;
int index = baseUrl.LastIndexOf('/');
baseUrl = baseUrl.Substring(index + 1);
Debug.LogError("Cannot find Elements item, file name = " + baseUrl);
return false;
}
else
{
return true;
}
}
public T GetItem(string key)
{
T item = null;
m_items.TryGetValue(key, out item);
return item;
}
public Dictionary<string, T> GetAllItems()
{
return m_items;
}
}
// ObjectParser.cs
using System.Collections.Generic;
using System.Reflection;
using System.Xml;
using System;
public static class ObjectParser
{
public enum Result
{
OK,
FieldNotExist,
InvalidEnum,
FormatError
}
public static string lastError { get; private set; }
private delegate object StringConverter(string raw);
private static Dictionary<Type, StringConverter> _predefinedConverters;
private delegate TOutput Converter<TInput, TOutput>(TInput input);
static StringConverter MakeConverter<T>(Converter<string, T> converter)
{
return s =>
{
if (string.IsNullOrEmpty(s))
return default(T);
return converter(s);
};
}
public static T GetConvert<T>(int index, params object[] objArr)
{
if (objArr.Length <= index) return default(T);
return GetConvert<T>(objArr[index]);
}
public static T GetConvert<T>(object obj)
{
if (obj == null)
return default(T);
StringConverter del = null;
_predefinedConverters.TryGetValue(typeof(T), out del);
return (T)del(obj.ToString());
}
static ObjectParser()
{
_predefinedConverters = new Dictionary<Type, StringConverter>();
_predefinedConverters[typeof(long)] = MakeConverter<long>(Convert.ToInt64);
_predefinedConverters[typeof(ulong)] = MakeConverter<ulong>(Convert.ToUInt64);
_predefinedConverters[typeof(int)] = MakeConverter<int>(Convert.ToInt32);
_predefinedConverters[typeof(uint)] = MakeConverter<uint>(Convert.ToUInt32);
_predefinedConverters[typeof(short)] = MakeConverter<short>(Convert.ToInt16);
_predefinedConverters[typeof(ushort)] = MakeConverter<ushort>(Convert.ToUInt16);
_predefinedConverters[typeof(byte)] = MakeConverter<byte>(Convert.ToByte);
_predefinedConverters[typeof(sbyte)] = MakeConverter<sbyte>(Convert.ToSByte);
_predefinedConverters[typeof(float)] = MakeConverter<float>(Convert.ToSingle);
_predefinedConverters[typeof(double)] = MakeConverter<double>(Convert.ToDouble);
_predefinedConverters[typeof(bool)] = str => "1" == str;
_predefinedConverters[typeof(string)] = s => s ?? "";
}
public static Result Parse(XmlNode node, ref object obj, Type type)
{
foreach (XmlNode attrNode in node.Attributes)
{
string fieldName = attrNode.Name;
string fieldValueStr = attrNode.Value;
var fieldInfo = type.GetField(fieldName);
if (fieldInfo == null)
continue;
var fieldType = fieldInfo.FieldType;
try
{
StringConverter converter;
if (_predefinedConverters.TryGetValue(fieldType, out converter))
fieldInfo.SetValue(obj, converter(fieldValueStr));
else
{
if (fieldType.IsSubclassOf(typeof(Enum)) && !string.IsNullOrEmpty(fieldValueStr))
{
int ivalue = 0;
if (Enum.IsDefined(fieldType, fieldValueStr))
{
fieldInfo.SetValue(obj, Enum.Parse(fieldType, fieldValueStr, true));
}
else if (int.TryParse(fieldValueStr, out ivalue) && Enum.IsDefined(fieldType, ivalue))
{
fieldInfo.SetValue(obj, Enum.ToObject(fieldType, ivalue));
}
else
{
//lastError = string.Format("invalid enum {0} for field {1}", fieldValueStr, fieldName);
//return Result.InvalidEnum;
}
}
}
}
catch (FormatException e)
{
lastError = string.Format("format error for field {0}, value is {1}, error message = {2}", fieldName, fieldValueStr, e.Message);
return Result.FormatError;
}
}
return Result.OK;
}
public static Result Parse(Dictionary<string, string> properties, ref object obj, Type type)
{
foreach (var kvp in properties)
{
string fieldName = kvp.Key;
string fieldValueStr = kvp.Value;
var fieldInfo = type.GetField(fieldName);
if (fieldInfo == null)
continue;
var fieldType = fieldInfo.FieldType;
try
{
StringConverter converter;
if (_predefinedConverters.TryGetValue(fieldType, out converter))
fieldInfo.SetValue(obj, converter(fieldValueStr));
else
{
if (fieldType.IsSubclassOf(typeof(Enum)) && !string.IsNullOrEmpty(fieldValueStr))
{
if (Enum.IsDefined(fieldType, fieldValueStr))
fieldInfo.SetValue(obj, Enum.Parse(fieldType, fieldValueStr, true));
else
{
//lastError = string.Format("invalid enum {0} for field {1}", fieldValueStr, fieldName);
//return Result.InvalidEnum;
}
}
}
}
catch (FormatException e)
{
lastError = string.Format("format error for field {0}, value is {1}, error message = {2}", fieldName, fieldValueStr, e.Message);
return Result.FormatError;
}
}
return Result.OK;
}
public static void ParsefieldInfo(FieldInfo fieldInfo, object obj, string fieldValueStr)
{
var fieldType = fieldInfo.FieldType;
StringConverter converter;
if (_predefinedConverters.TryGetValue(fieldType, out converter))
{
fieldInfo.SetValue(obj, converter(fieldValueStr));
}
else
{
if (fieldType.IsSubclassOf(typeof(Enum)) && !string.IsNullOrEmpty(fieldValueStr))
{
int ivalue = 0;
if (Enum.IsDefined(fieldType, fieldValueStr))
{
fieldInfo.SetValue(obj, Enum.Parse(fieldType, fieldValueStr, true));
}
else if (int.TryParse(fieldValueStr, out ivalue) && Enum.IsDefined(fieldType, ivalue))
{
fieldInfo.SetValue(obj, Enum.ToObject(fieldType, ivalue));
}
else
{
//lastError = string.Format("invalid enum {0} for field {1}", fieldValueStr, fieldName);
//return Result.InvalidEnum;
}
}
}
}
}