架构解析

Revolution 导表工具 · 架构解析

配套阅读:《导表工具对比与设计说明》(讲"为什么这么设计")

本篇只讲"为什么":为什么这么设计、为什么这么写代码、每个决策换来了什么、代价是什么、边界画在哪。
想学怎么用 → 看同目录的《使用说明》,那里是手把手,本篇不重复。
面向小白:用大白话讲"不这么做会怎样",不复述代码。

源设计起源 · 为什么要做这个模块

一句话 策划把数值配在 Excel 里,程序没法直接读 Excel —— 这个工具就是"Excel → 代码 + 数据"的中转站:策划写完表、导出一次,程序一行代码把表读进来。

更完整的"早期版本 vs 新版 / 为什么重写"论证,见同目录《导表工具对比与设计说明》。

本篇只讲为什么;怎么用见同目录《使用说明》。

0三句话速览(完整用法见《使用说明》)

① 写 Excel:第 1 行字段名 / 第 2 行类型 / 第 3 行描述 / 第 4 行起数据(第一个字段是主键)
② 启动工具 → 选 Excel → 读取 → 选三个输出目录 → 开始导出
③ 把 Code\ 拷到 Assets/Revolution/Generation\,把 Data\ 拷到 「资源根目录」\Data\(如 Assets/GameRes/Data)
④ 业务代码:
     await RevDataTableManager.LoadAsync<HeroTable>();
     HeroTable.Instance.FindByKey(1001, out var h);

1准备

1.1 运行环境

1.2 启动方式

# 方式一:命令行(开发期常用)
cd RevolutionFramework\Revolution.ExcelTool
dotnet run

# 方式二:直接跑 exe(打包给策划/TA 用)
Revolution.ExcelTool\bin\Debug\net8.0-windows\Revolution.ExcelTool.exe
发布给别人用:dotnet publish -c Release,把产物目录整个拷走即可(零第三方依赖,只有一个 exe + dll)。

1.3 界面长什么样

Revolution 导表工具
① Excel 数据源 [路径输入框] [选择文件] [选择目录] [读取]
② 表列表    ③ 表结构预览(字段摘要 + 前 50 行数据)
④ 输出路径   数据结构类 / 容器类 / TXT 数据文件
⑤ [仅生成数据] [全量生成(代码+数据)]  状态提示
⑥ 日志     每一步的结果都在这里,成功失败都看得见

首次启动会自动猜你的 Unity 工程路径(在工具目录附近找 RevolutionFrameWork_Unity),猜到了就预填好三个输出目录;猜不到也不影响使用,手动选一下即可。

2Excel 该怎么写

2.1 四行结构(这是唯一的契约)

行内容说明
第 1 行字段名要能当 C# 字段名(见第五章)
第 2 行字段类型只支持 int / float / string / bool
第 3 行字段描述只生成代码注释和 txt 表头,不参与数据
第 4 行起数据一行一条记录

2.2 一个完整示例(可以直接抄)

工作表名:Hero

ABCDEFGHI
1IDNameQualityAttackHpMoveSpeedIsRangedCampDesc
2intstringintfloatintfloatboolstringstring
3英雄ID英雄名品质(1-5)攻击力生命值移动速度是否远程所属阵营英雄描述
41001亚瑟3120.536003.8false秩序持剑的圣骑士
51002妲己39532003.6true无序魅惑的狐妖法师
61003后羿4160.831003.7true秩序射日的神射手

工作表名:Buff(演示 string 主键)

ABCDEF
1IDNameTypeDurationStackableDesc
2stringstringintfloatboolstring
3状态ID状态名类型持续(秒)可否叠加状态描述
4BUFF_ATK_UP攻击提升15false攻击力提升 20%
5DEBUFF_STUN眩晕31.5false无法移动与释放技能
完整可运行的样例见 Revolution.Demo\ExcelTool\Excel\DemoConfig.xlsx(4 张表:Hero / Item / Skill / Buff)。

2.3 列的处理规则

情况行为
第 1 行该列留空整列被忽略(Excel 右侧常见的空列,不用管)
列名以 # 开头视为策划备注列,整列忽略(可以写"策划:这列别删")
列数不一致以第 1 行的字段数为准;某行少填的列按空值处理

