一、文档范围

本说明中的“配置文件”主要指项目中以 Excel 为数据源、转换为 JSON 文本后由客户端或服务端读取的游戏静态配置,例如:
  • 角色配置
  • 地图配置
  • 传送点配置
  • 道具配置
  • 装备配置
  • 商店配置
  • 技能配置
  • Buff 配置
  • 任务配置
  • 怪物刷新配置
本说明中的“网络协议”指:
  • message.proto
  • 自动生成的 message.cs
  • 编译后的 Protocol.dll
  • 消息分发、发送和接收代码
本文不讨论 log4net.xml、App.config、Unity ProjectSettings 等运行环境配置。

二、项目中存在的两条生产链

1. 游戏配置生产链

对应目录:

2. 网络协议生产链

对应目录:
配置和协议属于两套不同的系统:
  • 配置解决“游戏中有哪些内容,以及这些内容的参数是什么”。
  • 协议解决“客户端与服务端通过网络传递什么消息,以及消息如何编码”。

三、当前项目的配置文件现状

1. 当前 Excel 配置表

Src\Data\Tables 中目前有以下 Excel:
共 15 个 Excel 配置源文件。

2. 当前生成的配置文本

Src\Data\Data 中目前有:
其中 SpawnPointDefine.txt 没有对应的 SpawnPointDefine.xlsx,说明它不是当前 Excel 转换链直接生成的普通配置。
客户端 DataManager 中提供了:
这两个函数可以把内存中的数据序列化为客户端 Data 目录下的文本文件。
但是当前项目没有找到调用这两个保存函数的代码入口,因此 SpawnPointDefine.txt 的正式编辑来源目前并不完全明确。修改它之前应先确认它是手工维护、场景工具生成,还是课程后续才会补充生成入口。

3. 当前真正处于加载状态的配置

客户端当前主动读取:
服务端当前主动读取:
其他配置虽然存在于 Excel 和 .txt 目录中,但当前加载代码大部分处于注释状态,或者没有对应的 Common.Data 类型。
因此:
配置文件存在,不代表当前程序已经在运行时使用它。
当前真正处于有效加载链路中的配置主要是:
配置
客户端加载
服务端加载
Common 类型
MapDefine
是
是
有
CharacterDefine
是
是
有
TeleporterDefine
是
是
有
SpawnPointDefine
是
否
有
SpawnRuleDefine
否
否
有
其他 Excel 配置
当前未启用或未完成
当前未启用或未完成
当前多数没有对应类型

四、配置文件各层分别负责什么

1. Excel 负责配置内容

例如:
Excel 适合人工维护,因为:
  • 内容直观。
  • 方便批量编辑。
  • 策划或开发人员不需要修改 C# 代码。
  • 容易增加数据行。
  • 可以统一管理多个游戏对象。
Excel 是配置的编辑源,不是程序直接运行时读取的文件。

2. .txt 负责运行时数据

虽然扩展名是 .txt,但内容实际上是 JSON。
例如:
程序使用:
将 JSON 文本转换成 C# 对象。
使用 JSON 的原因:
  • 比直接读取 Excel 简单。
  • 不需要在客户端和服务端引入 Excel 解析库。
  • 文件结构清晰。
  • 可以直接反序列化成字典和对象。
  • 适合客户端和服务端共同读取。

3. Common.Data 负责配置结构

例如:
JSON 是动态文本,C# 是静态类型。
反序列化时,Json.NET 会根据属性名称,将 JSON 字段写入对应的 C# 属性:
因此:
  • Excel 和 JSON 决定“有什么数据”。
  • Common.Data 决定“代码能以什么类型读取这些数据”。
如果 JSON 里有字段,但 C# 类中没有对应属性,Json.NET 默认会忽略该字段。
如果 C# 类中有属性,但 JSON 里没有对应字段,该属性通常会保持默认值:
如果字段类型不兼容,则可能在反序列化时报错。

4. DataManager 负责加载和保存

