把策划维护的 Excel 配置表读进来,转成统一的中间格式,然后一方面导出成各引擎能用的代码,另一方面让 AI 能直接帮你查表、填表、校验。
这个项目结合了两类工具的定位:
- 类似 GameConfig:把 Excel 导出成程序可用数据(多语言导出、表继承、多主键、增量发布)
- 类似 gamedesign-skills:让 AI 理解项目,帮你设计、填表、查表、校验
整个工具是三层结构,数据单向流动:
┌──────────────────────────────────────────┐
│ ① 表解析层(core/parser.ts) │
│ Excel → 统一中间格式(schema + rows) │
│ 支持横表 / 纵表 / 继承 / 多主键 │
└──────────────────┬───────────────────────┘
│ 中间格式(内存)
┌─────────────┴─────────────┐
▼ ▼
┌────────────┐ ┌────────────────┐
│ ② 导出层 │ │ ③ AI 层 │
│ adapters/ │ │ query/validate │
│ 中间格式 → │ │ /fill CLI │
│ 各语言代码 │ │ (供 AI 调用) │
└────────────┘ └────────────────┘
第一层和第二层是纯代码,不需要 AI,跑一次就出结果。第三层依赖第一层产出的数据,AI 不用读原始 Excel,读中间格式更快更准。
| 语言 ID | 别名 | 输出目录 | 输出内容 |
|---|---|---|---|
| typescript | ts | output/typescript/ | 接口 + 常量数组(.ts) |
| csharp | cs | output/csharp/ | 类 + 静态数据(.cs) |
| gdscript | gd | output/gdscript/ | 类 + 常量表(.gd) |
| lua | — | output/lua/ | 以主键为索引的表(.lua) |
| json | — | output/json/ | 通用 JSON |
| tres | godotres | output/tres/ | Godot Resource(.gd + .tres) |
| 引擎 | 默认语言 | 说明 |
|---|---|---|
| unity | csharp | Unity 用 C# |
| cocos | typescript | Cocos Creator 用 TypeScript |
| godot | gdscript | Godot 用 GDScript |
--engine 会自动选默认语言。--engine 和 --lang 同时传时以 --lang 为准,比如 --engine godot --lang tres 就能导出 Godot Resource 格式。
| schema 类型 | TypeScript | C# | GDScript | Lua |
|---|---|---|---|---|
| int | number | int | int | number |
| float | number | float | float | number |
| string | string | string | String | string |
| bool | boolean | bool | bool | boolean |
| enum | 字符串联合类型 | string | String | string |
| int[] / ref[] | number[] | int[] | Array[int] | table |
| float[] | number[] | float[] | Array[float] | table |
| string[] | string[] | string[] | Array[String] | table |
| ref | number | int | int | number |
| object | Record<string, unknown> | string(JSON) | Dictionary | table |
- Node.js 18 或以上
- 配置表是 .xlsx 格式
不依赖 Windows,不依赖 Python,Mac、Linux、Windows 都能跑。
cd game-design-tools
npm i依赖就一个 exceljs,用来读写 Excel。tsx 和 typescript 是开发依赖,用来跑和类型检查。
# 生成一份示例数据,写到 knowledge/gamedata/game.xlsx
npm run sample
# 看看有哪些表
npm run list
# 导出到不同引擎 / 语言
npm run export -- --engine unity # C#,输出到 output/csharp
npm run export -- --engine cocos # TypeScript,输出到 output/typescript
npm run export -- --engine godot # GDScript,输出到 output/gdscript
npm run export -- --lang lua # Lua
npm run export -- --lang json # JSON
npm run export -- --lang tres # Godot Resource导出后把 output 对应目录下的文件拷进引擎工程就行。
所有命令都通过 npm script 调用,传参数时在命令后加 --,比如 npm run query -- --table Hero。
npm run list输出 JSON,每个表包含 name(表名)、comment(注释)、count(行数)。
npm run query -- --table Hero # 查整张表
npm run query -- --table Hero --key 1001 # 按主键查一行
npm run query -- --table SkillLevel --key skillId=1,level=2 # 复合主键
npm run query -- --refs Buff # 反查谁引用了 Buff| 选项 | 说明 |
|---|---|
| --table <表名> | 要查询的表,必填(除非用 --refs) |
| --key <值> | 主键值。单主键传标量;复合主键传 k=v,k=v 或 JSON 对象 |
| --refs <表名> | 反查引用关系,替代 --table |
结果都是 JSON 输出,方便 AI 或脚本直接读。
npm run validate检查三类问题:
- 主键空值或重复,复合主键按整个元组判重
- 引用完整性,ref 和 ref[] 指向的表要存在,值要能命中目标表主键
- 枚举合法性,enum 字段的值要在 enumValues 范围内
问题分 error 和 warning 两级,没问题就输出"校验通过"。
先写一个 changes.json:
[
{ "table": "Hero", "key": 1001, "fields": { "attack": 1200 } },
{ "table": "Hero", "key": 1005, "fields": { "id": 1005, "name": "新英雄", "attack": 500 } },
{ "table": "SkillLevel", "key": { "skillId": 1, "level": 2 }, "fields": { "damage": 250 } }
]npm run fill -- --input changes.json每条变更的字段:
| 字段 | 说明 |
|---|---|
| table | 目标表名 |
| key | 主键值,用来定位行。单主键传标量,复合主键传对象 |
| fields | 要写入的字段和新值 |
规则是 key 能匹配到现有行就更新那行,匹配不到就追加新行,新行会自动补上主键字段。
数组字段在 JSON 里直接写数组就行,工具会自动按 arraySeparator 拼回 Excel 字符串。object 字段写对象,自动 JSON 序列化。
npm run export -- --engine unity
npm run export -- --lang tres
npm run export -- --engine unity --incremental true # 增量,只导改过的表
npm run export -- --engine unity --out output/myconfig --namespace MyGame| 选项 | 说明 |
|---|---|
| --lang | 语言,ts / cs / gd / lua / json / tres |
| --engine | 引擎,unity / cocos / godot,自动选默认语言 |
| --out <目录> | 输出目录,默认 output/语言名 |
| --incremental true | 增量发布 |
| --namespace | C# 命名空间,默认 GameConfig |
所有命令都支持:
| 选项 | 说明 | 默认值 |
|---|---|---|
| --schema <路径> | schema 配置文件路径 | config/schema.json |
| --data <目录> | Excel 数据目录 | knowledge/gamedata |
首次导出会在输出目录写一个 .manifest.json,记录每张表的内容哈希(SHA-256)。之后每次导出:
- 哈希没变的表跳过,不重写文件
- 哈希变了的表重新生成
- 已经从 schema 删掉的表,清理它的旧输出文件
适合接 CI 或热更流程,只推送真正变动的数据。
分三步。
编辑 config/schema.json,为每张表声明字段。结构如下:
把 Excel 放进 knowledge/gamedata。一个 sheet 就是一张表,sheet 名要和 schema 的 name 一致,第一行是表头(字段名),第二行起是数据。没在 schema 里声明的列会被忽略。
npm run validate # 先校验,确认没有引用或主键错误
npm run export -- --engine unity # 再导出| 字段 | 必填 | 说明 |
|---|---|---|
| name | 是 | 表名,和 Excel sheet 名一致 |
| fields | 是 | 字段定义数组 |
| primaryKey | 否 | 主键字段名数组,默认 ["id"] |
| comment | 否 | 表注释 |
| layout | 否 | horizontal(横表,默认)或 vertical(纵表) |
| extends | 否 | 父表名,启用表继承 |
| 字段 | 必填 | 说明 |
|---|---|---|
| name | 是 | 字段名,和 Excel 表头一致 |
| type | 是 | 类型,见下面字段类型表 |
| comment | 否 | 字段注释 |
| enumValues | enum 时 | 枚举允许的值列表 |
| refTable | ref / ref[] 时 | 引用的目标表名 |
| elementType | 数组时 | 数组元素类型 int / float / string |
| defaultValue | 否 | 空单元格的默认值 |
| nullable | 否 | 是否允许为空 |
| 类型 | 说明 | Excel 里的样子 |
|---|---|---|
| int | 整数 | 120 |
| float | 浮点数 | 0.05 |
| string | 字符串 | 亚瑟 |
| bool | 布尔,true/1/是/yes 都是真 | 0 或 1 |
| enum | 枚举,要配 enumValues | tank |
| int[] | 整数数组 | 1|2|3 |
| float[] | 浮点数组 | 0.5|1.0 |
| string[] | 字符串数组 | 战士|近战 |
| ref | 引用一条记录,要配 refTable | 101 |
| ref[] | 引用多条记录 | 101|102 |
| object | JSON 对象 | {"atk":120,"crit":0.15} |
数组默认用竖线分隔,可以在 schema 顶层改 arraySeparator。ref 和 ref[] 会在 validate 时做引用完整性检查。object 在 Excel 里存 JSON 字符串,导出到 C# 也是 JSON 字符串,GDScript 里则是 Dictionary。
横表(默认)第一行是字段名表头,每行一条记录:
| id | name | attack |
| 1001 | 亚瑟 | 120 |
| 1002 | 艾琳 | 800 |
纵表(layout 设为 vertical)第一列是字段名,每列一条记录:
| id | 1001 | 1002 |
| name | 亚瑟 | 艾琳 |
| attack | 120 | 800 |
空单元格取 defaultValue(如果声明了),否则是 null。整行全空的行会被跳过。
子表声明 extends 指向父表,就自动合并父表的所有字段,子表同名字段优先。子表的 Excel 里要包含父表的字段列加上自己的字段列。
{
"name": "EliteHero",
"extends": "Hero",
"primaryKey": ["id"],
"fields": [ { "name": "star", "type": "int" } ]
}导出时 C# 和 TypeScript 会生成真正的继承(class EliteHero : Hero、interface EliteHero extends Hero),父表字段不重复声明。GDScript 和 Lua 会把字段直接展平。
primaryKey 可以写多个字段,比如 ["skillId", "level"]。各命令对复合主键的处理:
- query 用
--key skillId=1,level=2,按全部主键字段匹配 - validate 按整个元组判重、查空
- fill 的 key 传对象 {"skillId":1,"level":2},按全部主键定位行
- Lua 导出用下划线拼主键做索引,比如 SkillLevel["1_2"]
Windows 的 PowerShell 会丢掉参数里的双引号,所以复合主键建议用 k=v,k=v 这种写法,别用 JSON 对象。
--lang tres 每张表生成三个文件:
Hero.gd 行资源类,extends Resource,@export 字段
HeroTable.gd 容器类,@export var items: Array[Hero]
HeroTable.tres 数据资源,sub_resource 逐行加顶层 items
把整个输出目录放进 Godot 项目的 res:// 下就能加载。
项目里带了一个 opencode 技能,位置在 .opencode/skills/gamedesign-tools/SKILL.md。重启 opencode 后,在项目里直接用自然语言说需求就行,比如:
- 把英雄 1001 的攻击改成 1200
- 查一下 Skill 表
- 校验配置有没有引用错误
- 导出成 Unity 代码
AI 会按技能里写好的流程去跑 query、fill、validate、export。技能文档里写清楚了所有命令和填表工作流,AI 会自动遵循。
game-design-tools/
│
├── config/
│ └── schema.json 表结构定义,接入项目主要改这里
│
├── knowledge/
│ └── gamedata/ 📥 输入:配置表 Excel,每 sheet 一张表
│
├── output/ 📤 输出:导出的代码,按语言分目录
│
├── src/
│ ├── index.ts CLI 入口,解析参数分发命令
│ │
│ ├── core/ ⚙️ 核心逻辑,跟语言无关
│ │ ├── types.ts 中间格式类型定义
│ │ ├── schema.ts 加载 schema、解析继承
│ │ ├── values.ts 类型转换(Excel→值、值→Excel)
│ │ ├── parser.ts Excel 解析成中间格式
│ │ ├── database.ts 扫描目录、构建配置库
│ │ ├── exporter.ts 导出编排、增量发布
│ │ ├── hash.ts 表内容哈希
│ │ ├── query.ts 查询
│ │ ├── validate.ts 校验
│ │ └── fill.ts 回写 Excel
│ │
│ └── adapters/ 🧩 各语言适配器,加语言就加文件
│ ├── index.ts 适配器注册、引擎默认语言
│ ├── helpers.ts 公共工具
│ ├── typescript.ts
│ ├── csharp.ts
│ ├── gdscript.ts
│ ├── lua.ts
│ ├── json.ts
│ └── tres.ts
│
├── scripts/
│ ├── make-sample.ts 生成示例 Excel
│ └── test.ts 端到端测试
│
└── .opencode/skills/
└── gamedesign-tools/SKILL.md AI 技能
数据流全景:
config/schema.json ─┐
├─→ database.ts(加载+继承)→ 中间格式 ConfigDatabase
knowledge/gamedata/*.xlsx ┘ │
┌────────────────────────────┬──────────────┘
▼ ▼
exporter.ts(导出) query/validate/fill(查询/校验/回写)
│ │
▼ ▼
adapters/* 各语言代码 JSON 输出 / 写回 Excel
在 src/adapters 下新建一个文件,实现 LanguageAdapter 接口,然后在 index.ts 里注册就行。接口定义在 src/core/types.ts,参考 json.ts(最简单)或 typescript.ts(较完整)写。
import type { LanguageAdapter } from '../core/types';
export const myLangAdapter: LanguageAdapter = {
id: 'mylang', // 唯一 ID,也是输出目录名
name: 'My Language',
extension: '.my',
mapType(field) { /* 字段类型转语言类型 */ return ''; },
mapValue(value, field) { /* 值转语言字面量 */ return ''; },
generateTable(table, options) { /* 生成文件数组 */ return []; },
};npm run test测试会临时生成一个 Excel,覆盖表继承、复合主键、引用校验、增量导出、回写这些场景。
object 字段导出到 C# 是 JSON 字符串,因为 C# 没有通用动态类型,运行时自己反序列化。要强类型就单独建表用 ref 引用。
不支持 .xls 旧格式,只支持 .xlsx,旧文件在 Excel 里另存一下。
schema 不能省略,它是字段类型、引用、主键的唯一来源,AI 也靠它理解项目。
{ "arraySeparator": "|", // 数组分隔符,默认 | "tables": [ { "name": "Hero", // 表名,要和 Excel sheet 名一致 "comment": "英雄配置表", // 注释,会进生成代码 "primaryKey": ["id"], // 主键,可多个字段 "layout": "horizontal", // 横表(默认)或 vertical 纵表 "extends": "BaseHero", // 表继承,父表名(可选) "fields": [ { "name": "id", "type": "int", "comment": "英雄ID" }, { "name": "roleType", "type": "enum", "enumValues": ["tank", "dps"] }, { "name": "skillIds", "type": "int[]", "elementType": "int" }, { "name": "passiveBuff", "type": "ref", "refTable": "Buff" }, { "name": "isBoss", "type": "bool", "defaultValue": false } ] } ] }