前言

首先要明白的一点是GF里面的全局配置并不是指的一般意义上的数据表,全局配置表的格式不能随便自定义,它的格式必须与当前的ConfigHelper里面写定的解析格式保持一致,否则无法解析。而它是用来存储一些全局性质的只读配置,如玩家初始速度、游戏初始音量等。

全局配置模块结构图

从结构上就可以看出几个点:
  • ConfigManager和DataProvider存在许多个相同的接口,是因为ConfigManager主要是对DataProvider里面的方法进行封装,模块的核心逻辑位于DataProvider。
  • DataProvider和DefaultConfigHelper的核心功能方法名称相同,是因为真正的对配置文件的读取,解析功能位于DefaultConfigHelper。

这样做的好处是不仅实现了解耦,而且方便功能的修改和拓展,ConfigManager存储解析好的配置数据,并提供给外界功能接口;模块核心逻辑位于DataProvider;读取和解析的逻辑位于ConfigHelper。使用者可以使用默认的DefaultConfigHelper来加载对应格式的配置文件,也可以根据自己项目的需求,去书写自己的ConfigHelper来实现自定义的配置格式和对应的解析逻辑。

读取解析存储全局配置流程

整个流程为:读取————>解析————>存储。根据资源的名称,读取相对应的文件,然后根据定义的解析规则,解析成对应的配置数据,最后存储到ConfigManager里面。其中也支持二进制文件,并设置了相对应的缓存步骤对此格式的配置文件进行处理。

ConfigManager

ConfigManager主要是对DataProvider里面的方法进行封装,提供给外部功能接口实现配置的读取,解析,并且会存储解析好的全局配置数据,供外界使用。

类内信息

  • m_configData:存储解析好的全局配置数据,提供给外界调用。根据其Value的ConfigData类型,可以得知支持的数据类型包括 int、float、string以及bool这四类。
  • m_DataProvider:模块的核心逻辑,用来读取配置文件。
  • m_configHelper:ConfigHelper的实例,用来解析配置文件。

DataProvider

Config模块的核心逻辑书写处,主要是通过获取的资源模块接口去读取配置文件,然后放入ConfigHelper里面进行解析。

类内信息

  • BlockSize: 缓存块大小,用来缓存配置文件的二进制流。
  • s_CachedBytes: 用于存储缓存的二进制流。之所以专门在此定义一下是为了避免频繁的内存分配,提高效率的同时降低内存碎片的可能性。
  • m_Owner: 当前DataProvider的拥有者,也就是ConfigManager。DataProvider是GF的基础模块,这里是ConfigManager在使用,这样做自然也是为了方便使用者修改和拓展。
  • m_ResourceManager: 资源管理器接口,用来读取配置文件。
  • m_DataProviderHelper:DataProviderHelper的实例,用来解析配置文件。这里的话其实就是DefaultConfigHelper的实例。

功能接口的也是支持对于文件(包含二进制文件)的读取,解析,存储。通过结构图和流程图已经能够直观了解到,这里不再赘述。

二进制流的缓存与释放

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
/// <summary>
/// 确保二进制流缓存分配足够大小的内存并缓存。
/// </summary>
/// <param name="ensureSize">要确保二进制流缓存分配内存的大小。</param>
public static void EnsureCachedBytesSize(int ensureSize)
{
if (ensureSize < 0)
{
throw new GameFrameworkException("Ensure size is invalid.");
}

if (s_CachedBytes == null || s_CachedBytes.Length < ensureSize)
{
FreeCachedBytes();
int size = (ensureSize - 1 + BlockSize) / BlockSize * BlockSize;
s_CachedBytes = new byte[size];
}
}

/// <summary>
/// 释放缓存的二进制流。
/// </summary>
public static void FreeCachedBytes()
{
s_CachedBytes = null;
}


DefaultConfigHelper

这是GF默认的全局配置解析器,看到这里,可以了解到因为每个部分都是功能分明,相对独立的,整个模块都便于支持使用者根据自己的需求和功能去修改和拓展的。
这里简单举例说明一下DefaultConfigHelper的的解析规则。

比如,接下来我们存在如下的全局配置文件,其配置内容如下:

我们调用模块接口进行读取:

1
GameEntry.Config.ReadData(configAssetName, this);

来到DefaultConfigHelper的解析方法:

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
private static readonly string[] ColumnSplitSeparator = new string[] { "\t" };
private static readonly string BytesAssetExtension = ".bytes";
private const int ColumnCount = 4;


/// <summary>
/// 解析全局配置。
/// </summary>
/// <param name="configManager">全局配置管理器。</param>
/// <param name="configString">要解析的全局配置字符串。</param>
/// <param name="userData">用户自定义数据。</param>
/// <returns>是否解析全局配置成功。</returns>
public override bool ParseData(IConfigManager configManager, string configString, object userData)
{
try
{
int position = 0;
string configLineString = null;
while ((configLineString = configString.ReadLine(ref position)) != null)
{
if (configLineString[0] == '#')
{
continue;
}

string[] splitedLine = configLineString.Split(ColumnSplitSeparator, StringSplitOptions.None);
if (splitedLine.Length != ColumnCount)
{
Log.Warning("Can not parse config line string '{0}' which column count is invalid.", configLineString);
return false;
}

string configName = splitedLine[1];
string configValue = splitedLine[3];
if (!configManager.AddConfig(configName, configValue))
{
Log.Warning("Can not add config with config name '{0}' which may be invalid or duplicate.", configName);
return false;
}
}

return true;
}
catch (Exception exception)
{
Log.Warning("Can not parse config string with exception '{0}'.", exception.ToString());
return false;
}
}

可以看到通过以制表符为分割,将配置文件的每一行内容分割成四个字段(ColumnCount),获取到了第一个和第四个配置信息,并将其添加到ConfigManager里面。

使用的情况:

1
GameEntry.Config.GetInt("Scene.Menu")

这样,我们就实现了全局配置的定义,读取,解析,存储以及使用。