客户端:
服务端:
它们负责:
  1. 读取指定 .txt 文件。
  1. 将 JSON 转换成配置对象。
  1. 将结果保存在字典中。
  1. 供后续业务系统查询。
例如:
后续代码可以通过:
获得对应地图配置。

5. Common.dll 负责向客户端提供共享类型

Common.Data 源代码位于 Unity 项目的 Assets 目录之外,Unity不会直接编译这些源文件。
因此需要:
客户端最终使用的是:
而不是直接使用 Src\Lib\Common\Data 中的 .cs 文件。

五、只修改现有配置数据的流程

适用于:
  • 修改名称。
  • 修改描述。
  • 修改数值。
  • 修改资源名称。
  • 修改地图音乐。
  • 修改角色速度。
  • 在字段结构不变的情况下新增一条配置数据。
操作流程:
这种修改不需要:
  • 修改 Common.Data 类。
  • 修改 message.proto。
  • 重新编译 Common.dll。
  • 重新编译 Protocol.dll。
原因是配置的数据结构没有变化,代码仍然可以使用原来的属性读取新值。
可以记成:

六、新增代码需要读取的配置字段

例如 Excel 新增:
并且代码需要使用这个字段。
完整流程:
例如 Excel 新增:
则 MapDefine.cs 中需要增加:
如果 Excel 使用:
则 C# 可以定义为:
字段名称和类型应保持一致。
底层原因是:
因此:
不需要把 MapDefine.cs 等源文件复制到 Unity。需要复制或自动更新的是编译后的 Common.dll。

七、新增一张全新的配置表

例如新增:
仅创建 Excel 不够,还需要把整个数据链补全。

操作流程

第一步:创建 Excel

创建:
定义字段、类型、Key 和数据行。

第二步:创建 Common 配置类

创建:
例如:

第三步:确认项目包含新类

当前 Common.csproj 是旧式非 SDK 项目,源文件需要出现在项目的 <Compile Include="..."> 列表中。
如果通过 Visual Studio 在项目中创建文件,通常会自动加入项目。
如果从文件系统直接创建,则需要确认 Common.csproj 中包含:
否则文件虽然存在,但不会被编译进 Common.dll。

第四步:在 DataManager 中增加容器和读取逻辑

根据需要在客户端或服务端增加:
以及:
只在真正需要这一配置的一端加载:
  • 客户端需要显示或表现数据,就加入客户端。
  • 服务端需要进行权威判定,就加入服务端。
  • 两边都需要,就两边都加入。

第五步:更新配置复制脚本

在 Excel2Json.cmd 中加入客户端或服务端复制命令:
不要默认复制到两端,应根据实际消费者决定。

第六步:运行转换并重新生成解决方案

第七步:验证

检查:
  • DungeonDefine.txt 是否生成。
  • 需要使用它的客户端或服务端目录中是否存在该文件。
  • Common.dll 是否更新。
  • DataManager 是否成功加载。
  • 业务代码是否能通过 ID 查询。
  • 缺失、重复或错误 ID 是否会得到明确错误。

八、重命名或删除配置字段

重命名字段不能只修改 Excel。
例如:
改成:
需要同步检查:
  • Excel 表头。
  • 对应 Common.Data 属性。
  • 所有读取该属性的客户端代码。
  • 所有读取该属性的服务端代码。
  • 生成后的 JSON 字段名。
  • 测试或编辑器工具。
  • 文档和调试输出。
删除字段时也必须先搜索代码引用。
否则可能出现:
  • 代码编译失败。
  • 属性始终得到默认值。
  • 旧 JSON 字段被静默忽略。
  • 客户端和服务端使用不同字段。
  • 业务逻辑在没有报错的情况下使用错误默认值。
因此,删除或重命名前应先进行全项目引用搜索。

九、配置 ID 的修改原则

配置 ID 通常会被多个系统引用,例如:
  • 数据库中的角色 MapID。
  • 其他配置表中的外键。
  • 网络协议中的对象 ID。
  • 客户端字典键。
  • 服务端字典键。
  • 存档数据。
