【Framework系列之Client】DataManager介绍
今天来介绍一下DataManager,DataManager用于负责数据的存储和管理,数据包括了Config数据、Server数据、PlayerPrefs数据等。话不多说直接开始。
Config数据
功能介绍
Config涵盖的是配置相关的数据,Config数据在Framework-Design框架工程中的Excel进行配置,Excel中的配置数据将会被导出成 json文件和 .cs类文件,在Framework-Client运行时Config数据会被反序列化成对应的类对象。关于导表的详细介绍可以看《Excel转Json,配置表、导表工具介绍》。
Config模块主要包括了DataManager、ConfigContainer、ConfigBase三个部分。DataManager包含了所有配置类型的ConfigContainer,ConfigContainer则是包含了同一配置类型所有的Config数据,ConfigBase则是Config数据类型基类。三个部分结构关系大致如下图:
DataManager的主要功能是:
- 存储所有配置类型的ConfigContainer对象。
- 提供多种获取Config数据的接口。
DataManager用Dictionary<string, IConfigContainer>类型容器来存储ConfigContainer实例,其中键对应的是ConfigName,值对应的是ConfigContainer实例。ConfigContainer实例的创建、销毁在DataManager中完成。
DataManager提供了多种获取Config数据的接口。接口分为泛型接口和参数接口两种,泛型接口可以通过传入的配置类型获取ConfigName,参数接口则是直接通过传入ConfigName。通过ConfigName可以获取ConfigContainer对象。DataManager通过调用ConfigContainer的接口获取Config数据。
下面是DataManager的部分示例代码:
namespace Framework.Manager
{
public class DataManager : ManagerBase<DataManager>
{
private Dictionary<string, IConfigContainer> mConfigContainerDictionary = new Dictionary<string, IConfigContainer>();
/// <summary>获取配置(Id获取)</summary>
/// <typeparam name="T">配置类型</typeparam>
public T GetConfig<T>(string pId) where T : ConfigBase
{
string configName = typeof(T).Name;
IConfigContainer configContainer = GetConfigContainer(configName);
T config = configContainer.GetConfig<T>(pId);
if (config == null)
Debug.LogWarning($"Get config is null, {configName} don't exist id {pId}.");
return config;
}
/// <summary>获取配置(ConfigName,Id获取)</summary>
public IConfigBase GetConfig(string pConfigName, string pId)
{
IConfigContainer configContainer = GetConfigContainer(pConfigName);
if (configContainer == null)
return null;
IConfigBase config = configContainer.GetConfig(pId);
if (config == null)
Debug.LogWarning($"Get config is null, {pConfigName} don't exist id {pId}.");
return config;
}
/// <summary>获取ConfigContainer</summary>
private IConfigContainer GetConfigContainer(string pConfigName)
{
IConfigContainer configContainer = null;
try
{
if (!mConfigContainerDictionary.TryGetValue(pConfigName, out configContainer))
{
Type configType = Type.GetType($"Game.Data.Config.{pConfigName}, Assembly-CSharp");
if (configType == null)
{
throw new InvalidOperationException($"Game.Data.Config.{pConfigName} is not exist");
}
Type containerType = typeof(ConfigContainer<>).MakeGenericType(configType);
configContainer = Activator.CreateInstance(containerType) as IConfigContainer;
mConfigContainerDictionary.Add(pConfigName, configContainer);
}
configContainer.DeserializeConfig();
}
catch (InvalidOperationException ex)
{
Debug.LogWarning(ex);
}
return configContainer;
}
}
}
ConfigContainer是一个泛型类,ConfigContainer的主要功能是:
- 获取数据Json,反序列化Config数据,创建Config数据实例。
- 存储指定配置类型的所有配置数据,并以List<T>和Dictionary<string, T>两种方式存储。
- 提供多种获取Config数据的接口。
ConfigContainer用List<T>和Dictionary<string, T>用两种容器类型存储了Config数据,两个容器中的Config数据都是一样的,用两个容器的目的在于可以通过索引或者键值对的方式快速获取对象。
ConfigContainer提供了通过索引或者通过键值获取Config数据的接口。索引方式主要用于遍历数据,键值方式主要用于通过Id获取数据。
下面是ConfigContainer的示例代码:
public class ConfigContainer<T> : IConfigContainer where T : ConfigBase
{
private bool mIsDeserialize = false;
private List<T> mConfigList = new List<T>();
private Dictionary<string, T> mConfigDictionary = new Dictionary<string, T>();
public void DeserializeConfig()
{
if (!mIsDeserialize)
{
string configName = typeof(T).Name;
TextAsset textAsset = ManagerCollection.ResourceManager.GetTextAsset(configName);
if (textAsset != null)
{
mConfigList = JsonConvert.DeserializeObject<List<T>>(textAsset.text);
for (int i = 0; i < mConfigList.Count; i++)
{
T config = mConfigList[i];
mConfigDictionary.Add(config.id, config);
}
mIsDeserialize = true;
}
}
}
public C GetConfig<C>(string pKey) where C : ConfigBase
{
T configBase = default;
mConfigDictionary.TryGetValue(pKey, out configBase);
return configBase as C;
}
public C GetConfig<C>(int pIndex) where C : ConfigBase
{
T configBase = default;
if (pIndex >= 0 && pIndex < mConfigList.Count)
configBase = mConfigList[pIndex];
return configBase as C;
}
}
ConfigBase是Config数据的基类,所有Config类都继承自ConfigBase。ConfigBase的主要作用是在其内部定义了成员变量Id,确保Config数据有Id这个变量。Id是获取Config数据的重要键值。
下面是ConfigBase的示例代码:
public class ConfigBase : IConfigBase
{
public string id;
}
public class RoleConfig : ConfigBase
{
/// <summary>预制体名称</summary>
public string PrefabName;
/// <summary>角色名称</summary>
public string RoleName;
/// <summary>等级</summary>
public int Level;
/// <summary>血量上限</summary>
public int HpMax;
/// <summary>攻击力</summary>
public int Attack;
/// <summary>防御力</summary>
public int Defense;
}
设计思路
接下来说一下关于Config模块中的一些设计,记录一下自己的思考过程。
DataManager中获取Config提供了泛型和参数两种接口。这里更提倡优先使用泛型接口,其优点在于,使用泛型接口在编写过程中就能检测出传递的类型是否正确,并且接口也对泛型进行了类型约束。而参数接口则需要再运行时才能检测类型是否正确。
下面是接口示例:
public T GetConfig<T>(string pId) where T : ConfigBase
public IConfigBase GetConfig(string pConfigName, string pId)
Server数据
暂未实现,后期补齐,敬请期待。
PlayerPrefs数据
功能介绍
在DataManager中提供了对PlayerPrefs调用的Get和Set接口,接口被设计成泛型接口。示例代码如下,未展示全部类型:
public class DataManager : ManagerBase<DataManager>
{
public void SetPlayerPrefs<T>(string pKey, T pValue)
{
if (typeof(T) == typeof(int))
PlayerPrefs.SetInt(pKey, (int)(object)pValue);
else if (typeof(T) == typeof(float))
PlayerPrefs.SetFloat(pKey, (float)(object)pValue);
else
Debug.LogWarning($"SetPlayerPrefs T type is error. T type can't is {typeof(T)}");
}
public T GetPlayerPrefs<T>(string pKey, T pDefault = default)
{
if (typeof(T) == typeof(int))
{
int value = PlayerPrefs.GetInt(pKey, (int)(object)pDefault);
return (T)(object)value;
}
else if (typeof(T) == typeof(float))
{
float value = PlayerPrefs.GetFloat(pKey, (float)(object)pDefault);
return (T)(object)value;
}
else
{
Debug.LogWarning($"GetPlayerPrefs T type is error. T type can't is {typeof(T)}");
return default;
}
}
}
设计思路
将PlayerPrefs功能放入到DataManager中主要目的是为了让与数据相关的功能集中管理。
PlayerPrefs数据的接口设计成泛型,是希望简化接口,避免过多的定义接口。
相关文档链接
导表工具介绍:https://blog.csdn.net/huoyixian/article/details/139478570
工程源代码:https://gitee.com/huoyixian/release-framework-client