Unity自动解析xml文件(通过反射,不用手动解析字段)

游戏项目中会有很多配置表,配置格式多种多样,有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字段的方法
文件结构如下
Unity自动解析xml文件(通过反射,不用手动解析字段)_第1张图片
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();
    }
}

输出结果
Unity自动解析xml文件(通过反射,不用手动解析字段)_第2张图片


补充

如果你把配置表打成了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;
                }
            }
        }
    }
}

你可能感兴趣的:(unity3D)