非常感谢 luban 的作者,提供了如此好用的工具,并热心的教了我很多关于实际工作流的思路,对于我来说,这些都是很宝贵的。

初识luban

简介

就目前我的使用来说,luban可以用于excel导出配置数据、可以用于生成proto协议(并使用Bright.Net的序列化框架)、可以用于编辑器交互、可以用于编辑器数据源与运行时真实配置数据交互。是一个各方面都非常成熟的配置文件、通信协议的解决方案。

开源地址 : github

案例开源地址:github

luban高效地处理游戏开发中常见的excel、json、xml之类的数据,检查数据错误,生成c#等各种语言的代码,导出成bytes或json等多种格式。

  • 强大的数据解析和转换能力 {excel(csv,xls,xlsx), json, bson, xml, yaml, lua, unity ScriptableObject} => {binary, json, bson, xml, lua, yaml, erlang}
  • 增强的excel格式,可以简洁地配置出像简单列表、子结构、结构列表,以及任意复杂的深层次的嵌套结构。
  • 完备的类型系统,支持OOP类型继承,搭配excel、json、lua、xml等格式数据灵活优雅表达行为树、技能、剧情、副本之类复杂GamePlay数据
  • 支持生成 protobuf(schema + binary + json)、flatbuffers(schema + json)、msgpack(binary)
  • 强大的数据校验能力。ref引用检查,path资源路径检查等等
  • 支持生成c#,java,go,c++,lua,python,javascript,typescript,erlang,rust代码
  • 完善的本地化支持
  • 强大灵活的自定义能力,支持代码模板和数据模板
  • **==通用型生成和缓存工具==**。也可以用于生成协议、数据库之类的代码,甚至可以用作对象缓存服务
  • 良好支持主流引擎、全平台、主流热更新方案、主流前后端框架。支持unity、unreal、cocos2x、微信小游戏等主流引擎。工具自身跨平台,能在Win,Linux,Mac平台良好工作。详见

配置第一个Unity项目

搭建环境

1.下载.net 6.0 sdk

2.下载案例luban_examples。

3.创建Unity工程,从luban_examples\Projects的Csharp_Unity_json中复制 luban_Libs目录到Unity Assets工程下。

4.在Project Settings菜单的Player里,设置开启unsafe。

5.从luban_examples\Tools复制 Luban.ClientServer工具库MiniDesignerConfigsTemplate文件夹 到Unity工程(不能放到Assets!这个不是给Unity加载的所以其实放外面都行)下。

6.配置bat文件

从luban_examples\Projects的Csharp_Unity_json中复制 gen_code_json.bat 文件 到5中的同级目录。之后开始编辑bat文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
set WORKSPACE=.. -- unity工程目录地址

set GEN_CLIENT=%WORKSPACE%\Luban\Luban.ClientServer\Luban.ClientServer.exe -- 运行exe文件地址
set CONF_ROOT=%WORKSPACE%\Luban\MiniDesignerConfigsTemplate -- 自己的配置表的根目录地址

%GEN_CLIENT% -j cfg --^
-d %CONF_ROOT%\Defines\__root__.xml ^
--input_data_dir %CONF_ROOT%\Datas ^ -- 配置表目录地址
--output_code_dir %WORKSPACE%/Assets/Gen ^ -- 配置表输出地址
--output_data_dir ..\GenerateDatas\json ^
--gen_types code_cs_unity_json,data_json ^
-s all

pause

7.配置表

上面CONF_ROOT的地址里就是我们的工作文件夹,这里采用案例的 MiniDesignerConfigsTemplate文件夹,在里面的Datas文件夹里有已经做好excel表格案例。

8.执行bat文件并调用

调用后生成的就是json了,可以直接用。下面简单使用一下:

1
2
3
4
5
6
7
8
9
10
11
void Start()
{
Tables table = new Tables(Loader);
Item item = table.TbItem.Get(10010);
Debug.Log($"{item.Name} {item.Desc}");
}