因此,已经被使用的 ID 不应随意修改或复用。
推荐规则:
  • 可以修改名称和描述。
  • 可以修改表现资源。
  • 不要轻易修改已进入数据库或存档的 ID。
  • 删除配置时,不要马上把旧 ID 分配给另一种对象。
  • 检查 Key 和对象内部 ID 是否一致。
  • 检查引用该 ID 的其他配置是否仍然有效。
底层上,ID 相当于不同系统之间的外键。

十、当前 Excel2Json 脚本并没有同步所有配置

当前 Excel2Json.cmd 会先转换 Tables 中的 Excel,但复制清单不是完整的。

当前自动复制到客户端的配置

当前自动复制到服务端 Debug 的配置

当前脚本没有自动复制的配置

此外:
  • SpawnRuleDefine.txt 当前只复制到客户端,没有复制到服务端。
  • RideDefine.txt 当前只复制到客户端,没有复制到服务端。
  • 服务端和客户端实际都会读取 TeleporterDefine.txt,但脚本没有复制它。
  • 客户端实际会读取 SpawnPointDefine.txt,但脚本没有复制它。
当前检查时,统一生成目录、客户端目录和服务端 Debug 目录中的所有 .txt 内容仍然一致。
但这只能证明当前文件没有不同步,不能证明下一次运行脚本后仍然全部同步。
例如修改 TeleporterDefine.xlsx 后:
因此,在现有脚本没有补全以前,修改脚本复制清单之外的配置后,需要手动同步对应 .txt,并验证三个目录中的文件一致。

十一、服务端 Release 配置问题

当前脚本只复制到:
不会自动复制到:
因此:
  • 使用 Debug 服务端时,按当前脚本路径检查。
  • 使用 Release 服务端时,需要单独准备 bin\Release\Data。
  • 不要认为“重新生成 Release”会自动携带所有 Data 文件。
配置文件是运行时外部数据,不会因为 C# 编译成功就自动进入输出目录。

十二、网络协议文件分别负责什么

1. message.proto

路径:
负责定义:
  • 枚举。
  • 基础数据结构。
  • 请求消息。
  • 响应消息。
  • NetMessageRequest。
  • NetMessageResponse。
  • 字段类型。
  • Protobuf 字段编号。
例如:
其中:
字段编号是二进制协议的一部分,不只是显示顺序。

2. genproto.cmd

路径:
负责将:
转换为:

3. message.cs

这是自动生成的代码。
不要直接修改,因为下一次执行 genproto.cmd 时会被覆盖。
应修改:
然后重新生成。

4. Protocol.dll

message.cs 仍然只是 C# 源代码。
重新生成 Protocol 项目后才会得到:
Unity 客户端需要的目标文件是:

5. PackageHandler

路径:
负责:
  1. 使用 Protobuf 将 NetMessage 序列化为二进制。
  1. 在数据前增加 4 字节长度。
  1. 通过 TCP 发送。
  1. 接收后根据长度拆包。
  1. 使用 Protobuf 反序列化为 NetMessage。
  1. 将消息交给消息分发器。
数据包结构可以理解为:
增加长度字段是因为 TCP 是字节流,不保留应用层消息边界。接收方需要先知道一条消息有多长,才能处理粘包和半包。

6. MessageDistributer

路径:
负责:
  • 接收已经反序列化的 NetMessage。
  • 区分 Request 和 Response。
  • 将消息交给 MessageDispatch。
  • 根据具体消息类型调用已注册的处理函数。

7. MessageDispatch

路径:
负责把外层消息拆成具体业务消息,例如:
当前文件顶部写着:
但当前仓库里没有找到负责重新生成 MessageDispatch.cs 的脚本。
现有 genproto.cmd 只生成 message.cs,不会更新 MessageDispatch.cs。
因此,以当前仓库为准:
  • 给现有消息增加普通字段,不需要改 MessageDispatch.cs。
  • 新增一种顶层 Request 或 Response,需要让 MessageDispatch.cs 能识别它。
  • 如果没有补充对应生成器,就需要手动增加分发代码。
  • 修改 MessageDispatch.cs 后需要重新生成 Common.dll。

