配置表工具

8.4k words

Desc:

使用数据表实现多语言、根据数据表生成相关数据类,数据包相当于一个外部的数据库,数据库中存放着与游戏人物相关的数据,只不过这个数据库里的数据是不能更改的

localization:

最开始的思路

通过Txt文本存储相关数据,相当于指定一个规则,指定的键对应相关的值,程序根据设定的键去读取,策划对txt修改值的详细数值,同时可以保证多个模块中数据的一致

1
2
m_test=213
m_npcNum=4

数据的读取,在游戏运行时,读取txt,将数据结果通过‘=’分割成key、value存入字典中

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

* 初始化数据,存入字典中
TextAsset file=Resources.Load<TextAsset>(path);
string[] lines = file.text.Split('\n');
foreach(string line in lines)
{
if (string.IsNullOrEmpty(line)) continue;//防止文件有空行或者null
if (line.Substring(0, 2) == "//") continue;//除去注释
string[] keyValues = line.Split('=');
if (m_languageDic.ContainsKey(keyValues[0]))
{
Debug.LogError("存在重复的键");
continue;
}
m_languageDic.Add(keyValues[0], keyValues[1]);
}

* 接口
public string V_GetValue(string key)
{
string value;
m_languageDic.TryGetValue(key, out value);
return value;
}

public int V_GetIntValue(string key)
{
string value = this.V_GetValue(key);
return int.Parse(value);
}

localization 之后的优化

上面的操作存在一个问题,用字符串去获取另一个字符串,相当于消耗了双份内存。为了表达这串文字的内容,key经常会设的很长,这会导致数据表变得很大,而且内存占用量也会加大很多,因为要存储一份常量的字符串。

优化方式:
根据这张表,生成特定的一个类去存储,用变量的形式去记录文字的id,通过文字表生成数据表,同时生成数据定义类,使用变量去代表数字

1
2
3
4
5
class TextKey{
public const BattleSceneFightAllianceWin=1;
public const BattleSceneFightAllianceLose=2;
}

多语言的情境下:
简单的方式:
制作多个表,每个表一门语言。可以根据不同的语言来获取
如果是一对一键值对,每次修改语言内容就要对所有语言内容进行修改,调试起来比较麻烦。可以采用一对多的形式合并语言内容数据表,把一个表里的一个key值对应多个语言的文字内容写在一个表里

注意:
如果把所有数据表的数据都集中在一个数据文件里,那么游戏在加载数据表时,就需要在一瞬间集中处理,导致CPU阻塞时间过长,发生卡顿现象
解决方式:
分散读取:
按需读取(但是多数情况下是在某个瞬间需要大部分数据,最好是在加载时指定读取顺序,并隔帧读取):

引用链接

功能的数据表:

表选择的是csv格式的文件去纯粹,因为文本是通过’,’进行分割。解析处理方便。正因为如此,数据配表中最好不能出现‘,’,具体解决方式,查询到有相关的,没自己实现过
Image text
生成的配置类Image text

具体思路:
字符串拼接生成文本,对于ARR类型的数据,当作字符串处理,在解析的时候自己根据类去不同的解析(会导致策划需求变动时,需要经常修改相关表的解析方式,暂时没想好怎么解决)
因为想要通过数据表生成的类去实现数据读取相关的操作,而这个类会随着表修改而自动生成。所以相关操作不能写在类里,我选择通过派生类的特性去解决,把配置表类分为两个文件去解决,一块会程序化自动生成,另一块做为平时开发的类去操作,ConfigBase类实现的功能主要是数据的初始化和提供查询的接口,比如根据id得到相关的数据

Excel2lua

来源

Image text
Image text

第一行 表名
第二行 所有列的描述
第三行 列名
第四行 数据类型

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

# 遍历第三行的所有列 保存字段名
for col in range(0, excel_sheet.ncols):
cell = excel_sheet.cell(2, col)
col_name_list.append(str(cell.value))
if (cell.ctype != 1):
print("found a invalid col name in col [%d] !~" % (col))