private JSONNode Loader(string fileName)
{
return JSON.Parse(File.ReadAllText(Application.dataPath + "/../GenerateDatas/json/" + fileName + ".json"));
}

excel基本:自定义类型

从小到大:enum > bean > table。

enums

自定义枚举enum。

  • full_name,必选。枚举名。命名空间为当前module的完整命名空间(包含父module)。
  • flags,可选。是否为bit标志位类型。默认false。
  • unique,可选。枚举值是否唯一。默认true。
  • comment,可选。注释。
  • var.name,必选。枚举名。
  • var.alias,可选。别名。
  • var.value,可选。枚举值,不填则为上一个枚举项目值+1。
  • var.comment,可选。注释。
  • 可以填枚举的 枚举项名,别名 或者 相应整数值 表达这个枚举。

beans

自定义class,也就是数据表每行的结构。

  • full_name,必选。结构(类)名,包含命名空间。
  • sep,可选。分割符。该结构以复合模式填写,例如MyIntVector3包含x,y,z三个int字段,通过sep=”,”,则所有读取MyIntVector3时,都会将读入的字符串用,拆分成三个整数。
  • var.name,必选。字段名。
  • var.type,必选。类型名。
  • var.group,可选。所属分组。可以多个,以,分割,每个值必须是root.xml中定义的group中的一个;如果不填,则该字段属于所有分组。

tables

数据索引表,将excel们联系起来。只有记载在本表的文件,才会被输出。

  • define_from_file,必选。false是取已有定义(也就是类型写在beans.excel里了);true是取未有定义,也就是直接根据数据表的表头、标题等自动生成定义。
  • full_name,必选。资源字典全名(包含模块和名字),一般习惯取名TbXXX,以后调用的时候就直接table.TbXXX.Get(key);来获取到资源。
  • value_type,必选。生成的类名,也是全名,要包含namespace。如果需要读取beans表里定义好的类型,那必须与那边的 namespace.类名 完全一致。
  • input,必选。文件列表,也就是数据表的地址。
  • index,可选。就是table.TbXXX.Get(key);的key,不写默认是类型第一个字段。

数据

这个就是数据了,表的名字就是类名。

  • 前三行分别填写 字段名、字段类型、字段中文注释,后面的行就是数据了。
  • 可以根据数据表直接生成对应的类结构,并不是必须要写beans表,只要table表里设置define_from_file就好。
  • 对于枚举值,可以填枚举的 枚举项名,别名 或者 相应整数值 表达这个枚举。

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using UnityEngine;
using cfg;
using cfg.Datas;
using SimpleJSON;
using System.IO;

public class Test : MonoBehaviour
{
void Start()
{
Tables table = new Tables(Loader);
Equip equip = table.Weapon.Get(1);
Debug.Log($"{equip.Name} {equip.Color} {equip.Quality}");
}

private JSONNode Loader(string fileName)
{
return JSON.Parse(File.ReadAllText(Application.dataPath + "/../GenerateDatas/json/" + fileName + ".json"));
}
}
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
// Weapon.json
[
{
"Id": 0,
"Color": 4,
"Quality": 2,
"Name": "眼球",
"ATK": 100,
"DEF": 20,
"SPD": 80
},
{
"Id": 1,
"Color": 4,
"Quality": 2,
"Name": "次元碎片",
"ATK": 120,
"DEF": 0,
"SPD": 100
},
{
"Id": 2,
"Color": 1,
"Quality": 1,
"Name": "无限",
"ATK": 999,
"DEF": 999,
"SPD": 0
},
{
"Id": 3,
"Color": 2,
"Quality": 12,
"Name": "塔拉夏的眼球",
"ATK": 80,
"DEF": 100,
"SPD": 80
},
{
"Id": 4,
"Color": 1,
"Quality": 8,
"Name": "眼光",
"ATK": 20,
"DEF": 100,
"SPD": 200
},
{
"Id": 5,
"Color": 1,
"Quality": 8,
"Name": "NPC赌来的法杖",
"ATK": 120,
"DEF": 20,
"SPD": 20
}
]

excel进阶:List与关联外键

