一、需求背景
在游戏开发中,策划同学经常使用 Excel 来维护游戏的各种配置数据,例如装备表、关卡表、道具表等。这些表格通常包含大量的字段和行数。作为程序,我们需要将这些配置高效地加载到游戏中,并提供类型安全、易于使用的访问接口。
手动处理 Excel 转成游戏可读的格式(如 JSON、XML)不仅繁琐,而且容易出错。(++在最近两家公司中,使用的配置要么过大,要么转换繁琐,上家公司是将所有的配置都统一转化JSON到一个压缩包中,导致首次加载配置压缩包就要加载很长时间,现在这家公司则是直接将对应的表在线导出成txt文件格式放到项目中,每次新表都要重复以上操作,不仅繁琐txt内存还大++ )
因此,一套自动化的配置导出工具链显得尤为重要。
本文介绍的这套工具链,可以将 Excel 文件经过一系列转换和压缩,最终生成**二进制 .bin**文件,并在游戏内通过代码 ConfigManager 解析成游戏可读取的内容。同时,工具还会自动为每个配置表生成两个 TypeScript 类:表名+ConfMgr(配置管理器)和 表名+Conf(配置项),极大地提升了开发效率和代码的可维护性。
使用这套工具的好处
这套工具链实现了从 Excel 到游戏内高效配置的完整自动化流程,具备以下优点:
该工具适用所有excel文件,如果需要生成其他引擎的脚本提示 只需要修改生成代码的逻辑即可
-
自动化:一键生成所有配置,减少人工操作错误。
-
类型安全:生成的 TypeScript 代码提供智能提示,避免手写字符串键。
-
高性能:二进制加载 + 内存数组存储,运行时查询速度快。
-
灵活性:支持主键和数组索引,满足各种查询需求;支持独立输出,方便分包管理。
对比同一张表转出的 二进制文件、json文件、txt文件的大小:

二、整体流程概览
工具链的核心是一个批处理文件 build.bat,它串联了四个 Python 脚本,每一步完成特定的功能:
bash
build.bat
├─ 1. ExcelToCsv.py # 将 Excel 文件转为 CSV 格式
├─ 2. dealConfig.py # 解析 CSV,生成 JS 配置文件和 TypeScript 定义
├─ 3. uglify_all_js.py # 压缩 JS 文件(去空格、混淆),生成 .min.js
└─ 4. zlibConfig.py # 使用 zlib 进一步压缩 .min.js 为二进制 .bin 文件
此外,还有一个 conf.json 配置文件,用来控制整个流程的行为(如输入输出目录、哪些表需要独立输出等)。
下面我们将逐一详解每个环节。
三、配置文件 conf.json 详解
conf.json 是整个工具链的"大脑",我们先理解它的结构:
TypeScript
{
"author": "Danny",
"inputDir": "csv/",
"outputJs": "outputJs",
"compressConf": "outputJs/config.min.js",
"outputCompressedJs": "../../ConfigTest/assets/Configs/config/config.bin",
"outputTsDir": "../../ConfigTest/assets/Script/configData/",
"otherOutputDir": [
{ "name": "GradeDefine", "outputDir": "../../ConfigTest/assets/GameBundle/config/" },
{ "name": "CardDefine", "outputDir": "../../ConfigTest/assets/HomeBundle/config/" }
],
"files": [
{ "name":"GradeDefine.csv", "keys":[["grade"]], "keys2Array":[["quality"]], "desc":"装备表" },
{ "name":"CardDefine.csv", "keys":[["cardTotal"],["card","cards"]], "keys2Array":[["card"]], "desc":"关卡表" },
{ "name":"TeamLogoDefine.csv", "keys":[["logoId"]], "keys2Array":[["logoType"]], "desc":"球队表" }
]
}
| 字段 | 含义 |
|---|---|
inputDir |
存放 CSV 文件的目录(由第一步生成) |
outputJs |
生成的 JS 文件输出目录 |
outputTsDir |
生成的 TypeScript 文件输出目录 |
otherOutputDir |
指定某些表独立输出到指定目录,用于分包或模块化 |
files |
需要处理的文件列表,每个对象包含: - name:CSV 文件名(必须与 Excel sheet 名一致) - keys:主键列表(支持复合主键),用于生成单条查询方法 - keys2Array:数组键列表,用于生成返回多条记录的方法 - desc:配置描述,会写入生成的 TS 注释中 |
特别注意:Excel 文件中的 sheet 名称会作为配置表的名字,且 CSV 文件前三行有特殊要求:
-
第1行:字段中文名(仅用于生成注释)
-
第2行:字段类型(如 int、float、string 等)
-
第3行:字段英文名(程序内使用的变量名)
四、Excel → CSV(ExcelToCsv.py)
这一步将策划维护的 Excel 文件(.xlsx 或 .xls)转换为工具链更容易解析的 CSV 格式。
核心逻辑
-
遍历指定目录下的所有 Excel 文件。
-
对于每个 Excel 文件,读取所有 sheet(忽略名称包含"sheet"的 sheet)。
-
将每个 sheet 转换为同名的 CSV 文件,保存到
csv/目录。 -
转换时保留前三行(中文名、类型、字段名),数据行从第四行开始。
-
支持按第二行的类型将数字列转为
int或float。
为什么需要这一步?
Excel 文件是二进制格式,Python 通过 openpyxl 库读取较为方便,但后续处理使用 CSV 更轻量,也便于调试查看。
五、CSV → JS + TS
这是最核心的一步,它完成以下工作:
-
读取
conf.json中的配置。 -
遍历
files列表,对每个 CSV 文件进行解析。 -
根据 CSV 的三行元数据,构建内存中的配置数据结构(
confData)。 -
根据
keys和keys2Array建立字典索引,用于快速查找。 -
根据表名是否在
otherOutputDir中,决定是独立输出为一个 JS 文件,还是合并到全局config.js中。 -
调用
tsHelper.py为每个表生成对应的 TypeScript 类(xxxConf和xxxConfMgr)。
5.1 数据结构设计
生成的 JS 文件内容格式如下:
javascript
window.gameConfData = {
"GradeDefine": {
"data": { 0: [1001, "青铜", ...], 1: [1002, "白银", ...] },
"dic_grade": { "1001": 0, "1002": 1 },
"dic_qualityArr": { "1": [0,1] } // 数组字典
},
...
}
-
data是一个数组(实际用对象模拟),每个元素是一条配置记录的数组表示(按字段顺序存储),这样可以节省内存,并且通过索引访问更快。 -
字典
dic_xxx用于快速根据某个键找到记录的索引,例如dic_grade的 key 是 grade 字段的值,value 是 data 中的索引。 -
数组字典
dic_xxxArr用于根据某个键获取一组记录的索引列表,例如根据quality字段获取所有该品质的装备索引。
5.2 TypeScript 代码生成(tsHelper.py)
工具会为每个表生成两个类,放在 outputTsDir 中,例如 GradeDefine.ts:
TypeScript
/** 装备表配置数据管理 */
export class GradeDefineConfMgr {
private static _ins: GradeDefineConfMgr;
public static get ins(): GradeDefineConfMgr{
GradeDefineConfMgr._ins = new GradeDefineConfMgr();
return GradeDefineConfMgr._ins;
}
private _confDict:{[key: number]: GradeDefineConf} ;
public data:any ;
public confList:Array<GradeDefineConf>;
private _dic_grade:any ;
private _dic_qualityArr:any ;
constructor(){
this._confDict = {};
var conf:any = (<any>window).gameConfData.GradeDefine;
this.data = conf.data ;
this._dic_grade = conf.dic_grade;
this._dic_qualityArr = conf.dic_qualityArr;
}
public getConfByGrade(grade:number): GradeDefineConf {
return this.getConf(<number>this._dic_grade[grade]);
}
public getConfsByQuality(quality:number): Array<GradeDefineConf> {
var indexs:Array<number> = this._dic_qualityArr[quality]
if(!indexs) return null;
var arr:Array<GradeDefineConf> = [];
indexs.forEach((index)=>{arr.push(this.getConf(index))});
return arr;
}
private getConf(index:number):GradeDefineConf {
if(!this._confDict[index]){
this._confDict[index] = new GradeDefineConf(this.data[index]);
}
return this._confDict[index];
}
public getConfList():Array<GradeDefineConf> {
if(!this.confList){
this.confList = new Array();
for(var id in this.data){
this.confList.push(this.getConf(parseInt(id)));
}
}
return this.confList;
}
}
/** 装备表配置数据 */
export class GradeDefineConf {
data:Array<any> ;
constructor(data:Array<any>){
this.data = data;
}
/** 等级ID */
get grade():number{ return this.data[0]; }
/** 品质 */
get quality():number{ return this.data[1]; }
// ... 其他字段
}
优点:
-
通过
ConfMgr.ins单例访问配置。 -
自动生成的
getConfByXxx方法,参数类型与 CSV 中定义的类型一致。 -
配置数据存储在数组中,通过 getter 访问,既保证了类型安全,又节省了内存(相比直接存对象)。
六、JS 压缩
生成的 JS 文件包含大量空格和长变量名,为了减少体积,我们使用 uglify-js 工具进行压缩。
-
脚本会自动查找系统中是否安装了
uglifyjs命令(全局安装)或npx uglifyjs。 -
遍历
outputJs目录下所有.js文件(排除已压缩的.min.js),生成对应的.min.js文件。 -
压缩选项包括
--compress和--mangle,可以极大地减小文件体积。
七、Zlib 压缩为二进制
最后一步,将 .min.js 文件进一步压缩为二进制 .bin 文件,因为游戏内加载二进制文件比文本文件更快,且体积更小。
-
读取
conf.json中的配置,对于独立表(在otherOutputDir中定义的),将其.min.js压缩后输出到对应的outputDir目录,文件名改为表名.bin。 -
对于公共的
config.min.js,压缩后输出到outputCompressedJs指定的路径。 -
使用 Python 内置的
zlib库进行压缩,压缩级别为 9(最大压缩)。
八、游戏内读取与使用
在 Cocos Creator 中,我们通过一个 ConfigManager 来加载这些二进制文件。例如:
TypeScript
/*
* @Description: Cocos Creator 3.8.6 配置表加载(支持压缩包解压)
* @Author: danny
* @Date: 2025-02-15 15:20:00
*/
import pako from 'pako';
import { _decorator, assetManager, BufferAsset } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('ConfigManager')
export class ConfigManager {
private static _instance: ConfigManager | null = null;
public static get instance(): ConfigManager {
if (!this._instance) {
this._instance = new ConfigManager();
}
return this._instance;
}
/**加载压缩的二进制配置文件*/
loadConfig(bundleName: string, path: string) {
assetManager.loadBundle(bundleName, BufferAsset, (err, bundle) => {
if (err) {
console.error('bundle加载失败,bundleName:', bundleName, "err:", err);
return;
}
bundle.load(path, (err, arrayBuffer) => {
if (err) {
console.error('本地加载失败,尝试加载远程config', ", bundleName:", bundleName, "path:", path, "err:", err);
this.remoteConfig();
return;
}
this.parseBinData(arrayBuffer);
});
});
}
/** 解析配置数据*/
parseBinData(bufferAsset) {
const arrayBuffer = bufferAsset.buffer();
const compressedData = new Uint8Array(arrayBuffer);
try {
const inflated = pako.inflate(compressedData);
const decoder = new TextDecoder('utf-8');
const fullText = decoder.decode(inflated);
// 提取 JSON 对象部分
const jsonStart = fullText.indexOf('{');
const jsonEnd = fullText.lastIndexOf('}');
if (jsonStart === -1 || jsonEnd === -1) {
console.error('无效的格式,未找到有效的 JSON 对象');
return;
}
const jsonString = fullText.substring(jsonStart, jsonEnd + 1);
if (!window["gameConfData"]) {
window["gameConfData"] = new Function('return ' + jsonString)();
} else {
window["gameConfData"] = Object.assign(window["gameConfData"], new Function('return ' + jsonString)());
}
console.log("配置加载成功", window["gameConfData"]);
} catch (e) {
console.error('解压或解析失败:', e);
}
}
}
然后就可以在代码中愉快地使用自动生成的类了:

如果你正在使用 Cocos Creator 开发游戏,并且深受 Excel 配置管理的困扰,不妨参考本文的思路,搭建一套属于自己的配置导出工具,让开发更高效、更愉快!