0 前言
5月份参加了机核的Booom,和机组组到的神仙队友们一起完成了《恶魔真探》这款游戏:游戏地址,感兴趣的朋友可以在首页简介链接里下载,如果能够给我们点个关注投个票就太感谢了!
其中为了方便策划进行调整修改,很多数据的存储我们都采用了Excel表的方式,如恶魔属性部分:
那么这样就自然涉及到对于Excel表格数据的管理。
我们很容易发现一个Excel表的属性加起来其实可以抽象为一个类,比如上述的表格,他描述的是恶魔的属性数值,对于单只恶魔,我们可以把它抽象为这样一个类:
class Devil{
public int ID;
public string Name;
public int[] Skills;
}
而一个Excel表的数据加起来显然是类数据的集合,它可以抽象为key为ID,value为类实例的字典,如上述的Excel表可以描述为:
Dictionary<int,Devil> DevilnDataDict;
这样显然是比较理想的管理数据的方式,在游戏里,我们也可以比较方便地通过序号去读取某只特定恶魔的数据。
那么我们如何做到呢?
我采用的方式是通过工具读取Excel表并生成一个特定的脚本,这个脚本包含了类的定义以及一个读取Json文件并存储为字典的方法。(是的,这个工具还将Excel表格转换为Json文件以便读取)
1 工具相关代码
1.1 ExcelUtility
表格内不同字段类型的数据应该如何填写,可以看这个类有一段switch-cash的代码里对于数据是如何分割的,然后根据自己需要进行修改。
/// <summary>
/// Excel工具类
/// </summary>
public class ExcelUtility
{
/// <summary>
/// 表格数据集合
/// </summary>
private DataSet mResultSet;
private string TempexcelFile;
List<string> dataName = new List<string>();
List<string> dataType = new List<string>();
List<string> enumName = new List<string>();
Dictionary<string, List<string>> enumType = new Dictionary<string, List<string>>();
/// <summary>
/// 构造函数
/// </summary>
public ExcelUtility(string excelFile) {
TempexcelFile = excelFile;
using (FileStream mStream = File.Open(excelFile, FileMode.Open, FileAccess.Read)) {
IExcelDataReader mExcelReader = ExcelReaderFactory.CreateOpenXmlReader(mStream);
mResultSet = mExcelReader.AsDataSet();
}
}
/// <summary>
/// 转换为Json
/// </summary>
public void ConvertToJsonForGameData(string JsonPath, Encoding encoding, string CSharpPath) {
//判断Excel文件中是否存在数据表
if (mResultSet == null || mResultSet.Tables == null || mResultSet.Tables.Count <= 0) {
Debug.Log("不存在数据表,直接退出");
return;
}
//读取Excel文件中的数据表
for (int x = 0; x < mResultSet.Tables.Count; x++) {
//默认读取第一个数据表
var mSheet = mResultSet.Tables[x];
var outname = mSheet.TableName;
if (outname.IndexOf('#') >= 0 && outname.LastIndexOf('#') != outname.IndexOf('#')) {
Debug.Log("无法导出名 " + outname + " 请确定#书写正确!");
continue;
}
//判断数据表内是否存在数据
if (mSheet.Rows.Count < 1) continue;
//读取数据表行数和列数
int rowCount = mSheet.Rows.Count;
int colCount = mSheet.Columns.Count;
//准备一个列表存储整个表的数据
List<Dictionary<string, object>> table = new List<Dictionary<string, object>>();
//List<object> tempvaluestrList = null;
string tempfield = null;
string temptypestring = null;
//读取数据
for (int i = 3; i < rowCount; i++) {
//标记当前行是否为空行
var isEmptyRow = true;
//准备一个字典存储每一行的数据
Dictionary<string, object> row = new Dictionary<string, object>();
for (int j = 1; j < colCount; j++) {
//读取第1行数据作为表头字段
string field = mSheet.Rows[1][j].ToString();
field = field.Trim();
if (field != "") {
tempfield = field;
if (!dataName.Contains(field)) {
dataName.Add(field);
}
}
else if (tempfield != "" && field == "") {
field = tempfield;
}
string typestring = mSheet.Rows[2][j].ToString();
typestring = typestring.Trim();
if (typestring != "") {
temptypestring = typestring;
dataType.Add(typestring);
}
else if (typestring == "" && temptypestring != "") {
typestring = temptypestring;
}
string valuestr = mSheet.Rows[i][j].ToString();
valuestr = valuestr.Trim();
if (!string.IsNullOrEmpty(valuestr)) isEmptyRow = false;
//Key-Value对应 按类型存放
switch (typestring) {
case "int":
if (valuestr != "") {
row[field] = int.Parse(valuestr);
}
else {
row[field] = 0;
}
break;
case "int[]":
if (!string.IsNullOrEmpty(valuestr)) {
var strs = valuestr.Split(",");
var list = new List<int>();
for (var k = 0; k < strs.Length; k++) {
list.Add(int.Parse(strs[k]));
}
row[field] = list.ToArray();
}
else {
row[field] = new int();
}
break;
case "float":
if (valuestr != "") {
row[field] = float.Parse(valuestr);
}
else {
row[field] = 0;
}
break;
case "float[]":
if (!string.IsNullOrEmpty(valuestr)) {
var strs = valuestr.Split(",");
var list = new List<float>();
for (var k = 0; k < strs.Length; k++) {
list.Add(float.Parse(strs[k]));
}
row[field] = list.ToArray();
}
else {
row[field] = new float();
}
break;
case "double":
if (valuestr != "") {
row[field] = double.Parse(valuestr);
}
else {
row[field] = 0;
}
break;
case "double[]":
if (!string.IsNullOrEmpty(valuestr)) {
var strs = valuestr.Split(",");
var list = new List<double>();
for (var k = 0; k < strs.Length; k++) {
list.Add(double.Parse(strs[k]));
}
row[field] = list.ToArray();
}
else {
row[field] = new double();
}
break;
case "bool":
if (valuestr == "0" || string.Equals(valuestr, "false", StringComparison.OrdinalIgnoreCase) || valuestr == "") {
row[field] = false;
}
else {
row[field] = true;
}
break;
case "List<int>":
var tempintList = new List<int>();
if (!string.IsNullOrEmpty(valuestr)) {
var strs1 = valuestr.Substring(1, valuestr.Length - 2).Split(',');
for (var k = 0; k < strs1.Length; k++) {
tempintList.Add(int.Parse(strs1[k]));
}
}
row[field] = tempintList;
break;
case "List<int[]>":
var tempintintList = new List<int[]>();
if (!string.IsNullOrEmpty(valuestr)) {
var strs2 = valuestr.Split('、');
for (var k = 0; k < strs2.Length; k++) {
var tempStrs = strs2[k].Substring(1, strs2[k].Length - 2).Split(',');
var tempList = new List<int>();
for (var p = 0; p < tempStrs.Length; p++) {
tempList.Add(int.Parse(tempStrs[p]));
}
tempintintList.Add(tempList.ToArray());
}
}
row[field] = tempintintList;
break;
case "Dictionary<string,float>":
var tempstringfloatDict = new Dictionary<string, float>();
if (!string.IsNullOrEmpty(valuestr)) {
var dictStrs1 = valuestr.Split('、');
for (var k = 0; k < dictStrs1.Length; k++) {
var str = dictStrs1[k];
var strs = str.Substring(1, str.Length - 2).Split(',');
tempstringfloatDict.Add(strs[0], float.Parse(strs[1]));
}
}
row[field] = tempstringfloatDict;
break;
case "enum":
if (!enumName.Contains(field)) {
enumName.Add(field);
}
if (!string.IsNullOrEmpty(valuestr)) {
if (!enumType.ContainsKey(field)) {
enumType[field] = new List<string>();
}
if (!enumType[field].Contains(valuestr)) {
enumType[field].Add(valuestr);
}
}
row[field] = valuestr;
break;
default:
row[field] = valuestr.Trim();
break;
}
}
//添加到表数据中
if (!isEmptyRow) table.Add(row);
}
//生成Json字符串
string json = JsonConvert.SerializeObject(table);
//修复中文乱码
Regex reg = new Regex(@"(?i)\\[uU]([0-9a-f]{4})");
json = reg.Replace(json, delegate (Match m) { return ((char)Convert.ToInt32(m.Groups[1].Value, 16)).ToString(); });
string JsonFilePath = JsonPath + Path.GetFileNameWithoutExtension(TempexcelFile) + ".json";
//删除原来的同名文件
if (File.Exists(JsonFilePath)) {
File.Delete(JsonFilePath);
}
//写入文件
using (FileStream fileStream = new FileStream(JsonFilePath, FileMode.Create, FileAccess.Write)) {
using (TextWriter textWriter = new StreamWriter(fileStream, encoding)) {
textWriter.Write(json);
}
}
CreatCSharpForGameData(Path.GetFileNameWithoutExtension(TempexcelFile), CSharpPath);
dataName.Clear();
dataType.Clear();
enumName.Clear();
enumType.Clear();
//刷新本地资源
AssetDatabase.Refresh();
}
}
/// <summary>
/// 创建C#代码
/// </summary>
private void CreatCSharpForGameData(string name, string CSharpPath) {
//创建一个StringBuilder来构建C#脚本
StringBuilder scriptStr = new StringBuilder();
//从提供的文件名中提取类名
string className = new FileInfo(name).Name.Split('.')[0];
//在脚本开头添加必要的using语句
scriptStr.AppendLine("using Newtonsoft.Json;");
scriptStr.AppendLine("using System;");
scriptStr.AppendLine("using System.Collections.Generic;");
scriptStr.AppendLine("using System.IO;");
scriptStr.AppendLine("using System.Linq;");
scriptStr.AppendLine("using UnityEngine;");
//枚举
if (enumName.Count > 0) {
for (var i = 0; i < enumName.Count; i++) {
var list = enumType[enumName[i]];
scriptStr.AppendLine("\n[Serializable]");
scriptStr.AppendLine("\npublic enum " + enumName[i]);
scriptStr.AppendLine("{");
for (var j = 0; j < list.Count; j++) {
scriptStr.AppendLine($"\t{list[j]},");
}
scriptStr.AppendLine("}");
}
}
scriptStr.AppendLine("\n[Serializable]");
scriptStr.AppendLine("\npublic class " + className);
scriptStr.AppendLine("{");
//遍历dataName和dataType列表以生成类的属性
for (int i = 0; i < dataName.Count; ++i) {
if (dataType[i] == "enum") {
var tmpName = enumName[0];
enumName.Remove(tmpName);
var tmpName2 = char.ToLower(tmpName[0]) + tmpName.Substring(1);
scriptStr.AppendLine($"\tpublic {tmpName} {tmpName2};");
}
else {
scriptStr.AppendLine($"\tpublic {dataType[i].PadRight(10, ' ')} {dataName[i]};");
}
}
//添加一个Json数据转Dictionary字典数据的方法
scriptStr.AppendLine($"\tpublic static Dictionary<int, {className}> LoadJson()");
scriptStr.AppendLine("\t{");
string pathToLoad = "/Json/GameData/" + $"{className}.json";
scriptStr.AppendLine($"\t\tstring jsonPath = Application.streamingAssetsPath + \"{pathToLoad}\";");
// scriptStr.AppendLine($"\t\tjsonPath = jsonPath.Replace('/','\\\\');");
scriptStr.AppendLine($"\t\tif (Application.platform == RuntimePlatform.WindowsPlayer || Application.platform == RuntimePlatform.WindowsEditor) {{ jsonPath = jsonPath.Replace('/','\\\\'); }}");
scriptStr.AppendLine($"\t\tstring jsonContent = File.ReadAllText(jsonPath, System.Text.Encoding.UTF8);");
scriptStr.AppendLine($"\t\tList<{className}> classInfo = JsonConvert.DeserializeObject<List<{className}>>(jsonContent);");
scriptStr.AppendLine($"\t\tDictionary<int, {className}> idClassDict = classInfo.ToDictionary(classInfo => classInfo.ID, classInfo => classInfo);");
scriptStr.AppendLine($"\t\treturn idClassDict;");
scriptStr.AppendLine("\t}");
scriptStr.AppendLine("}");
string path = $"{CSharpPath}/{className}.cs";
if (File.Exists(path)) {
File.Delete(path);
}
File.WriteAllText(path, scriptStr.ToString());
}
1.2 ExcelToGameData
Excel表格需要放置的路径以及其他文件的生成路径,可以看这个类一开始的文件路径定义,然后根据自己需要进行修改。
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;
/// <summary>
/// Excel文件处理器
/// </summary>
public class ExcelToGameData
{
//Excel文件路径
public static string ExcelPath = Application.dataPath+"/Excel/GameData";
//Json文件路径:
//对于需要在运行时访问的二进制文件,可以将它们放在 "Assets/StreamingAssets" 文件夹中。
//这个文件夹中的内容在构建后会被包含在应用程序中,并且可以通过 Application.streamingAssetsPath访问。
public static string JsonPath = Application.streamingAssetsPath + "/Json/GameData/";
//创建的Script脚本路径
public static string CSharpPath = Application.dataPath + "/Scripts/Excel/GameData";
/// <summary>
/// Excel转GameData工具入口
/// </summary>
[MenuItem("Tools/Excel/ExcelToGameData")]
public static void StartExcelToJson()
{
if (Directory.Exists(ExcelPath))
{
//拿到所有的Excel文件
var directoryInfo = new DirectoryInfo(ExcelPath);
var FilesAll = directoryInfo.GetFiles("*.xlsx");
//循环转换所有Excel表格并刷新本地资源
for (int i = 0; i < FilesAll.Length; i++)
{
var ExcelTempPath = FilesAll[i].ToString();
var excelUtility = new ExcelUtility(ExcelTempPath);
excelUtility.ConvertToJsonForGameData(JsonPath, Encoding.GetEncoding("utf-8"), CSharpPath);
AssetDatabase.Refresh();
}
Debug.Log("Excel转换已完成");
}
else
{
Debug.Log("未找到Excel文件");
}
}
}
2 结果
现在用我们一开始的示例类验证一下工具是否可行
2.1 生成
Excel表
生成的C#脚本
生成的Json文件
2.2 使用
测试脚本
结果