光上面这样是不够的,我研究了一下手册,根据自己的项目需求整理了最合适的方案来解决List和外键的连接。不得不说,luban真的好用。

List与关联外键

excel配置

生成 json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[
{
"Id": 0,
"Color": 4,
"Quality": 2,
"Name": "眼球",
"ATK": 100,
"DEF": 20,
"SPD": 80,
"Skill": [
1,
2
]
},
... // 后面略了

使用

和前面一样方法,json加载全表。使用的时候,有一个equip.Skill_Ref,这个Ref就是外键加载进来的List<Skills.SkillCfg>类型数据。

分隔符sep

list,string => (list#sep=,),string

excel进阶:使用luban生成protobuf协议并接上自己的网络库

1.准备xml

luban的root配置

__root__.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<module>
<topmodule name="proto"/>

<import name="."/>

<service name="client">
<prefix value="proto"/>
</service>

<service name="server">
<prefix value="proto"/>
</service>

<service name="all">

</service>
</module>

protobuf协议(不是最佳实践,CMD可以去掉,因为会生成协议号):

test.xml

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
<module name="test">

<enum name="CMD">
<var name="None" value="1"/>
<var name="Ping" value="2"/>
<var name="ReqLogin" value="3"/>
<var name="RspLogin" value="4"/>
</enum>

<bean name="Ping">
<var name="isOver" type="bool"/>
</bean>

<bean name="ReqLogin">
<var name="acct" type="string"/>
<var name="psd" type="string"/>
</bean>

<bean name="RspLogin">
<var name="info" type="list,LoginInfo"/>
</bean>

<bean name="LoginInfo">
<var name="lv" type="string"/>
<var name="exp" type="string"/>
<var name="money" type="string"/>
</bean>

<proto name="NetMsg">
<var name="info" type="string?"/>
<var name="cmd" type="CMD?"/>
<var name="ping" type="Ping?"/>
<var name="reqLogin" type="ReqLogin?"/>
<var name="rspLogin" type="RspLogin?"/>
</proto>


</module>

2.配置bat文件

注意这么写的话,protobuf的xml 和 root.xml 需要放一块。

1
2
3
4
5
6
7
8
9
10
11
12
13
set WORKSPACE=..

set GEN_CLIENT=%WORKSPACE%\Luban\Luban.ClientServer\Luban.ClientServer.exe
set PROTO_ROOT=%WORKSPACE%\Luban\MiniDesignerConfigsTemplate\ProtoDefines

%GEN_CLIENT% -j proto --^
-d %PROTO_ROOT%\__root__.xml ^
--output_code_dir %WORKSPACE%\Assets\Gen ^
--gen_type cs ^
--cs:use_unity_vector ^
-s all

pause

3.迁移unity

代码生成完毕后,Unity会显示缺失很多文件。把luban_examples-main\ProtoProjects\Csharp_Unity\Assets下的三个文件夹移到Unity目录下。

分别是:Bright.Core Bright.Net Bright.Serialization。

这是luban所依赖的protobuf序列化器。

4.简单实用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static byte[] ProtoSerialize<T>(T msg) where T : Protocol,new()
{
var bf = new ByteBuf();
msg.Serialize(bf);
return bf.CopyData();
}

public static T ProtoDeSerialize<T>(byte[] bytes) where T : Protocol, new()
{
var bf = new ByteBuf(bytes);
T instance = new T();
instance.Deserialize(bf);
return instance;
}

5.接上自己的网络库

我自己的kcp网络库使用了这一套东西,替换了protobuf-net。开源地址:

https://github.com/CodingCodingK/CodingK_Session

常用命令

bat命令

禁止名字优化:

–naming_convention:module none ^
–naming_convention:bean_member none ^
–naming_convention:enum_member none ^

二进制data+cs生成类型:

–gen_types code_cs_bin,data_bin ^

其他:github手册

模板

直接去luban_examples-main文件夹里拷贝bat吧!

二进制配置文件优化

原理部分

原理部分,写在另外一篇里了。