2.4 行的处理规则

情况行为
整行为空跳过
该行第一列以 # 开头视为注释行,整行跳过(可以在表尾写备注)
主键列为空报错(主键必填)
主键重复报错

3字段类型与取值规则

3.1 四种类型

Excel 里写生成的 C# 类型运行时解析器
intintRevDataFieldParser.ToInt
floatfloatRevDataFieldParser.ToFloat
stringstringRevDataFieldParser.ToStr
boolboolRevDataFieldParser.ToBool

大小写不敏感、前后空格会被去掉(Int、 int 都认)。

3.2 容错与默认值

类型空单元格 / 解析失败说明
int0用 InvariantCulture 解析
float0f同上(系统小数点是逗号也不会读错)
boolfalse见下表
string""null 统一成空串,省得业务到处判空

bool 认这些写法(不区分大小写):

真假
true / 1 / 是 / yes / yfalse / 0 / 否 / no / n
为什么这么宽?策划表里这几种写法都会出现。与其让策划改表,不如读取端宽容一点。
注意:int 列填了 abc 会在导表时报错(第 5 行 [Cost]:"abc" 不是合法的 int),不会被静默变成 0。

4主键规则

规则说明
谁是主键第一个字段(第一列),不需要额外标记
类型int / string 都行 → 分别生成 RevDataTable<int, T> / RevDataTable<string, T>
必填主键列为空 → 报错
唯一主键重复 → 报错(导出期就拦截,不留到运行时)
类型体检用 float 或 bool 做主键会给出警告(浮点精度会导致"表里明明有却查不到";bool 最多两行数据)
⚠️ 别踩主键值不要以 # 开头(会被当成注释行整行跳过)

字符串主键的典型用法(业务代号做主键):

BUFF_ATK_UP / DEBUFF_STUN / ITEM_SWORD_01

运行时就是:

BuffTable.Instance.FindByKey("BUFF_ATK_UP", out Buff buff);

5命名规则

5.1 字段名 → C# 字段名

Excel 里的字段名生成的 C# 字段规则
IDId长度 ≤ 3 且全大写 → 首字母大写、其余小写
HPHp同上
NameName原样(首字母已是大写)
moveSpeedMoveSpeed首字母改大写
英雄名英雄名汉字是合法 C# 标识符,允许
英雄 id报错有空格 → 不能作为标识符
2Name报错数字开头

5.2 表名(工作表名)→ 类名

工作表名 "Hero"      → 结构体 Hero      + 容器 HeroTable     + 数据文件 Hero.txt
工作表名 "Buff"      → 结构体 Buff      + 容器 BuffTable     + 数据文件 Buff.txt
工作表名 "HeroSkin"  → 结构体 HeroSkin  + 容器 HeroSkinTable + 数据文件 HeroSkin.txt
表名建议只用一个英文单词(首字母大写最好)。表名只做"首字母大写"处理,不做驼峰转换: my_hero 会生成 My_hero 这种不好看的类名和文件名。

6界面操作流程(六步)

7输出物详解

7.1 RevDataStructures.cs(数据结构类)

namespace Revolution
{
    /// <summary>Hero —— 配置表的一条数据(对应 Excel 一行;struct:值类型,零 GC)</summary>
    [Serializable]
    public struct Hero
    {
        /// <summary>英雄ID(★ 主键)</summary>
        public int Id;

        /// <summary>英雄名</summary>
        public string Name;
        // ...
    }
}

7.2 RevDataTables.cs(容器类)

namespace Revolution
{
    /// <summary>Hero 表的容器(主键:ID,9 个字段,5 条数据)</summary>
    public sealed class HeroTable : RevDataTable<int, Hero>
    {
        /// <summary>便捷访问;未加载时为 null(先 RevDataTableManager.LoadAsync<HeroTable>())</summary>
        public static HeroTable Instance => RevDataTableManager.Get<HeroTable>();

        public HeroTable() : base("Hero") { }

        protected override int GetKey(in Hero data) => data.Id;

