CluckCluck 的开发时间只有 48 小时,在这样短的开发时间内,作为程序的我们需要频繁对场景以及各类预制件做出修改,从避免提交冲突的角度考虑,我们没有办法让策划同学直接在Unity场景中配置道具的数据。
不过,我想到之前在 hll 学长的代码仓库里看到过配置表工具,结合实际开发中策划人员也是通过打表实现数据传递的,我决定在 CluckCluck 中使用这个功能,完美解决了策划的配置与调试可能影响到提交冲突的问题(好耶!
所以在本文中,我想剖析一下这个妙妙小工具,看一下它是怎么运作的。首先来看看工具的文件结构:
1 2 3 4 5 6 7 8 9 10 11
| 配置表 |- Excels |- Scripts |- Plugins |- Foods |- FoodProps.cs |- FoodsInfo.cs |- Editor |- FoodsInfo_Editor.cs |- Weapons |- BaswExcel.cs
|
Excels
顾名思义,是存放 .xlsx
文件(也就是上面两张截图)的目录。让我们看看 Scripts
文件夹里头的东西吧!
Plugins
Plugins
文件夹里有三个文件,分别是:
- EPPlus.dll:EPPlus 库的核心动态链接库,包含了 EPPlus 的所有功能代码。
- EPPlus.pbd:调试符号文件,包含了调试信息,可以用来帮助定位 EPPlus 库中的错误或异常(并非必须)。
- EPPlus.xml:EPPlus 库的 XML 文档,包含了库中所有类、方法和属性的注释说明。
这个 EPPlus 就是实现 excel 表格操作的核心工具,它究竟是何方神圣?EPPlus 是一个用于操作 Excel 文件的开源 .NET 库,允许开发者在 C# 中创建、读取、编辑和格式化 Excel 文件(支持 .xlsx 格式),能够处理单元格数据、公式、图表、样式、数据验证、条件格式等功能。对于配置表工具来说,加粗的这两项工具就足够我们使用啦。
关于 EPPlus 的各个 API 如何使用,可以参阅官方文档,在本文中不过多描述。
BaseExcel.cs
这个脚本很简单,只包含了 IndividualData
一个类。该类用于存储 Excel 表中一行中每一列的数据。它的结构如下:
1 2 3 4 5 6 7 8
| namespace 配置表 { public class IndividualData { public string[] Values; public IndividualData(int Columns) { Values = new string[Columns]; } } }
|
道具
Foods 和 Weapons 这两个文件夹一个代表食物道具,一个代表武器道具。它们本质上都是在通过配置表工具解析表格中数据并使用,在功能上是一样的,因此在下文中我们只介绍 Foods
文件夹的结构:
1 2 3 4 5
| |- Foods |- FoodProps.cs |- FoodsInfo.cs |- Editor |- FoodsInfo_Editor.cs
|
道具属性(Properties)
道具属性对应 Foods 文件目录下的 FoodProps.cs
脚本,该脚本通过 EPPlus API 从记录了食品道具配置信息的 Excel 文件(foods.xlsx
)中读取并解析数据,并将其转换为一个字典(Dictionary<int, IndividualData>),其中键是每行数据的唯一标识(ID),值是一个包含该行数据的 IndividualData
对象。
展开来说,FoodProps.cs
主要做了以下四件事:
-
读取 Excel 文件:
- 从指定路径(Application.dataPath + “/配置表/Excels/foods.xlsx”)加载 Excel 文件。
- 使用 EPPlus 库解析 Excel 文件内容。
-
解析 Excel 数据:
- 读取第一个工作表(workSheets[0])。
- 遍历每一行(从第二行开始,跳过表头)。
- 将每行的数据存储到
IndividualData
对象中。
-
存储数据:
- 对于每一行,创建一个
IndividualData
对象,初始化其 Values
数组大小为 CountOfAttributes
。
- 遍历每一列(从第一列到最后一列),将单元格的值存储到
Values
数组中。
- 将第一列的值(item.Values[0])转换为整数,作为道具 ID。
- 将 ID 和对应的
IndividualData
对象存储到字典中。
-
返回结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| using System; using System.Collections.Generic; using System.IO; using OfficeOpenXml; using UnityEngine;
namespace 配置表.Foods { public class FoodProps : MonoBehaviour { public static int CountOfAttributes = 6;
public static Dictionary<int, IndividualData> LoadExcelAsDictionary() { Dictionary<int, IndividualData> ItemDictionary = new Dictionary<int, IndividualData>(); string path = Application.dataPath + "/配置表/Excels/foods.xlsx"; FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
ExcelPackage excel = new ExcelPackage(fs); ExcelWorksheets workSheets = excel.Workbook.Worksheets; ExcelWorksheet workSheet = workSheets[0];
int colCount = workSheet.Dimension.End.Column; int rowCount = workSheet.Dimension.End.Row;
for (int row = 2; row <= rowCount; row++) { IndividualData item = new IndividualData(CountOfAttributes);
for (int col = 1; col < colCount; col++) { item.Values[col - 1] = workSheet.Cells[row, col].Text; } int itemID = Convert.ToInt32(item.Values[0].ToString()); ItemDictionary.Add(itemID, item); }
Debug.Log("complete"); return ItemDictionary; } } }
|
FoodsInfo.cs
这个脚本定义了两个类:FoodSettings
和 FoodsInfo
,主要用于在 Unity 中管理和存储食物道具的具体配置项信息。
FoodSettings
:用于存储食物的配置属性(如 ID、名称、是否为buff、体积、速度、buff时间等)。
FoodsInfo
:可以挂载到 Unity 的游戏对象上,是策划配置好表格后在 Unity 中测试的工具(结合下一小节的编辑器工具使用)。
- 包含一个
FoodSettings
类型的字段,用于在编辑器中显示存储具体的食物配置。
- 包含一个
InitFromID
字段,用于指定从配置表中加载的食物的 ID。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| using System.Collections; using System.Collections.Generic; using UnityEngine; using System;
[Serializable] public class FoodSettings { public int id; public string name; public int is_buff; public float volume; public int speed; public int buff_time; }
public class FoodsInfo : MonoBehaviour { public FoodSettings Settings;
[Header("配表内ID")] public int InitFromID; }
|
编辑器工具(Editor)
Editor 文件夹里包含了一个自定义编辑器脚本,通过增加一个按钮扩展了 FoodsInfo
组件在 Unity Inspector 中的功能,允许策划同学从配置表中加载指定 ID 的食物道具数据,并将其赋值给 FoodsInfo 组件的 Settings 字段。
这样,策划同学就可以直观地看到配置表中的数据了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| using UnityEngine; using UnityEditor; using System; using 配置表; using 配置表.Foods;
[CustomEditor(typeof(FoodsInfo),true)] public class FoodsInfo_Editor : Editor { public override void OnInspectorGUI() { DrawDefaultInspector();
if (GUILayout.Button("从配表ID刷新食物数据")) { FoodsInfo foodsInfo = (FoodsInfo)target; Init(foodsInfo); } }
public void Init(FoodsInfo instance) { Action initfood;
var dictionary = FoodProps.LoadExcelAsDictionary();
if (!dictionary.ContainsKey(instance.InitFromID)) { Debug.LogErrorFormat("未能在 foods 配表中找到指定的ID:{0}", instance.InitFromID); return; } IndividualData item = dictionary[instance.InitFromID];
initfood = (() => { instance.Settings.id = Convert.ToInt32(item.Values[0]); instance.Settings.name = Convert.ToString(item.Values[1]); instance.Settings.is_buff = Convert.ToInt32(item.Values[2]); instance.Settings.volume = Convert.ToSingle(item.Values[3]); instance.Settings.speed = Convert.ToInt32(item.Values[4]); instance.Settings.buff_time = Convert.ToInt32(item.Values[5]); });
initfood(); } }
|
参考资料
https://github.com/EPPlusSoftware/EPPlus
https://epplussoftware.com/docs/7.2/articles/readme.html