# 遍历第四行的所有列 保存数据类型
for col in range(0, excel_sheet.ncols):
cell = excel_sheet.cell(3, col)
col_val_type_list.append(str(cell.value))
if (cell.ctype != 1):
print("found a invalid col val type in col [%d] !~" % (col))

# 剔除表头、字段名和字段类型所在行
# 从第五行开始遍历 构造行数据
for row in range(4, excel_sheet.nrows):
# 保存数据索引 默认第一列为id
cell_id = excel_sheet.cell(row, 0)

# assert cell_id.ctype == 2, "found a invalid id in row [%d] !~" % (row)

# 检查id的唯一性
if cell_id.value in excel_data_dict:
print('[warning] duplicated data id: "%d", all previous value will be ignored!~' % (cell_id.value))

# row data list
row_data_list = []

# 保存每一行的所有数据
for col in range(0, excel_sheet.ncols):
cell = excel_sheet.cell(row, col)
k = col_name_list[col]
cell_val_type = col_val_type_list[col]

# ignored the string that start with '_'
if str(k).startswith('_'):
continue

# 根据字段类型去调整数值 如果为空值 依据字段类型 填上默认值
if cell_val_type == 'string':
if cell.ctype == 0:
v = '\'\''
else:
v = '\'%s\'' % (str(cell.value))
elif cell_val_type == 'int':
if cell.ctype == 0:
v = 0
else:
v = toInt(cell.value)
elif cell_val_type == 'float':
if cell.ctype == 0:
v = 0
else:
v = float(cell.value)
elif cell_val_type == 'bool':
if cell.ctype == 0:
v = 'false'
else:
v = cell.value
elif cell_val_type == 'table':
if cell.ctype == 0:
v = '{}'
else:
v = cell.value
else:
v = cell.value

# 加入列表
row_data_list.append([k, v])

# 保存id 和 row data
excel_data_dict[cell_id.value] = row_data_list

ScriptableObject 编辑器

ScriptableObject:在编辑器下可以保存和存储数据在本地Assets文件下,保存的数据可以共享,可以在当前整个项目进行引用或者其他项目共享。通过将数据存储在ScriptableObject对象中来减少工程以及游戏运行时因拷贝值所造成的内存占用

Image text
根据 RPG Builder 插件的工具,学习编辑器编写时顺手写的

思路

EditorDataObj:ScriptableObject
定义一系列编辑器的属性

EditorUtils.cs
提供在编辑器中绘制相关组件的接口和百分比计算相关的接口

DataCreate.cs
处理面板的逻辑,scriptable的选择,保存和新建

1
2
3
4
5
6
7
8
9
public Texture2D DarkThemeBackground, LightThemeBackground;
public float labelFieldWidth = 100f, fieldContentWidth = 400f, filterLabelFieldWidth = 50;
public float MinEditorWidth = 1050;
public float MinEditorHeight = 550;
public float CategoriesY;
public float actionButtonsY;
public float ModuleButtonsY = 36;
public float CategoryWidthPercent, CategoryWidthPercentHover, SubCategoryWidthPercent, SubCategoryWidthPercentHover, ElementListWidthPercent, ViewWidthPercent, FilterWidthPercent, TopBarHeightPercent;
public float viewSmallFieldHeight, smallButtonHeight;
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
public static void StartHorizontalMargin(float space, bool beginVertical)
{
EditorGUILayout.BeginHorizontal();
GUILayout.Space(space);
if (beginVertical) EditorGUILayout.BeginVertical();
}
public static void EndHorizontalMargin(float space, bool endVertical)
{
if (endVertical) EditorGUILayout.EndVertical();
GUILayout.Space(space);
EditorGUILayout.EndHorizontal();
}