        protected override bool ParseRow(string[] cells, out Hero data)
        {
            data = default;
            if (cells.Length < 9) return false;      // 列数不够 → 数据文件与结构不匹配

            data.Id = RevDataFieldParser.ToInt(cells[0]);      // 英雄ID
            data.Name = RevDataFieldParser.ToStr(cells[1]);    // 英雄名
            // ...
            return true;
        }
    }
}
数据文件的位置是怎么定的:容器基类把"路径"拆成两段(和资源系统 RevResManager 的参数口径完全一致)—— ResourceRoot 默认 "Data/"(也就是生成的 RevResPath.Data), ResourceName 默认 = 表名。所以 base("Hero") 等价于 ("Data/", "Hero")。 要把某张表放到别的目录就显式写:public HeroTable() : base("Hero", "Data/Activity/", "Hero") { }

每张表只生成两个方法(GetKey / ParseRow),查询能力全部继承自运行时基类 RevDataTable<TKey, TData>:

能力用法
按主键查FindByKey(key, out data) / TryGet(key, out data) / Get(key)
按下标查GetByIndex(index)
遍历Count + GetByIndex 或 All
判存在ContainsKey(key)

一张表生成代码只有二十来行,而且改工具重新生成时不会动到数据结构文件(两个文件分开的用意)。

7.3 TXT 数据文件

以 Buff.txt 为例:

#Buff
#ID	Name	Type	Duration	Stackable	Desc
#状态ID	状态名	类型	持续(秒)	可否叠加	状态描述
BUFF_ATK_UP	攻击提升	1	5	false	攻击力提升 20%
DEBUFF_STUN	眩晕	3	1.5	false	无法移动与释放技能

格式规范:

规则说明
一行一条记录字段之间用 \t(制表符) 分隔
# 开头整行是注释运行时整行跳过(表名 / 字段名 / 描述都放这里,方便肉眼核对与 git diff)
空行跳过—
行分隔符写文件统一用 \n,读的时候兼容 \r\n
编码UTF-8 不带 BOM(带 BOM 会让文件首字符变成 \uFEFF,注释行会失效)

转义规则(字符串里出现这些字符时会被写成两个字符,运行时还原):

原字符文件里写成
\\\
制表符\t
回车\r
换行\n
这就是为什么"字段名注释行里有制表符、数据行里也有制表符"不会互相干扰 —— 数据里的制表符是被转义过的。

空白单元格:保留为空,运行时取该类型的默认值:

3004	女王崇拜	1002	35		true	连续三次冲击...   ← Damage 列空着 → 运行时读到 0f

为什么用 txt 而不是 json / 二进制:可读、可 diff、可手工核对(详见对比文档第三章)。

8接入 Unity

8.1 两个输出目录怎么选

Revolution.Demo\ExcelTool\Code\   →   Assets\Revolution\Generation\   ← 生成代码(asmdef 已引用 Revolution.Runtime)
Revolution.Demo\ExcelTool\Data\   →   Assets\GameRes\Data\             ← 数据文件(逻辑路径正好是 Data\<表名>)

导出后回 Unity,会自动刷新并编译(日志里也会提示)。

8.2 运行时加载(三行)

// ① 加载(走资源系统:编辑器直读工程、真机走 AB)
await RevDataTableManager.LoadAsync<HeroTable>();

// ② 查询
if (HeroTable.Instance.FindByKey(1001, out Hero hero))
    Debug.Log($"{hero.Name} 攻击力 {hero.Attack} 是否远程 {hero.IsRanged}");

// ③ 用完卸载(归还资源引用)
RevDataTableManager.Unload<HeroTable>();

字符串主键 / 字符串表名同样支持:

BuffTable.Instance.FindByKey("BUFF_ATK_UP", out Buff buff);   // 字符串主键
RevDataTableManager.Get("Buff");                                  // 按表名取表
RevDataTableManager.TryGet<BuffTable>("Buff", out BuffTable tbl);  // 按表名取具体容器

8.3 走 AB 模式:数据表直接就能进包

