少年修仙传客户端代码仓库
client_Wu Xijin
2019-02-13 4dc88e1540d2fea7635eb2c376da8ec6c141e4be
3335 配置表读取重构。
4个文件已修改
708 ■■■■ 已修改文件
Core/GameEngine/Model/Config/NPCConfig.cs 302 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Core/GameEngine/Model/ConfigBase.cs 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Core/GameEngine/Model/ConfigManager.cs 370 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Fight/GameActor/PartialModelResConfig.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Core/GameEngine/Model/Config/NPCConfig.cs
@@ -13,66 +13,66 @@
{
    public readonly int NPCID;
    public readonly int NPCType;
    public readonly string MODE;
    public readonly string charName;
    public readonly int NPCLV;
    public readonly float ModleHeight;
    public readonly float ModelRadius;
    public readonly float ModeProportion;
    public readonly Vector3 UIModeLOffset;
    public readonly float UIModeLProportion;
    public readonly Vector3 UIModelRotation;
    public readonly int CanDeadFly;
    public readonly int Country;
    public readonly int MinAtk;
    public readonly int MaxAtk;
    public readonly int Def;
    public readonly int Realm;
    public readonly int PoisionAtk;
    public readonly int FireAtk;
    public readonly int IceAtk;
    public readonly int PoisionDef;
    public readonly int IceDef;
    public readonly int AtkInterval;
    public readonly int Hit;
    public readonly int MissRate;
    public readonly int SuperHiteRate;
    public readonly int OrgSpeed;
    public readonly int MoveType;
    public readonly int AtkDist;
    public readonly int Skill1;
    public readonly int Skill2;
    public readonly int Skill3;
    public readonly int Skill4;
    public readonly int Skill5;
    public readonly int Skill6;
    public readonly int Skill7;
    public readonly int Skill8;
    public readonly int AtkType;
    public readonly int Sight;
    public readonly int MoveArea;
    public readonly int DHP;
    public readonly int MaxHPEx;
    public readonly int IsBoss;
    public readonly int SP;
    public readonly int AIType;
    public readonly int CanAttack;
    public readonly float weight;
    public readonly string HeadPortrait;
    public readonly int Show;
    public readonly int AtkFeedback;
    public readonly int hurtFeedback;
    public readonly int AutomaticFace;
    public readonly int Dig;
    public readonly int[] Sounds;
    public readonly int LifeBarCount;
    public readonly int NPCEffect;
    public readonly int NPCSpeakID;
    public readonly int ClientRealm;
    public readonly string Equips;
    public readonly int NPCType;
    public readonly string MODE;
    public readonly string charName;
    public readonly int NPCLV;
    public readonly float ModleHeight;
    public readonly float ModelRadius;
    public readonly float ModeProportion;
    public readonly Vector3 UIModeLOffset;
    public readonly float UIModeLProportion;
    public readonly Vector3 UIModelRotation;
    public readonly int CanDeadFly;
    public readonly int Country;
    public readonly int MinAtk;
    public readonly int MaxAtk;
    public readonly int Def;
    public readonly int Realm;
    public readonly int PoisionAtk;
    public readonly int FireAtk;
    public readonly int IceAtk;
    public readonly int PoisionDef;
    public readonly int IceDef;
    public readonly int AtkInterval;
    public readonly int Hit;
    public readonly int MissRate;
    public readonly int SuperHiteRate;
    public readonly int OrgSpeed;
    public readonly int MoveType;
    public readonly int AtkDist;
    public readonly int Skill1;
    public readonly int Skill2;
    public readonly int Skill3;
    public readonly int Skill4;
    public readonly int Skill5;
    public readonly int Skill6;
    public readonly int Skill7;
    public readonly int Skill8;
    public readonly int AtkType;
    public readonly int Sight;
    public readonly int MoveArea;
    public readonly int DHP;
    public readonly int MaxHPEx;
    public readonly int IsBoss;
    public readonly int SP;
    public readonly int AIType;
    public readonly int CanAttack;
    public readonly float weight;
    public readonly string HeadPortrait;
    public readonly int Show;
    public readonly int AtkFeedback;
    public readonly int hurtFeedback;
    public readonly int AutomaticFace;
    public readonly int Dig;
    public readonly int[] Sounds;
    public readonly int LifeBarCount;
    public readonly int NPCEffect;
    public readonly int NPCSpeakID;
    public readonly int ClientRealm;
    public readonly string Equips;
    public NPCConfig()
    public NPCConfig()
    {
    }
@@ -82,128 +82,128 @@
        {
            var tables = input.Split('\t');
            int.TryParse(tables[0],out NPCID);
            int.TryParse(tables[0], out NPCID);
            int.TryParse(tables[1],out NPCType);
            int.TryParse(tables[1], out NPCType);
            MODE = tables[2];
            MODE = tables[2];
            charName = tables[3];
            charName = tables[3];
            int.TryParse(tables[4],out NPCLV);
            int.TryParse(tables[4], out NPCLV);
            float.TryParse(tables[5],out ModleHeight);
            float.TryParse(tables[5], out ModleHeight);
            float.TryParse(tables[6],out ModelRadius);
            float.TryParse(tables[6], out ModelRadius);
            float.TryParse(tables[7],out ModeProportion);
            float.TryParse(tables[7], out ModeProportion);
            UIModeLOffset=tables[8].Vector3Parse();
            UIModeLOffset = tables[8].Vector3Parse();
            float.TryParse(tables[9],out UIModeLProportion);
            float.TryParse(tables[9], out UIModeLProportion);
            UIModelRotation=tables[10].Vector3Parse();
            UIModelRotation = tables[10].Vector3Parse();
            int.TryParse(tables[11],out CanDeadFly);
            int.TryParse(tables[11], out CanDeadFly);
            int.TryParse(tables[12],out Country);
            int.TryParse(tables[12], out Country);
            int.TryParse(tables[13],out MinAtk);
            int.TryParse(tables[13], out MinAtk);
            int.TryParse(tables[14],out MaxAtk);
            int.TryParse(tables[14], out MaxAtk);
            int.TryParse(tables[15],out Def);
            int.TryParse(tables[15], out Def);
            int.TryParse(tables[16],out Realm);
            int.TryParse(tables[16], out Realm);
            int.TryParse(tables[17],out PoisionAtk);
            int.TryParse(tables[17], out PoisionAtk);
            int.TryParse(tables[18],out FireAtk);
            int.TryParse(tables[18], out FireAtk);
            int.TryParse(tables[19],out IceAtk);
            int.TryParse(tables[19], out IceAtk);
            int.TryParse(tables[20],out PoisionDef);
            int.TryParse(tables[20], out PoisionDef);
            int.TryParse(tables[21],out IceDef);
            int.TryParse(tables[21], out IceDef);
            int.TryParse(tables[22],out AtkInterval);
            int.TryParse(tables[22], out AtkInterval);
            int.TryParse(tables[23],out Hit);
            int.TryParse(tables[23], out Hit);
            int.TryParse(tables[24],out MissRate);
            int.TryParse(tables[24], out MissRate);
            int.TryParse(tables[25],out SuperHiteRate);
            int.TryParse(tables[25], out SuperHiteRate);
            int.TryParse(tables[26],out OrgSpeed);
            int.TryParse(tables[26], out OrgSpeed);
            int.TryParse(tables[27],out MoveType);
            int.TryParse(tables[27], out MoveType);
            int.TryParse(tables[28],out AtkDist);
            int.TryParse(tables[28], out AtkDist);
            int.TryParse(tables[29],out Skill1);
            int.TryParse(tables[29], out Skill1);
            int.TryParse(tables[30],out Skill2);
            int.TryParse(tables[30], out Skill2);
            int.TryParse(tables[31],out Skill3);
            int.TryParse(tables[31], out Skill3);
            int.TryParse(tables[32],out Skill4);
            int.TryParse(tables[32], out Skill4);
            int.TryParse(tables[33],out Skill5);
            int.TryParse(tables[33], out Skill5);
            int.TryParse(tables[34],out Skill6);
            int.TryParse(tables[34], out Skill6);
            int.TryParse(tables[35],out Skill7);
            int.TryParse(tables[35], out Skill7);
            int.TryParse(tables[36],out Skill8);
            int.TryParse(tables[36], out Skill8);
            int.TryParse(tables[37],out AtkType);
            int.TryParse(tables[37], out AtkType);
            int.TryParse(tables[38],out Sight);
            int.TryParse(tables[38], out Sight);
            int.TryParse(tables[39],out MoveArea);
            int.TryParse(tables[39], out MoveArea);
            int.TryParse(tables[40],out DHP);
            int.TryParse(tables[40], out DHP);
            int.TryParse(tables[41],out MaxHPEx);
            int.TryParse(tables[41], out MaxHPEx);
            int.TryParse(tables[42],out IsBoss);
            int.TryParse(tables[42], out IsBoss);
            int.TryParse(tables[43],out SP);
            int.TryParse(tables[43], out SP);
            int.TryParse(tables[44],out AIType);
            int.TryParse(tables[44], out AIType);
            int.TryParse(tables[45],out CanAttack);
            int.TryParse(tables[45], out CanAttack);
            float.TryParse(tables[46],out weight);
            float.TryParse(tables[46], out weight);
            HeadPortrait = tables[47];
            HeadPortrait = tables[47];
            int.TryParse(tables[48],out Show);
            int.TryParse(tables[48], out Show);
            int.TryParse(tables[49],out AtkFeedback);
            int.TryParse(tables[49], out AtkFeedback);
            int.TryParse(tables[50],out hurtFeedback);
            int.TryParse(tables[50], out hurtFeedback);
            int.TryParse(tables[51],out AutomaticFace);
            int.TryParse(tables[51], out AutomaticFace);
            int.TryParse(tables[52],out Dig);
            int.TryParse(tables[52], out Dig);
            string[] SoundsStringArray = tables[53].Trim().Split(StringUtility.splitSeparator,StringSplitOptions.RemoveEmptyEntries);
            Sounds = new int[SoundsStringArray.Length];
            for (int i=0;i<SoundsStringArray.Length;i++)
            {
                 int.TryParse(SoundsStringArray[i],out Sounds[i]);
            }
            string[] SoundsStringArray = tables[53].Trim().Split(StringUtility.splitSeparator, StringSplitOptions.RemoveEmptyEntries);
            Sounds = new int[SoundsStringArray.Length];
            for (int i = 0; i < SoundsStringArray.Length; i++)
            {
                int.TryParse(SoundsStringArray[i], out Sounds[i]);
            }
            int.TryParse(tables[54],out LifeBarCount);
            int.TryParse(tables[54], out LifeBarCount);
            int.TryParse(tables[55],out NPCEffect);
            int.TryParse(tables[55], out NPCEffect);
            int.TryParse(tables[56],out NPCSpeakID);
            int.TryParse(tables[56], out NPCSpeakID);
            int.TryParse(tables[57],out ClientRealm);
            int.TryParse(tables[57], out ClientRealm);
            Equips = tables[58];
            Equips = tables[58];
        }
        catch (Exception ex)
        {
@@ -213,13 +213,13 @@
    static Dictionary<string, NPCConfig> configs = new Dictionary<string, NPCConfig>();
    public static NPCConfig Get(string id)
    {
        if (!inited)
    {
        if (!inited)
        {
            Debug.Log("NPCConfig 还未完成初始化。");
            return null;
        }
        if (configs.ContainsKey(id))
        {
            return configs[id];
@@ -235,7 +235,7 @@
        return config;
    }
    public static NPCConfig Get(int id)
    public static NPCConfig Get(int id)
    {
        return Get(id.ToString());
    }
@@ -262,32 +262,32 @@
        return values;
    }
    public static bool Has(string id)
    public static bool Has(string id)
    {
        return configs.ContainsKey(id) || rawDatas.ContainsKey(id);
    }
    public static bool Has(int id)
    public static bool Has(int id)
    {
        return Has(id.ToString());
    }
    public static bool inited { get; private set; }
    public static bool inited { get; private set; }
    protected static Dictionary<string, string> rawDatas = new Dictionary<string, string>();
    public static void Init(bool sync=false)
    public static void Init(bool sync = false)
    {
        inited = false;
        var path = string.Empty;
        inited = false;
        var path = string.Empty;
        if (AssetSource.refdataFromEditor)
        {
            path = ResourcesPath.CONFIG_FODLER +"/NPC.txt";
            path = ResourcesPath.CONFIG_FODLER + "/NPC.txt";
        }
        else
        {
            path = AssetVersionUtility.GetAssetFilePath("config/NPC.txt");
        }
        var tempConfig = new NPCConfig();
        var tempConfig = new NPCConfig();
        var preParse = tempConfig is IConfigPostProcess;
        if (sync)
@@ -305,38 +305,42 @@
                if (preParse)
                {
                    configs[id] = new NPCConfig(line);
                    var config = new NPCConfig(line);
                    configs[id] = config;
                    (config as IConfigPostProcess).OnConfigParseCompleted();
                }
                else
                {
                    rawDatas[id] = line;
                }
            }
            inited = true;
            inited = true;
        }
        else
        {
            ThreadPool.QueueUserWorkItem((object _object) =>
            {
                var lines = File.ReadAllLines(path);
                if (!preParse)
                {
                    rawDatas = new Dictionary<string, string>(lines.Length - 3);
                }
                if (!preParse)
                {
                    rawDatas = new Dictionary<string, string>(lines.Length - 3);
                }
                for (int i = 3; i < lines.Length; i++)
                {
                    var line = lines[i];
                    var index = line.IndexOf("\t");
                    var id = line.Substring(0, index);
                    if (preParse)
                    {
                        configs[id] = new NPCConfig(line);
                    }
                    else
                    {
                        rawDatas[id] = line;
                    }
                    if (preParse)
                    {
                        var config = new NPCConfig(line);
                        configs[id] = config;
                        (config as IConfigPostProcess).OnConfigParseCompleted();
                    }
                    else
                    {
                        rawDatas[id] = line;
                    }
                }
                inited = true;
Core/GameEngine/Model/ConfigBase.cs
@@ -1,38 +1,6 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Text.RegularExpressions;

public interface IConfigPostProcess
{
    void OnConfigParseCompleted();
}
public class ConfigBase
{
    public ConfigBase()
    {
    }
    public ConfigBase(string content)
    {
    }
    public virtual string getKey()
    {
        return string.Empty;
    }
    public virtual void Parse(string content)
    {
    }
    protected static bool IsNumeric(string value)
    {
        return !string.IsNullOrEmpty(value) && Regex.IsMatch(value, @"^[+-]?\d*[.]?\d*$");
    }
}
Core/GameEngine/Model/ConfigManager.cs
@@ -20,24 +20,6 @@
        private set { m_Inited = value; }
    }
    Dictionary<int, Dictionary<string, ConfigBase>> configDictionary = new Dictionary<int, Dictionary<string, ConfigBase>>();
    public void RegisterGlobalEvent()
    {
        SnxxzGame.Instance.RemoveApplicationOutAction(OnApplicationOut);
        SnxxzGame.Instance.AddApplicationOutAction(OnApplicationOut);
    }
    public void LoadPriorBundleConfig()
    {
    }
    public void PreLoadConfigs()
    {
    }
    List<ConfigTask> configTasks = new List<ConfigTask>();
    public IEnumerator Co_LoadConfigs()
    {
@@ -91,49 +73,6 @@
    public void SyncLoadConfigs()
    {
    }
    public bool AllCompleted()
    {
        foreach (var task in configTasks)
        {
            if (task.state != TaskState.ParseSuccess)
            {
                // DesignDebug.LogFormat("未完成是配置解析任务:{0},当前状态:{1}", task.taskName, task.state);
                return false;
            }
        }
        return true;
    }
    public float GetProgress()
    {
        var count = 0;
        foreach (var task in configTasks)
        {
            if (task.state == TaskState.ParseSuccess)
            {
                count++;
            }
        }
        return (float)count / configTasks.Count;
    }
    public int GetTaskCount()
    {
        return configTasks.Count;
    }
    public void ConfigParsePostProcess()
    {
        foreach (var task in configTasks)
        {
            configDictionary[task.token] = task.container;
        }
        inited = true;
    }
    int GetMinWorkingTaskCount()
@@ -204,315 +143,6 @@
            default:
                return 5;
        }
    }
    ConfigTask AddAsyncTask<T>() where T : ConfigBase, new()
    {
        var typeName = typeof(T).Name;
        var path = string.Empty;
        var fileName = typeName.Substring(0, typeName.Length - 6);
        if (AssetSource.refdataFromEditor)
        {
            path = ResourcesPath.CONFIG_FODLER + "/" + fileName + ".txt";
        }
        else
        {
            path = AssetVersionUtility.GetAssetFilePath(StringUtility.Contact("config/", fileName, ".txt"));
        }
        var task = new ConfigTask(typeof(T), path);
        Action<ConfigTask> launch = (ConfigTask _task) => { ReadFile(_task, OnEndReadFile<T>); };
        task.launch = launch;
        configTasks.Add(task);
        return task;
    }
    void StartAsyncTask(ConfigTask _task)
    {
        _task.state = TaskState.Working;
        _task.launch(_task);
    }
    void ReadFile(ConfigTask _task, Action<ConfigTask> _callBack)
    {
        ThreadPool.QueueUserWorkItem(
            (object _obj) =>
            {
                string[] lines = null;
                try
                {
                    lines = File.ReadAllLines(_task.filePath, Encoding.UTF8);
                    _task.state = TaskState.ReadFileSuccess;
                }
                catch (Exception ex)
                {
                    _task.state = TaskState.ReadFileFailure;
                    DebugEx.Log(ex);
                }
                finally
                {
                    _task.contentLines = lines;
                    if (_callBack != null)
                    {
                        _callBack(_task);
                    }
                }
            }
            );
    }
    void AsyncParseConfig<T>(ConfigTask _task, Action<ConfigTask> _callBack) where T : ConfigBase, new()
    {
        ThreadPool.QueueUserWorkItem(
            (object _obj) =>
            {
                try
                {
                    var count = 0;
                    var container = _task.container as Dictionary<string, ConfigBase>;
                    for (int i = 3; i < _task.contentLines.Length; i++)
                    {
                        var newConfig = new T();
                        newConfig.Parse(_task.contentLines[i]);
                        if (newConfig is IConfigPostProcess)
                        {
                            (newConfig as IConfigPostProcess).OnConfigParseCompleted();
                        }
                        container[newConfig.getKey()] = newConfig;
                        count++;
                        if (count >= 500)
                        {
                            count = 0;
                            Thread.Sleep(30);
                        }
                    }
                    _task.state = TaskState.ParseSuccess;
                }
                catch (Exception ex)
                {
                    _task.state = TaskState.ParseFailure;
                    DebugEx.Log(ex);
                }
                finally
                {
                    if (_callBack != null)
                    {
                        _callBack(_task);
                    }
                }
            }
            );
    }
    private void OnEndReadFile<T>(ConfigTask _task) where T : ConfigBase, new()
    {
        if (isPlaying && _task.state == TaskState.ReadFileFailure)
        {
            Thread.Sleep(30);
            ReadFile(_task, OnEndReadFile<T>);
        }
        else
        {
            _task.capacity = Mathf.Max(_task.contentLines.Length - 3, 0);
            _task.container = new Dictionary<string, ConfigBase>(_task.capacity);
            AsyncParseConfig<T>(_task, OnEndParse<T>);
        }
    }
    private void OnEndParse<T>(ConfigTask _task) where T : ConfigBase, new()
    {
        if (isPlaying && _task.state == TaskState.ParseFailure)
        {
            Debug.LogFormat("配置表解析失败:{0}", _task.taskName);
            Thread.Sleep(30);
            ReadFile(_task, OnEndReadFile<T>);
        }
    }
    private void StartSyncTask<T>() where T : ConfigBase, new()
    {
        var typeName = typeof(T).Name;
        var fileName = typeName.Substring(0, typeName.Length - 6);
        var path = string.Empty;
        if (Application.isEditor || AssetSource.refdataFromEditor)
        {
            path = ResourcesPath.CONFIG_FODLER + "/" + fileName + ".txt";
        }
        else
        {
            path = AssetVersionUtility.GetAssetFilePath(StringUtility.Contact("config/", fileName, ".txt"));
        }
        var task = new ConfigTask(typeof(T), path);
        task.contentLines = File.ReadAllLines(path, Encoding.UTF8);
        if (task.contentLines != null && task.contentLines.Length > 3)
        {
            var length = Mathf.Max(task.contentLines.Length - 3, 0);
            task.capacity = length;
            task.container = new Dictionary<string, ConfigBase>(length);
            task.state = TaskState.ReadFileSuccess;
            SyncParseConfig<T>(task);
        }
        else
        {
            task.state = TaskState.ReadFileFailure;
        }
    }
    private void SyncParseConfig<T>(ConfigTask _task) where T : ConfigBase, new()
    {
        try
        {
            for (int i = 3; i < _task.contentLines.Length; i++)
            {
                var newConfig = new T();
                newConfig.Parse(_task.contentLines[i]);
                if (newConfig is IConfigPostProcess)
                {
                    (newConfig as IConfigPostProcess).OnConfigParseCompleted();
                }
                _task.container[newConfig.getKey()] = newConfig;
            }
        }
        catch (Exception ex)
        {
            DebugEx.Log(ex);
        }
        finally
        {
            _task.state = TaskState.ParseSuccess;
            configDictionary[_task.token] = _task.container;
        }
    }
    public T Get<T>(string _dwTemplateID) where T : ConfigBase, new()
    {
        if (string.IsNullOrEmpty(_dwTemplateID))
        {
            return null;
        }
        var token = typeof(T).MetadataToken;
        if (configDictionary.ContainsKey(token) == false)
        {
            DebugEx.LogErrorFormat("not find the config:{0}", token);
            return null;
        }
        ConfigBase config = null;
        var dic = configDictionary[token];
        if (!dic.ContainsKey(_dwTemplateID))
        {
            DebugEx.LogFormat("not find the config:{0},<color=#ff0000ff>ID:{1}</color>", typeof(T).Name, _dwTemplateID);
        }
        else
        {
            config = dic[_dwTemplateID];
        }
        return config as T;
    }
    public T Get<T>(int _dwTemplateID) where T : ConfigBase, new()
    {
        return Get<T>(_dwTemplateID.ToString());
    }
    public bool ContainKey<T>(int _id)
    {
        return ContainKey<T>(_id.ToString());
    }
    public bool ContainKey<T>(string _key)
    {
        if (string.IsNullOrEmpty(_key))
        {
            return false;
        }
        var token = typeof(T).MetadataToken;
        if (configDictionary.ContainsKey(token) == false)
        {
            return false;
        }
        var dic = configDictionary[token];
        return dic.ContainsKey(_key);
    }
    public List<string> GetAllKeys<T>() where T : ConfigBase
    {
        var token = typeof(T).MetadataToken;
        if (!configDictionary.ContainsKey(token))
        {
            DebugEx.LogErrorFormat("not find the dic of config: {0}", typeof(T).Name);
            return null;
        }
        var dicTemplate = configDictionary[token];
        return new List<string>(dicTemplate.Keys);
    }
    public List<T> GetAllValues<T>() where T : ConfigBase, new()
    {
        var token = typeof(T).MetadataToken;
        if (!configDictionary.ContainsKey(token))
        {
            DebugEx.LogErrorFormat("not find the dic of config: {0}", typeof(T).Name);
            return null;
        }
        var configs = new List<T>();
        var dicTemplate = configDictionary[token];
        foreach (var value in dicTemplate.Values)
        {
            configs.Add(value as T);
        }
        return configs;
    }
    class ConfigTask
    {
        public readonly Type type;
        public readonly int token;
        public readonly string taskName;
        public readonly string filePath;
        public Action<ConfigTask> launch;
        public int capacity = 0;
        public string[] contentLines;
        public Dictionary<string, ConfigBase> container;
        public TaskState state = TaskState.None;
        public ConfigTask(Type _type, string _filePath)
        {
            type = _type;
            taskName = type.Name;
            token = type.MetadataToken;
            filePath = _filePath;
        }
    }
    public enum TaskState
    {
        None = 0,
        Working = 1,
        ReadFileSuccess = 2,
        ReadFileFailure = 3,
        ParseFailure = 4,
        ParseSuccess = 5,
    }
    private void OnApplicationOut()
Fight/GameActor/PartialModelResConfig.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
public partial class ModelResConfig : ConfigBase, IConfigPostProcess
public partial class ModelResConfig : IConfigPostProcess
{
    private static readonly Dictionary<string, ModelResConfig> suitDict = new Dictionary<string, ModelResConfig>();
    private static readonly Dictionary<string, ModelResConfig> petDict = new Dictionary<string, ModelResConfig>();