public static Sprite DrawIconField(Sprite icon, float iconSize)
{
return (Sprite)EditorGUILayout.ObjectField(icon, typeof(Sprite), false, GUILayout.Width(iconSize), GUILayout.Height(iconSize));
}
public static void DrawIDField(float smallFieldHeight, float fieldWidth, int id,EditorDataObj editorDataObj)
{
fieldWidth *= 2;
EditorGUILayout.BeginHorizontal();
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.LabelField("ID:", GUILayout.Width(editorDataObj.labelFieldWidth), GUILayout.Height(smallFieldHeight));
EditorGUILayout.IntField(id, GUILayout.Height(smallFieldHeight));
EditorGUI.EndDisabledGroup();
EditorGUILayout.EndHorizontal();
}

public static string DrawHorizontalTextField(string labelName, string tooltip, float smallFieldHeight, string content, EditorDataObj editorDataObj)
{
GUILayout.BeginHorizontal();
if (!string.IsNullOrEmpty(labelName)) EditorGUILayout.LabelField(new GUIContent(labelName, tooltip), GUILayout.Width(editorDataObj.labelFieldWidth), GUILayout.Height(smallFieldHeight));
content = EditorGUILayout.TextField(content, GUILayout.Height(smallFieldHeight));
GUILayout.EndHorizontal();
return content;
}
public static int DrawHorizontalTextField(string labelName, string tooltip, float smallFieldHeight, int content, EditorDataObj editorDataObj)
{
GUILayout.BeginHorizontal();
if (!string.IsNullOrEmpty(labelName)) EditorGUILayout.LabelField(new GUIContent(labelName, tooltip), GUILayout.Width(editorDataObj.labelFieldWidth), GUILayout.Height(smallFieldHeight));
content = EditorGUILayout.IntField(content, GUILayout.Height(smallFieldHeight));
GUILayout.EndHorizontal();
return content;
}
public static float DrawHorizontalTextField(string labelName, string tooltip, float smallFieldHeight, float content, EditorDataObj editorDataObj)
{
GUILayout.BeginHorizontal();
if (!string.IsNullOrEmpty(labelName)) EditorGUILayout.LabelField(new GUIContent(labelName, tooltip), GUILayout.Width(editorDataObj.labelFieldWidth), GUILayout.Height(smallFieldHeight));
content = EditorGUILayout.FloatField(content, GUILayout.Height(smallFieldHeight));
GUILayout.EndHorizontal();
return content;
}
public static bool DrawHorizontalToggle(string labelName, string tooltip, float smallFieldHeight, bool toggle, EditorDataObj editorDataObj)
{
GUILayout.BeginHorizontal();
if (!string.IsNullOrEmpty(labelName)) EditorGUILayout.LabelField(new GUIContent(labelName, tooltip), GUILayout.Width(editorDataObj.labelFieldWidth), GUILayout.Height(smallFieldHeight));
toggle = EditorGUILayout.Toggle(toggle, GUILayout.Height(smallFieldHeight));
GUILayout.EndHorizontal();
return toggle;
}
public static string DrawFileNameField(string labelName, string tooltip, float smallFieldHeight, string content, EditorDataObj editorDataObj)
{
EditorGUILayout.BeginHorizontal();
EditorGUI.BeginDisabledGroup(true);
if (!string.IsNullOrEmpty(labelName)) EditorGUILayout.LabelField(new GUIContent(labelName, tooltip), GUILayout.Width(editorDataObj.labelFieldWidth), GUILayout.Height(smallFieldHeight));
content = EditorGUILayout.TextField(content, GUILayout.Height(smallFieldHeight));
EditorGUI.EndDisabledGroup();
EditorGUILayout.EndHorizontal();
return content;
}

public static string DrawHorizontalTextField(string labelName, string tooltip, float smallFieldHeight, float width, string content, EditorDataObj editorDataObj)
{
GUILayout.BeginHorizontal();
if (!string.IsNullOrEmpty(labelName)) EditorGUILayout.LabelField(new GUIContent(labelName, tooltip), GUILayout.Width(width), GUILayout.Height(smallFieldHeight));
content = EditorGUILayout.TextField(content, GUILayout.Height(smallFieldHeight));
GUILayout.EndHorizontal();
return content;
}