数据文件是 .txt,而打包工具的排除后缀 excludeExtensions 默认只剩 .cs / .meta —— 所以数据表会正常进 ResMap,不需要任何额外设置。

⚠️ 但反过来要注意:如果你手动把 .txt 加进排除列表,数据表就会被静默踢出包:

不会进 ResMap → RevABResPolicy 查不到 → PathNotMapped
好在校验器会直接告警(有 N 个资源被 excludeExtensions 排除,不会进包), 看到那条就去把 .txt 删掉。
编辑器直读模式下不受影响(RevEditorResPolicy 是按路径直接读工程的)。

详见《资源加载系统架构解析》第四章「打包注意事项」。

9报错对照表

导出的报错都带行号(和 Excel 左上角行号一致),直接指给策划看:

报错信息原因怎么改
表 [X] 行数不足 3 行缺表头补全"字段名 / 类型 / 描述"三行
表 [X] 一个有效字段都没有第 1 行全空填字段名
字段 [X] 的类型 "xxx" 不支持类型写了别的改成 int / float / string / bool
字段名 [X] 重复两列同名改名
字段名 [X] 不能作为 C# 标识符有空格/标点/数字开头改名(汉字可以)
第 N 行:主键 [X] 为空主键列没填填上
第 N 行:主键 [X] = v 重复主键重复改值或删行
第 N 行 [X]:"abc" 不是合法的 int类型与数据不符改数据或改类型
表 [X] 用 float 做主键不推荐警告建议改 int / string
表 [X] 用 bool 做主键警告基本是配错了,确认一下
表 [X] 校验未通过,已跳过导出(N 个错误)该表有错按上面逐条修;其它表照常导出
所有表都未通过校验,没有任何可导出的表全军覆没同上
存在同名表 [X](来自 Y)不同文件里出现同名工作表改工作表名(会生成重复类名,编译不过)
未指定XX目录输出路径为空在④里选目录
创建XX目录失败:...权限 / 路径非法换一个可写目录
[错误] 读取 X 失败:...文件损坏或不是 xlsx用 Excel 打开后"另存为 .xlsx"

10局限与边界(用之前先知道)

  1. 只支持 .xlsx(Excel 2007+)。老的 .xls 是 OLE 复合文档,不是 zip,读不了 —— 用 Excel 另存为 xlsx 即可。
  2. 不处理图表、批注、条件格式、合并单元格;公式取其缓存值(Excel 保存时会写入计算结果,够导表用)。
  3. 只支持 4 种类型(int / float / string / bool),不支持数组、嵌套结构、枚举。
  4. 只支持单主键,且必须是第一个字段。
  5. 每次导出会整体重写两个代码文件(表很多时会变大,并触发 Unity 重编译)。
  6. 没有 schema 版本号:靠"导表同时覆盖代码与数据"保证一致,别只拷其中一个。
  7. 坏表会被跳过(而非中止),所以导出后一定看一眼日志有没有 [提示],别以为"没报红色就全导了"。

11附:文件索引

文件职责
Revolution.ExcelTool\Core\XlsxReader.csxlsx 解析(zip + XML)
Revolution.ExcelTool\Core\ExcelTable.cs表模型 + 配置规则校验
Revolution.ExcelTool\Core\CodeGenerator.cs生成数据结构类 + 容器类
Revolution.ExcelTool\Core\DataTextWriter.cs生成 TXT 数据文件
Revolution.ExcelTool\Core\ExportService.cs导出编排(过滤 / 重名检查 / 编码 / 落盘)
Revolution.ExcelTool\MainWindow.xaml(.cs)界面
Runtime\RevDataLoad\Core\RevDataTable.cs容器基类(字典 + 顺序表 + 查询 API)
Runtime\RevDataLoad\Core\RevDataTextFormat.csTXT 格式(★ 与工具共用同一份源码)
Runtime\RevDataLoad\Core\RevDataFieldParser.cs字段取值(容错 + InvariantCulture)
Runtime\RevDataLoad\Manager\RevDataTableManager.cs运行时入口(加载 / 查询 / 卸载)
Revolution.Demo\ExcelTool\可直接使用的样例(Excel + 生成产物)