十三、给现有网络消息增加字段

例如给角色进入地图响应增加:
完整流程:
通常不需要修改 MessageDispatch.cs,因为消息类型仍然是原来的:
只是消息内部增加了字段。
这种情况下核心产物是:
如果 Common 代码也使用了新字段,则还应重新生成并更新:

十四、新增一种全新的网络消息

例如新增:
完整流程如下。

第一步:在 message.proto 中定义具体消息

第二步:加入外层消息容器

在 NetMessageRequest 中增加:
在 NetMessageResponse 中增加:
外层容器决定这条具体消息能否被包装进统一的 NetMessage 中。

第三步:运行协议生成

生成新的:

第四步:更新消息分发

在 MessageDispatch.cs 的 Request 分发中增加:
在 Response 分发中增加对应逻辑。
原因是:
  • Protobuf 只负责把二进制转换成对象。
  • MessageDispatch 才负责把对象交给具体业务处理函数。
  • 如果没有分发代码,消息可能已经成功收到和反序列化,但业务层收不到事件。

第五步:实现发送方

例如客户端创建:

第六步:实现接收方

例如服务端订阅:
并实现:
响应方向同理:
  • 服务端构造 DungeonEnterResponse。
  • 客户端订阅 DungeonEnterResponse。
  • 客户端处理响应。

第七步:重新生成解决方案

新增顶层协议后通常需要生成:
原因:
  • Protocol.dll 包含新消息类型。
  • Common.dll 包含新的消息分发代码。
  • GameServer.exe 包含新的服务端处理逻辑。
  • Unity 会重新编译 Assets 下的客户端业务脚本。

十五、修改协议枚举或共享数据结构

例如修改:
或者:
流程与修改现有消息字段相同:
协议枚举的数字值也属于网络契约。
不要因为显示顺序变化就随意调整已有枚举值,否则旧客户端和新服务端可能把同一个数字解释成不同含义。

十六、Protobuf 字段编号规则

1. 已发布字段不要随意改编号

例如:
不要直接改成:
字段名主要影响生成代码,字段编号决定二进制数据如何解释。

2. 新字段使用新的唯一编号

例如已有:
新增字段应使用:

3. 删除字段后不要马上复用旧编号

否则旧客户端发送的旧字段可能被新服务端解释成另一种新字段。

4. 重命名字段但保留编号

在二进制层面通常仍可能兼容,但生成的 C# 属性名称会变化,因此所有代码引用都需要更新。

5. 不要随意修改字段类型

例如:
直接改成:
可能造成协议兼容问题。
如果语义发生明显变化,通常更适合新增一个字段编号,并逐步停用旧字段。

十七、协议修改后需要复制哪些文件

只修改现有协议字段

必须更新:

新增顶层消息并修改 MessageDispatch

需要更新:
来源:
客户端目标:
不要把这些源文件复制到 Unity:
Unity 使用的是编译后的 DLL。

十八、当前项目的 DLL 自动复制

当前 Common.csproj 配置了构建后复制:
目标为:
因此,重新生成包含 Common 的解决方案后,通常会自动更新:
需要注意:
  • 如果只单独生成 Protocol 项目,Common 的构建后复制可能不会执行。
  • 如果 Common 被判断为无需重新生成,构建后命令也可能不执行。
  • 最稳妥的方法是重新生成整个 GameServer.sln,然后检查客户端 DLL。
  • .pdb 是调试符号,不是运行必需文件。
  • 覆盖 DLL 时保留 Unity 已有的 .meta 文件,不要删除后重新导入。

十九、不同修改对应的实际流程

