Unity实用框架(四)全局数据管理框架

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

猜你喜欢

转载自blog.csdn.net/natrick/article/details/125855503