配置文件
在编辑器的StreamingAssets目录下,放置一个json格式的InitPlanes.json
文件,作为配置文件。同时我们也在Resources文件夹下放这个文件,用在后面测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| { "planes": [ { "planeId": 0, "level": 0, "attack": 5, "fireRate": 0.8, "life": 100 }, { "planeId": 1, "level": 0, "attack": 10, "fireRate": 0.6, "life": 120 }, { "planeId": 2, "level": 0, "attack": 20, "fireRate": 0.4, "life": 150 }, { "planeId": 3, "level": 0, "attack": 40, "fireRate": 0.2, "life": 200 } ] }
|
配置文件读取
IReader
读取接口定义一种读取器,用于读取各种格式的配置文件,通过实现这个接口,可以实现Json读取、Xml读取、Scriptable Object读取等。
在Scripts——Modle——Reader文件夹内新建IReader
文件
索引器和属性的不同点
- 索引器不能是静态的,索引器必须通过对应类的实例访问。
- 索引器相当于方法,可以被重载。比如
this[int key] ; this[int key1,string key2]
索引器和属性的相同点
- 都能使用get,set语法。注意索引器的形参不能命名为“value”,这样就在get中重复了
链式写法:定义接口内的方法或索引器时返回接口本身。
1 2 3 4 5 6 7
| public interface IReader { IReader this[string key] { get; } IReader this[int key] { get; } void Get<T>(System.Action<T> callback); void SetData(object data); }
|
JsonReader(难点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
| using System; using System.ComponentModel; using System.Collections; using System.Collections.Generic; using LitJson; using UnityEngine;
public class JsonReader : IReader { private JsonData _data; private JsonData _tempData; private KeyQueue _keys; private readonly Queue<KeyQueue> _keyQueues = new(); public IReader this[string key] { get { if (!SetKey(key)) { try { _tempData = _tempData[key]; } catch (Exception) { Debug.LogError("在数据中无法找到对应Key:" + "Key为:"+ key + "数据为:" + _tempData.ToJson()); } } return this; } }
public IReader this[int key] { get { if (!SetKey(key)) { try { _tempData = _tempData[key]; } catch (Exception) { Debug.LogError("在数据中无法找到对应Key:" + "Key为:" + key + "数据为:" + _tempData.ToJson()); } } return this; } } private bool SetKey<T>(T key) { if (_data == null || _keys != null) { if (_keys == null) _keys = new KeyQueue();
IKey keyData = new Key(); keyData.Set(key); _keys.Enqueue(keyData);
return true; } return false; } public void Get<T>(Action<T> callback) { if (_keys != null) { _keys.OnComplete(dataTemp => { T value = GetValue<T>(dataTemp); ResetData(); callback(value); });
_keyQueues.Enqueue(_keys); _keys = null; ExecuteKeyQueues(); return; }
if (callback == null) { Debug.LogWarning("当前回调方法为空,不返回数据"); ResetData(); return; } T data = GetValue<T>(_tempData); ResetData(); callback(data); }
private void ExecuteKeyQueues() { if(_data == null) return;
IReader reader = null; foreach (KeyQueue keyQueue in _keyQueues) { foreach (object key in keyQueue) { if (key is string) reader = this[(string)key]; else if (key is int) reader = this[(int)key]; else Debug.LogError("当前键值类型还不支持"); } keyQueue.Complete(_tempData); } } private T GetValue<T>(JsonData data) { TypeConverter converter = TypeDescriptor.GetConverter(typeof(T)); return (T)converter.ConvertTo(data.ToString(), typeof(T)); } private void ResetData() { _tempData = _data; }
public void SetData(object data) { if (data is string) { _data = JsonMapper.ToObject(data as string); ResetData(); ExecuteKeyQueues(); } else { Debug.LogError("传入数据类型错误,当前类只能解析Json"); } } }
|
注意看索引器的写法,JsonReader
其实是将LitJson
提供的索引访问方式又封装了一层。每次用索引器访问一次,就更新一次tempData
的引用,最后使用Get
方法获取最终数据并重置tempData
。
在使用JsonReader
获取数据之前,需要执行SetData
方法,这样_data
才有数据,而SetData
方法是在异步读取配置文件的回调中读取的,这意味着我们在调用JsonReader
时,里面可能没有数据。这里的解决方案是提前缓存访问的key值,等到配置读取完毕后通过缓存的key值返回数据,而key值可能是string,也可能是int,所以使了一个IKey
接口来统一缓存key值,并且实现一个自定义的KeyQueue
类,更加方便地遍历缓存的key值。
需要注意的是,一个KeyQueue
类保存的是一组访问命令,我们还需要一个Queue<KeyQueue>
来缓存多组访问命令。
KeyQueue类
包装了一个Queue<IKey>
,并且实现了一种迭代器和一个回调,这个回调是JsonReader
确认Json数据读取完毕后,把缓存的访问命令都执行时调用的,之所以会把JsonReader
的回调传递到这里是因为我们可能有很多个KeyQueue
访问命令需要执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class KeyQueue : IEnumerable { private Queue<IKey> _keys = new Queue<IKey>(); private Action<JsonData> _onComplete; public void Enqueue(IKey key) { _keys.Enqueue(key); } public IKey Dequeue() { return _keys.Dequeue(); } public void Clear() { _keys.Clear(); } public void Complete(JsonData jsonData) { _onComplete?.Invoke(jsonData); } public void OnComplete(Action<JsonData> callback) { _onComplete = callback; }
public IEnumerator GetEnumerator() { foreach (IKey key in _keys) { yield return key.Get(); } } }
|
Key类和IKey接口
设计这两个是为了解决Key的类型很多,无法在一个Queue里遍历的问题。每次添加一个key值都自动记录它的Type(这里并没有记录)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public interface IKey { Type KeyType { get; } void Set<T>(T key); object Get(); } public class Key : IKey { private object _key; public Type KeyType { get;private set; } public object Get() { return _key; }
public void Set<T1>(T1 key) { _key = key; } }
|
测试
通过测试代码,能够理解之前缓存Key的意义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| using UnityEngine;
public class GameRoot : MonoBehaviour { private void Start() {
string jsonString = Resources.Load<TextAsset>("InitPlane").text; JsonReader reader = new JsonReader(); reader["planes"][0]["fireRate"].Get<float>(value => Debug.Log(value)); reader["planes"][2]["attack"].Get<int>(value => Debug.Log(value)); reader.SetData(jsonString);
} }
|
ReaderManager
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| using System.Collections.Generic; using UnityEngine;
public class ReaderMgr : NormalSingleton<ReaderMgr> { private Dictionary<string,IReader> _readersDic = new Dictionary<string,IReader>(); public IReader GetReader(string path) { IReader reader = null; if(_readersDic.ContainsKey(path)) reader = _readersDic[path]; else { reader = ReaderConfig.GetReader(path); LoadMgr.Instance.LoadConfig(path,data=>reader.SetData(data)); if(reader != null) _readersDic[path] = reader; else Debug.LogError("未获取到对应Reader:" + path); } return reader; } }
|
ReaderConfig
使用脚本的形式写Config,使用字典来决定返回的Reader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| using System; using System.Collections.Generic; using UnityEngine;
public class ReaderConfig { private static readonly Dictionary<string, Func<IReader>> _readersDic = new Dictionary<string, Func<IReader>>() { {".json",()=>new JsonReader()}, }; public static IReader GetReader(string path) { foreach (KeyValuePair<string, Func<IReader>> pair in _readersDic) { if (path.Contains(pair.Key)) { return pair.Value(); } } Debug.LogError("未找到对应文件的读取器 " + path); return null; } }
|
注意这里的使用KeyValuePair
遍历字典的写法
当需要新的读取器时,在实现对应文件的读取器后只需要在这里的字典中添加一下即可。还是尽可能修改配置的思想
ResouceLoader
修改ILoader
,添加读取配置文件的方法
1 2 3 4 5 6 7 8
| using System; using UnityEngine;
public interface ILoader { public GameObject LoadPrefab(string path, Transform parent = null); void LoadConfig(string path,Action<object> onComplete); }
|
添加配置文件Path
1 2 3 4 5 6 7 8
| using UnityEngine;
public class Path { private static readonly string CONFIG_FOLDER = Application.streamingAssetsPath + "/Config"; public static readonly string INIT_PLANE_CONFIG = CONFIG_FOLDER + "/InitPlane.json"; }
|
我们把配置文件InitPlane.json
放在了StreamingAssets目录下,所以需要使用UnityWebRequest
来加载。
修改ResourceLoader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Networking;
public class ResourceLoader : ILoader { public GameObject LoadPrefab(string path,Transform parent = null) { GameObject prefab = Resources.Load<GameObject>(path); GameObject temp = Object.Instantiate(prefab,parent); return temp; } public void LoadConfig(string path, System.Action<object> onComplete) { CoroutineMgr.Instance.ExecuteOnce(Config(path, onComplete)); } private IEnumerator Config(string path, System.Action<object> onComplete) { using (UnityWebRequest unityWebRequest = UnityWebRequest.Get(path)) { yield return unityWebRequest.SendWebRequest(); switch (unityWebRequest.result) { case UnityWebRequest.Result.ConnectionError: Debug.Log("连接出错"); yield break; case UnityWebRequest.Result.ProtocolError: Debug.Log("协议出错"); yield break; case UnityWebRequest.Result.DataProcessingError: Debug.Log("数据处理出错"); yield break; } onComplete?.Invoke(unityWebRequest.downloadHandler.text); Debug.Log("文件加载成功:路径为:" + path); } } }
|
修改LoadMgr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| using System; using UnityEngine;
public class LoadMgr : NormalSingleton<LoadMgr>, ILoader { private ILoader m_Loader; public LoadMgr() { m_Loader = new ResourceLoader(); }
public GameObject LoadPrefab(string path,Transform parent = null) { return m_Loader.LoadPrefab(path,parent); } public void LoadConfig(string path, Action<object> onComplete) { m_Loader.LoadConfig(path, onComplete); } }
|