修改内容
更新 .txt
修改 Common 配置类
修改 Proto
修改 MessageDispatch
重新编译 Common
重新编译 Protocol
修改配置值
是
否
否
否
否
否
新增同结构配置行
是
否
否
否
否
否
新增代码需要读取的配置字段
是
是
否
否
是
否
新增全新配置表
是
是
否
否
是
否
给现有协议消息增加字段
否
通常否
是
否
视 Common 使用情况
是
新增顶层 Request 或 Response
否
否
是
是
是
是
修改 Common 网络基础代码
否
否
视情况
视情况
是
视情况
修改客户端业务处理脚本
否
否
否
否
否
否
修改服务端业务处理代码
否
否
否
否
否
否,但需要重新生成 GameServer

二十、为什么配置和协议不能混为一谈

例如新增一个地图字段:
如果它只用于客户端界面或服务端本地判断:
不需要修改协议。
只有当服务端需要通过网络把它发送给客户端,并且客户端不能通过本地配置查询时,才需要加入协议。
反过来,协议中新增:
这些数据是运行时网络状态,不属于静态配置,不应该写进 Excel。
判断方式:

二十一、修改后必须验证的内容

配置修改验证

  1. Excel 已保存。
  1. Excel2Json.cmd 执行成功。
  1. 没有忽略覆盖提示。
  1. 统一生成目录中的 .txt 已更新。
  1. 客户端和服务端需要的 .txt 已同步。
  1. JSON 字段名与 C# 属性名一致。
  1. JSON 字段类型与 C# 属性类型兼容。
  1. Key 和对象 ID 唯一且一致。
  1. 引用其他配置的 ID 确实存在。
  1. 对应 DataManager 已加载该配置。
  1. 修改配置结构后,Common.dll 已更新。
  1. 服务端和客户端已经重启。

协议修改验证

  1. message.proto 语法正确。
  1. 新字段编号没有与已有字段冲突。
  1. 没有随意修改旧字段编号。
  1. genproto.cmd 执行成功。
  1. message.cs 的修改时间已经更新。
  1. Protocol.dll 已重新生成。
  1. 客户端 Protocol.dll 已更新。
  1. 新增顶层消息时,MessageDispatch 已更新。
  1. 发送端已经正确构造外层 NetMessage。
  1. 接收端已经订阅具体消息类型。
  1. 服务端业务处理代码已经重新生成。
  1. 客户端和服务端使用同一版协议。
  1. 请求和响应都经过实际联调。

二十二、常见错误判断

错误一:运行 Excel2Json 后,DLL 也会自动更新

错误。
Excel2Json.cmd 只负责:
  • Excel 转 JSON。
  • 复制配置文本。
它不会:
  • 修改 C# 类。
  • 编译 Common。
  • 编译 Protocol。
  • 生成 DLL。

错误二:Excel 中有字段,代码就能自动读取

错误。
只有 Common.Data 中存在匹配属性时,代码才能访问该字段。

错误三:修改了 message.proto,直接复制 message.cs 到 Unity

错误。
Unity需要的是:
不是 message.cs。

错误四:新增协议消息后,只运行 genproto 就完成了

错误。
还需要:
  • 把新消息加入外层 Request 或 Response。
  • 更新消息分发。
  • 实现发送端。
  • 实现接收端。
  • 重新编译。
  • 同步 DLL。
  • 联调。

错误五:配置文件存在,就说明系统已经使用它

错误。
还需要确认:
  • 有对应 Common 类型。
  • DataManager 读取了它。
  • 文件被复制到正确运行目录。
  • 业务代码查询并使用了它。

错误六:客户端和服务端都应该加载所有配置

错误。
应该根据职责加载:
  • 服务端加载权威判定需要的配置。
  • 客户端加载表现和本地查询需要的配置。
  • 两边都需要的配置才同时加载。

二十三、推荐的统一操作顺序

当不确定本次改动属于哪一类时,按以下顺序判断。

第一步:判断修改的是数据、结构还是网络消息

第二步:执行对应流程

数据修改

配置结构修改

现有协议消息增加字段

新增协议消息


二十四、最终记忆方式

完整职责可以记成: