资讯详情

Unity生成Excel表格:选型、配置与实现全攻略

📅 2026/10/14 13:43:35 | 华诺云谱 👁 阅读
Unity生成Excel表格:选型、配置与实现全攻略
如果你在Unity项目里待过一阵子大概率会遇到这么个需求游戏跑完了要把玩家战绩导出来做成Excel发给运营策划配好的表要反推一份Excel给文档组归档本地自动化测试跑完领导点名要一份带格式的测试报告。说白了一句话——Unity 生成 Excel表格。这个需求听起来简单实际上坑不少。我见过不少人第一反应是去商店搜“Excel插件”搜完更懵也有人直接生成一个.csv文件丢给运营结果中文乱码、数字变科学计数法、公式全没被同事追着问“你到底会不会做”。这篇文章不绕弯子把我这些年跑过的路线、踩过的坑、最后稳定使用的方案一次性讲清楚从底层原理到可直接抄的代码尽量让你看完能直接动手。1. 为什么Unity项目里会冒出“导出Excel”这种需求1.1 三个最常见的业务场景先说场景因为选型完全取决于你要干什么。第一种是数据交付类。游戏内玩家有排行榜、对战记录、经济数据运营想要一份按日期、按服务器筛选的明细表用于分析留存和付费。这类数据通常来自服务器下发或本地存档需要在客户端生成一份可读性强的表格文件。第二种是策划/配置反推类。项目里很多数值是策划用Excel配好然后转成二进制或Json供游戏读取的但某些情况下需要反向操作把运行时产生的配置快照导出来给策划确认“当前线上到底跑的是哪个版本”。我做过一个某项目就是这样线上环境出了问题需要立刻导出一份当前生效的配置Excel交给各方核对。第三种是工具链整合类。QA跑完测试需要自动生成一份包含执行结果、截图路径、错误日志的测试报告或者你在Editor里做批量处理工具处理完几百个资源要输出一份清单给同事核对。这种场景下Unity的Editor扩展配合Excel导出是非常顺手的组合。这些需求的共同点是文件最终要被另一个人用Excel/WPS打开看而不是给程序自己读。这一点很重要因为给程序读我们可以用Json、用SQLite、用二进制但给人类看还指望对方能用筛选、排序、公式那Excel仍然是绕不开的形态。1.2 先想清楚行家口中的xlsx和CSV差在哪很多教程会告诉你“导出Excel很简单啊写个CSV就行”。这句话对了一半但恰恰是那一半没说清楚的地方会坑人。CSV本质上就是一个纯文本文件字段间用逗号分隔Excel能打开但不等于Excel表格。它没有样式、没有多Sheet、没有公式、没有单元格合并、没有数据类型的严格区分。最典型的问题是当你的数据里有超过15位的ID比如某些雪花ID时Excel打开CSV会把数字自动转成科学计数法当你的内容里有逗号或换行时CSV的解析规则又会导致内容错位中文编码不统一时缺BOM的UTF-8、ANSI、GBK打开直接乱码。而xlsx完全不一样。它内部是一个zip压缩包包里面是一组XML文件分别描述工作簿结构、单元格数据、样式、公式等。格式是国际规范Office Open XML的一部分有专门的处理库来读写。它能保留你设置的列宽、颜色、合并单元格、数据验证、公式Excel和WPS打开后几乎不会出现“变样”的问题。所以我的结论放在最前面如果你只是要一份临时数据给同事瞄一眼CSV可以但只要对方会拿这个文件做筛选、做汇总、做二次编辑请老老实实生成真正的xlsx。这也是本篇文章重点讨论xlsx生成的原因。2. 技术路线选型不要盲目去搜“Unity Excel插件”2.1 开源库对比EPPlus、ClosedXML、NPOIUnity的C#环境本质上跑的还是.NET那一套所以很多.NET生态里的Excel库可以直接拿过来用。绕开商店里各种收费插件社区里最常提到的三驾马车是EPPlus、ClosedXML和NPOI。先看一张我用下来总结的对比表库名称主攻格式许可协议上手难度性能与功能EPPlusxlsx不含xls5.0为Polyform非商用许可需商业授权4.x版本仍为LGPL较低功能全样式/公式/图表/透视表都有写入性能好ClosedXMLxlsxMIT宽松较低API比EPPlus更贴近人的直觉性能略逊但日常够用NPOIxls和xlsxApache 2.0中老牌稳定适合读写老格式xls但xlsx体验相对老旧从Unity集成的角度三个库都能用因为它们都是纯托管代码。但在实际项目中我见过最多的情况是用了EPPlus 5.x结果被法务要求替换因为新版EPPlus的许可证不是商用友好的又或者是用了NPOI结果发现API是Java POI的直译风格写起来很别扭。我自己现在的选择逻辑是这样的如果只是做Editor阶段工具导出配置表、测试报告等用ClosedXMLMIT许可没有后顾之忧API写起来也顺手。如果需要运行时真机生成Excel比如手机上生成报表分享出去继续用ClosedXML或者NPOI具体看数据量。如果项目有很重的老xls格式兼容需求历史上遗留了很多.xls文件需要处理那NPOI基本上是唯一选择。EPPlus不是不能用但必须注意版本用4.5.3.3及之前的LGPL版本或确认公司买了商业授权否则别冒风险。2.2 手写XML方案与CSV方案的边界还有一种很“硬核”的方案我提一下就是完全不依赖第三方库手动构建xlsx。因为xlsx本质上就是zip里的XML理论上你可以用System.IO.Compression创建zip把[Content_Types].xml、_rels/.rels、xl/workbook.xml、xl/worksheets/sheet1.xml这些文件按规范写出来最后压缩成.xlsx后缀文件。这个方案的优点是没有第三方依赖也不会被IL2CPP裁剪问题困扰缺点是工作量陡然上升随便一个单元格样式就要写一长串XML而且Excel对文件规范的要求很严格有一点不合规打开时就会弹“文件已损坏是否尝试修复”。我试过一次在项目里用这个方式生成一个带合并单元格的表格光调试XML结构就花了半天。所以这个方案只适合两种情况一是你的领导规定“绝对不允许引入任何第三方DLL”二是你只需要生成最最基础的、无样式、纯数据的xlsx。CSV方案也不是完全不能用。如果数据量极大几十万行以上xlsx库反而吃力CSV写起来飞快。但你要做好两件事以UTF-8 with BOM编码写入避免中文乱码字段里如果有逗号、引号、换行要做标准CSV转义。另外提前和对方打好招呼“这个CSV里不要手工改容易出错”。在我的经验里CSV方案适合做数据备份、导入导出链路中的过渡格式不适合作为交付给运营做分析的终版格式。2.3 我的推荐组合和判断标准把方案都摆出来后说说我实际的项目组合。最近两年我主要用两个组合组合AEditor工具场景ClosedXML Unity Editor脚本。用于批量导出配置表、生成测试报告、处理资源清单。这个组合的好处是MIT许可、API顺手、出问题容易查。组合B运行时真机场景NPOI或者ClosedXML通过.NET Standard方式引入重点确认IL2CPP打包后功能正常。真机场景我还会多做一个分流超过5万行的数据我会考虑CSV替代或者分多个Sheet导出避免一次性把手机内存撑爆。再说判断标准。不管用什么库有一个原则必须坚持看这个库是否维护活跃、看它的许可证是否允许你的商业模式。Unity商店里那些打包好的Excel插件很多时候只是把某个开源库封装了一层UI价格还不便宜。学会直接用底层库主动权在你手里。3. 环境配置三处最容易翻车的地方确定选type之后真正的麻烦从环境配置开始。很多人在这里卡住以为代码有问题其实Unity工程环境没配好。3.1 API Compatibility Level不切到.NET 4.x库根本跑不起来这是最常见的坑没有之一。Unity新版默认的Api Compatibility Level可能是.NET Standard 2.1这在很多情况下没问题但不少Excel库的某些功能依赖.NET Framework的API或者依赖的依赖没做完整的.NET Standard适配。最典型的报错是FileNotFoundException或者TypeLoadException脚本管理Mono下编译器能过一运行就崩。解决方法是打开Player Settings - Other Settings - Configuration - Api Compatibility Level改为.NET Framework老版本叫.NET 4.x。这会让你的App在Mono模式下获得更完整的.NET API集合第三方库跑起来的阻力会小很多。但注意不是改了后就万事大吉。如果你要打包IL2CPP后面还有新的坑。3.2 IL2CPP与代码裁剪打包后报MissingMethodException真机打包通常用IL2CPP而IL2CPP在编译前会做代码裁剪Strip把“它认为没用到”的托管代码移除。问题是很多Excel库底层用了反射或动态程序集加载裁剪器识别不到这些“隐式调用”最终导致打包后运行时报MissingMethodException或者MethodAccessException。怎么处理我目前稳定可靠的做法是在Unity项目里添加一个link.xml文件放在Assets目录下内容大意告诉裁剪器“以下程序集和里面的类型/方法不要动”。CLosedXML和NPOI的处理方式有些差异我以ClosedXML为例给一个大致模板linker assembly fullnameDocumentFormat.OpenXml preserveall / assembly fullnameClosedXML preserveall / /linkerDocumentFormat.OpenXml是ClosedXML依赖的底层XML库必须一起保留。NPOI的话一般是这些linker assembly fullnameNPOI preserveall / assembly fullnameNPOI.OOXML preserveall / assembly fullnameNPOI.OpenXml4Net preserveall / /linker如果你用了反射来创建动态单元格类型比如通过字符串名字构造工厂类还需要在link.xml里显式列出这些类型否则照样被裁。我吃过一次亏导出Excel没问题导出Excel后附带生成一个图片放到单元格里结果那个图片处理类被裁剪运行到一半才崩。后来直接在link.xml里preserveall才彻底安心。3.3 许可证合规EPPlus 5.x的商用灰色地带EPPlus是功能最全的xlsx库但你自己用和公司商用是两码事。EPPlus 5.0之后改了许可协议Polyform Noncommercial个人学习没问题商业用途必须购买商业授权。很多团队不看公告直接装了最新版做到一半法务来问“你用的这个库什么协议”场面一度很尴尬。我处理的某项目当时用的EPPlus老版本4.5.3.3这个版本是LGPL协议可以在商业项目中以动态链接方式使用但要遵守LGPL的条款比如用户可以替换库等。后来为了省心新项目我都改用ClosedXML或NPOI因为它们的MIT/Apache协议对商业应用友好得多。所以无论你选哪个库先花五分钟看一下它的License声明特别是如果这个项目最终要上架、要被公司拿去盈利。这是环境配置里最容易被忽略但又最“致命”的一环。4. 核心实现从零生成一份可打开的xlsx这章开始上代码。我会以ClosedXML为主讲实现因为它语法直观且许可干净。代码是在Unity环境里跑的基本上是一个普通的C#脚本不依赖任何编辑器API。4.1 最小可运行代码创建工作簿、填数据、保存先展示一段最小化的导出脚本。原理很简单创建XLWorkbook对象添加工作表往指定单元格写值最后SaveAs到目标路径。using System.IO; using ClosedXML.Excel; using UnityEngine; public class SimpleExcelExporter { public static void ExportPlayers(string savePath) { using (var workbook new XLWorkbook()) { var worksheet workbook.Worksheets.Add(玩家数据); // 表头 worksheet.Cell(1, 1).Value ID; worksheet.Cell(1, 2).Value 昵称; worksheet.Cell(1, 3).Value 金币; worksheet.Cell(1, 4).Value 注册时间; // 数据行这里只是示例真实项目里通常来自ListPlayerData worksheet.Cell(2, 1).Value 1001; worksheet.Cell(2, 2).Value 张三; worksheet.Cell(2, 3).Value 2500; worksheet.Cell(2, 4).Value new System.DateTime(2024, 5, 1, 10, 30, 0); // 保存 workbook.SaveAs(savePath); } Debug.Log($导出成功: {savePath}); } }不要小看这段代码它涵盖了三个关键点第一using语句很重要。XLWorkbook会占用资源不释放的话在Windows上下一次导出同一个文件会报“文件被占用”在Android上可能导致文件句柄泄漏。用using是最稳的。第二Cell(1,1).Value传入的是什么类型将来Excel里就是什么类型。传int就是数字传string就是文本传DateTime就是日期。千万不要把数字先转成字符串再写入否则后续排序和公式都会出问题。第三SaveAs的路径要确保目录存在。Unity不会自动帮你建目录导出的文件夹如果不存在直接报DirectoryNotFoundException。所以保存前最好加一句Directory.CreateDirectory(Path.GetDirectoryName(savePath))。4.2 把数据从List转换为Excel表格的高效姿势实际项目里你的数据肯定不在代码里写死而是来自一个List或数组。初学者最容易写出来的版本是两层for循环逐个单元格赋值比如for (int row 0; row players.Count; row) { worksheet.Cell(row 2, 1).Value players[row].Id; worksheet.Cell(row 2, 2).Value players[row].Name; // ... }这样写功能上没问题数据量小时也感受不到差别。但一旦数据量到了几千行、几十列逐单元格写入的性能就会很难看因为每次.Cell().Value赋值都要经过一次对象查找和类型处理。更好的做法是把数据组织成一个二维数组或者DataTable然后用ClosedXML提供的批量写入方法。下面是我在实际项目中用的一种方式手写一个函数把行集合转成object[,]再调用Range.Value一次性赋值public static void FillWorksheet(IXLWorksheet worksheet, ListPlayerData players) { string[] headers { ID, 昵称, 金币, 注册时间 }; int rows players.Count 1; int cols headers.Length; var data new object[rows, cols]; // 表头 for (int c 0; c cols; c) data[0, c] headers[c]; // 数据 for (int r 0; r players.Count; r) { data[r 1, 0] players[r].Id; data[r 1, 1] players[r].Name; data[r 1, 2] players[r].Gold; data[r 1, 3] players[r].RegisterTime; } // 一次性写入 var range worksheet.Range(1, 1, rows, cols); range.Value data; }这个写法在数据量大时性能差距非常明显。我实测过1万行x10列的表格逐单元格写入大约要10秒以上批量写入不到1秒。核心原因是批量写入减少了大量重复的对象创建和查找开销而且底层对XML节点的生成做了合并处理。顺带提一下如果你觉得手写数组太机械也可以用worksheet.Cell(2, 1).InsertTable(list)之类的方法但InsertTable会把数据转成Excel“表”Table Object带自动筛选和样式有时候反而多出你不需要的东西。我偏好上面的纯数组写法可控性最高。4.3 样式、日期、公式的细节处理数据填进去以后你多半还要做点“颜值工程”。表头加粗、关键列加底色、列宽自适应、数字格式化成千分位、日期显示成斜杠格式。这些都是Excel用户平时最在意的细节。先看表头样式。ClosedXML里设置单元格样式很直观var headerRange worksheet.Range(1, 1, 1, 4); headerRange.Style.Font.Bold true; headerRange.Style.Font.FontColor XLColor.White; headerRange.Style.Fill.BackgroundColor XLColor.FromArgb(0x4472C4); headerRange.Style.Alignment.Horizontal XLAlignmentHorizontalValues.Center;列宽自适应在填完数据之后调用worksheet.Columns(1, 4).AdjustToContents();这句会让列宽根据内容长度自动调整。要注意的是AdjustToContents也要放在所有数据都填完之后因为它是按当前单元格内容计算的。日期列如果你不想让“2024-05-01 10:30:00”显示得过于冗长可以设置日期格式worksheet.Range(2, 4, 2 players.Count - 1, 4).Style.DateFormat.Format yyyy-mm-dd hh:mm;公式的话ClosedXML支持直接给单元格赋值公式字符串比如worksheet.Cell(players.Count 2, 3).FormulaA1 SUM(C2:C (players.Count 1) );FormulaA1表示这是A1风格的公式。保存后Excel或WPS打开时会自动计算所以用户看到的就是计算结果而不是公式文本。这一点比CSV强太多了——CSV里根本没有公式的概念。还有一个我经常会用到的操作冻结首行。运营拿到表格后往下翻几页就把表头忘了冻结首行能省很多事worksheet.SheetView.FreezeRows(1);这三个细节表头样式、日期格式、冻结行看起来不大但你交付出去的文件给人的专业感完全不同。反正导出代码已经写了顺手加几行样式代码的成本几乎为零。5. 实际项目中的坑与性能表现5.1 文件被占用、公式不计算结果、中文列宽等常见坑我在开头说过这套流程坑很多。把几个我真实踩过的列出来坑一文件被占用第二次导出报IOException。这个问题通常不是Unity的锅而是你在上一个导出流程里没有释放XLWorkbook。哪怕你调用了SaveAs只要对象没Dispose文件句柄就还在。所以再次强调using或者finally里Dispose二选一但绝对不能省。坑二带公式的Excel用其他工具打开显示0。前面说过Excel和WPS打开xlsx会自动重算公式但有些第三方的表格组件比如某些网页预览器不会重算它直接读取文件里缓存的公式结果。ClosedXML在保存时默认不会帮你把公式计算后的结果写进缓存单元格——不是它的功能缺陷而是规范允许的做法。如果你遇到“我生成的Excel在WPS里正常但在某预览器里全是0”的反馈先别慌问一下对方是用什么工具打开的。如果必须在预览器里也显示结果我目前没有特别好的招要么让预览器开启重算要么在导出前用公式库算好结果再写入。坑三中文列宽自适应后仍然显示不全。AdjustToContents()根据字符数量估算列宽但中文字符的宽度和英文字母不同估算经常偏小。我的经验是给中文居多的列手动设置一个最小宽度worksheet.Column(2).Width 20;或者直接给列设置一个偏大的固定值。别问我为什么ClosedXML不智能问就是“够用就行”。坑四手机端导出路径权限。在Android/iOS真机上导出到Application.persistentDataPath是没问题的但如果你尝试把文件写进StreamingAssets或者应用私有目录之外的公共目录会直接失败。不少新手把路径写成Application.dataPath下的某个子目录这在Editor里能跑通打包后就彻底不行了。真机导出的正确路径永远是Application.persistentDataPath下面的子目录。5.2 逐单元格写入与批量写入的性能差距性能这块我拿自己的测试数据说话。在Windows系统下Unity Editor里跑同一份10000行x10列的导出测试两种写法对比逐单元格赋值约8.5秒二维数组批量赋值约0.9秒差异接近10倍。如果把样式也逐单元格设置而不是整片Range设置时间还会再翻倍。原因是Excel文件在底层的逻辑是每设置一个单元格、一个样式都要在XML结构里维护对应的节点信息批量写入时这些操作能合并节点生成次数大幅减少。所以性能优化的核心只有两条数据用批量方式写样式用Range方式整片设置避免对单行单列做细粒度操作。另一个容易被忽略的性能点是文件体积。如果你把相同文本重复写到很多列文件不会变小因为xlsx是zip压缩格式重复内容理论上会被压缩但样式重复不会。我见过一个中途接手的项目导出一个只有2万行数据的Excel文件竟然有80MB打开要半分钟。最后定位原因就是大量单元格被重复设置了默认样式。所以样式越少、越统一文件越小。5.3 Editor工具导出和运行时导出的区别最后聊聊Editor工具和运行时导出这两个场景的区别因为它们虽然代码逻辑差不多但使用体验要求完全不同。Editor工具导出指的是你写一个[MenuItem(Tools/Export Excel)]这样的菜单项在Unity编辑器里点击执行。这里最大的方便是可以使用EditorUtility.SaveFilePanel弹出系统的保存对话框让用户自己选路径[MenuItem(Tools/导出玩家数据Excel)] public static void ExportFromEditor() { string path EditorUtility.SaveFilePanel(导出Excel, Application.dataPath, players.xlsx, xlsx); if (string.IsNullOrEmpty(path)) return; SimpleExcelExporter.ExportPlayers(path); }这种方式适合工具链内部使用。因为是在Editor环境下跑API Compatibility和IL2CPP的问题基本不用考虑随便用哪个库都行。运行时导出则完全不同。它面对的是真机环境你无法弹出一个系统保存对话框iOS上尤其受限大多数情况下是导出到persistentDataPath然后通过分享面板让用户把文件发给别人。我还碰到过一种需求运行时要自动生成Excel并上传到公司内部的服务器那就不需要本地保存直接把XLWorkbook保存到MemoryStream再转成byte[]上传using (var workbook new XLWorkbook()) { var worksheet workbook.Worksheets.Add(Sheet1); worksheet.Cell(1, 1).Value Hello; using (var stream new MemoryStream()) { workbook.SaveAs(stream); byte[] bytes stream.ToArray(); // 上传 bytes ... } }注意SaveAs(stream)不能写workbook.SaveAs(stream, false)这样的参数了不同库版本参数略有差异但基本思路一致存流而不是存路径。真机场景我还会在上传前加一个校验读一下文件头的PK标识xlsx是zip格式文件头固定为PK防止传出去的文件是0字节或者损坏状态。6. 我的选型建议和一段工作心得6.1 不同诉求下的最终方案结合前面的分析我把结论汇总一下方便你们直接对照自己的需求抄答案。你的场景推荐方案关键注意事项Editor工具、一次性导出配置/报告ClosedXML用SaveFilePanel选保存路径注意using释放资源商用项目、运行时生成xlsxClosedXML或NPOI检查许可证为MIT/Apache配置link.xml防裁剪需要兼容老.xls文件NPOI用HSSFWorkbook处理.xlsXSSFWorkbook处理.xlsx超大数据量导出10万行以上先考虑CSVUTF8 BOM或分Sheet导出不要指望一个Sheet塞下十几万行还有好性能完全禁止第三方DLL手写XML打zip包只建议做无样式纯数据别碰样式和合并单元格我的默认选择是ClosedXML不是因为它在性能上冠绝群雄而是因为它的API最贴近人的直觉出错率低而且MIT许可证让我不用半夜被法务电话叫醒。6.2 直接用一段真实经历收尾最后说个最近的经历。上个月我帮一个项目组把CSV导出改成xlsx导出起因很简单运营用WPS打开CSV时玩家ID超过15位全部变成了科学计数法而且像“1.23457E14”这种格式根本没法还原。更麻烦的是里面有个字段碰巧带了逗号导致整列数据错位运营根本没法筛。他们只能反复找开发“能不能导出Excel”改完之后其实代码量并不大——把CSV写入循环换成ClosedXML的批量写入再补上表头样式和日期格式前后不到两个小时。但效果立竿见影运营再也没找过我们说乱码、错位、科学计数法这些词了。所以如果你正在纠结“要不要专门去学一下Excel导出”我的建议是别纠结这个功能本质上不是花活而是正经的项目交付能力。你早晚会遇到给非技术同事交付数据文件的场景学会用靠谱的库生成一份真正能用的xlsx一次投入后面所有项目都能复用。如果非要说一个最值得记住的经验那就是导出Excel的核心从来不是“把数据写进文件”而是“让你的数据在别人的Excel里看起来像一份精心准备的报告”。数据都进了文件但没人能正常打开、没人看得懂等于没做。多花10分钟处理样式、格式和兼容性值得。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