Unity实用框架(四)全局数据管理框架
数据管理看似简单,里面的门道却很深。说它简单,是因为实现在一个游戏中传递数据、储存数据的方式实在太多,实现方式也并不复杂;说它门道深,是因为,要想实现一个安全的、通用的、灵活的、高效的数据管理框架,是一件相当讲究的事情。下面,笔者将介绍一种实现全局数据管理的方式,供读者参考。
注:本文中的所有“数据”只已经加载到内存中的数据,外存中以及将数据加载到内存的过程不在讨论之列。
数据的形式
在面向对象的编程思想的指导下,我们很容易地想到将某一模块的数据封装成一个类进行储存。首先,需要一个数据基类来整合所有数据共有的特点。
IGameData
这个接口定义了一个数据集可能暴露给其调用者的方法。
public interface IGameData
{
bool IsValid { get; }
string DataName { get; }
//当数据被移除时的回调函数,通常用于主动销毁一些对象以释放内存
void OnRemoved();
GameDataType GameDataType { get; }
//load函数通过各种方式将数据导入内存,包括但不限于数据库读、文件读、网络请求等。具体加载方式不做讨论。
bool Load();
// 同步加载
IEnumerator LoadSync();
}
上面的各个方法和属性通过其名称就能看出作用。这里的GameDataType定义了这组数据的加载方式,简单起见,这里只有两种方式:同步加载和其他。
public enum GameDataType
{
None = 0,
LoadSync = 1,
}
GameData : IGameData
GameData是一个抽象类,它实现IGameData接口。其实可以将IGameData中的定义全部放在GameData中,但面向接口的代码书写形式永远是更好的,而且,传递只含有方法的接口的效率显然高于传递包含数据的类。一个完整的GameData类除实现接口外,还会做一些异常状态处理和错误处理。数据的加密也可以在这里进行,尤其是游戏中一些比较敏感的、可能被修改器修改的数据,在这里做一些加密可以有效的防止部分作弊行为。为了简化我们的讨论,这里并不会深入上述功能。
public abstract class GameData : IGameData
数据的管理
GameDataSystem
首先,用一个字典去储存当前所有的GameData
private ConcurrentDictionary<string, IGameData> gameDataDict;
使用ConcurrentDictionary是处于线程安全的考虑,可以百度一下它的作用。使用String作为独一无二的键值,而不使用IGameData本身,是因为一个IGameData对应的数据块可能有多个,比如EnemyAttackData,多个Enemy拥有各自的Data因此使用string作为标明数据的键值。此字符串应当与IGameData中声明的DataName对应。
使用数据之前,要先把它加载到内存,朴实无华地将对应的IGameData存入字典。此函数的调用者可以是更高层模块,也可以是想要修改某处数据的模块。
IGameData.Load()保证了GameData的有效性,并决定了它加载到内存的方式。
gameDataDict保存的是GameData的引用。因此在GameData的子类修改自身时,它不需要重新调用AddData将自己注册在GameDataSystem上。
public static bool AddData(IGameData data)
{
gameDataDict[gameData.DataName] = gameData;
return data.Load();
}
获取某块数据。
public static T GetData<T>(string dataName) where T : class, IGameData
{
if (gameDataDict.ContainsKey(dataName))
{
return null;
}
var data = service[dataName] as T;
//保证字符串对应的数据和声明的数据类型T相同
if (data == null)
{
return null;
}
return data;
}
当一个模块被移除时,它应当主动调用GameDataSystem.RemoveGameData()以从数据模块中移除此数据,此函数将调用IGameData的Onremove函数。
public static void RemoveGameData(string gamedataName)
{
GameData data;
while(gameDataDict.TryRemove(gameDataDict[gamedataName], data)){
//多线程保护
//WaitForSecond(...)
}
data.OnRemove();
}
想要使用GameDataSystem的模块通过如下方式调用:
var data = GameDataSystem.GetData<EnemyKilledData>("EnemyKilled") as EnemyKilledData;
上面这行代码或许就可以出现在游戏内部的任务系统中(比如玩家当前杀死了多少怪物)。
更新时间:2022.7.18