feat: refactor audio generation and improve Excel handling
- Add GenAudioConsts.cs to generate AudioConsts.cs with Cues, NameDictionaries, and Parameters - Remove GenEnums.cs and its enum generation functions - Update GenModels.cs to remove AudioObjectDefinitions generation - Modify ExcelHelper.cs to improve numeric and formula cell handling - Update Program.cs to use GenAudioConsts for audio constants generation - Update README.md with improved validation checks and CueSheet generation status
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
using NPOI.XSSF.UserModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using ExcelTool.Parser;
|
||||
|
||||
@@ -158,13 +159,13 @@ namespace ExcelTool
|
||||
return cell.StringCellValue;
|
||||
|
||||
case CellType.Numeric:
|
||||
return DateUtil.IsCellDateFormatted(cell) ? cell.DateCellValue.ToString() : cell.NumericCellValue.ToString();
|
||||
return DateUtil.IsCellDateFormatted(cell) ? cell.DateCellValue.ToString() : cell.NumericCellValue.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
case CellType.Boolean:
|
||||
return cell.BooleanCellValue ? "1" : "0";
|
||||
|
||||
case CellType.Formula:
|
||||
return cell.ToString();
|
||||
return cell.StringCellValue;
|
||||
|
||||
default:
|
||||
return "";
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace ExcelTool.Parser
|
||||
{
|
||||
public static class GenAudioConsts
|
||||
{
|
||||
public static bool Gen(
|
||||
List<ParsedSheet> parsedSheets,
|
||||
List<ParsedEnum> enumSheets,
|
||||
string outputDir,
|
||||
string nameSpace = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
ParsedSheet audioSheet = parsedSheets?.Find(s => s.SheetName == "AudioObject");
|
||||
bool hasAudioObject = audioSheet != null;
|
||||
bool hasEnums = enumSheets is { Count: > 0 };
|
||||
|
||||
if (!hasAudioObject && !hasEnums) return true;
|
||||
|
||||
// 根据是否有 namespace 决定顶层缩进
|
||||
string i1 = string.IsNullOrEmpty(nameSpace) ? "" : "\t"; // class 级
|
||||
string i2 = i1 + "\t"; // class 成员级
|
||||
string i3 = i2 + "\t"; // 嵌套 class 成员级
|
||||
|
||||
StringBuilder sb = new();
|
||||
sb.Append("/*\n * auto generated by tools(注意:千万不要手动修改本文件)\n * AudioConsts\n */\n\n");
|
||||
sb.Append("using System.Collections.Generic;\nusing System.Diagnostics.CodeAnalysis;\n\n");
|
||||
|
||||
if (!string.IsNullOrEmpty(nameSpace))
|
||||
sb.Append($"namespace {nameSpace}\n{{\n");
|
||||
|
||||
// ── CUES ──────────────────────────────────────────────────────────
|
||||
if (hasAudioObject)
|
||||
{
|
||||
AppendCues(sb, audioSheet, i1, i2);
|
||||
sb.Append('\n');
|
||||
}
|
||||
|
||||
// ── NAME_DICTIONARIES ─────────────────────────────────────────────
|
||||
if (hasAudioObject)
|
||||
{
|
||||
AppendNameDictionaries(sb, audioSheet, i1, i2, i3);
|
||||
sb.Append('\n');
|
||||
}
|
||||
|
||||
// ── PARAMETERS ────────────────────────────────────────────────────
|
||||
if (hasEnums)
|
||||
{
|
||||
AppendParameters(sb, enumSheets, i1, i2, i3);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(nameSpace))
|
||||
sb.Append("}\n");
|
||||
|
||||
FileManager.WriteToFile(Path.Combine(outputDir, "AudioConsts.cs"), sb.ToString());
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.ToString().WriteErrorLine();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
// CUES
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private static void AppendCues(StringBuilder sb, ParsedSheet audioSheet, string i1, string i2)
|
||||
{
|
||||
int idIndex = audioSheet.Headers.FindIndex(h => h.FieldName == "Id");
|
||||
int cueNameIndex = audioSheet.Headers.FindIndex(h => h.FieldName == "CueName");
|
||||
|
||||
// 如果没有 CueName 字段,回退到 Name 字段(向后兼容)
|
||||
if (cueNameIndex < 0)
|
||||
{
|
||||
cueNameIndex = audioSheet.Headers.FindIndex(h => h.FieldName == "Name");
|
||||
if (cueNameIndex < 0)
|
||||
{
|
||||
// 两个字段都没有,跳过 CUES 生成
|
||||
sb.Append($"{i1}public class Cues\n{i1}{{\n");
|
||||
sb.Append($"{i1}}} //public class Cues\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, uint> cueNameToMinId = BuildNameToMinId(audioSheet, idIndex, cueNameIndex);
|
||||
|
||||
sb.AppendLine($"{i1}[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]");
|
||||
sb.Append($"{i1}public class Cues\n{i1}{{\n");
|
||||
foreach (KeyValuePair<string, uint> kv in cueNameToMinId)
|
||||
{
|
||||
string fieldName = SanitizeIdentifier(kv.Key);
|
||||
sb.Append($"{i2}public static uint {fieldName} = {kv.Value};\n");
|
||||
}
|
||||
sb.Append($"{i1}}} //public class Cues\n");
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
// NAME_DICTIONARIES
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private static void AppendNameDictionaries(
|
||||
StringBuilder sb, ParsedSheet audioSheet,
|
||||
string i1, string i2, string i3)
|
||||
{
|
||||
int idIndex = audioSheet.Headers.FindIndex(h => h.FieldName == "Id");
|
||||
int namesIndex = audioSheet.Headers.FindIndex(h => h.FieldName == "Name");
|
||||
|
||||
Dictionary<uint, List<string>> idToNames = new();
|
||||
Dictionary<string, List<uint>> nameToIds = new();
|
||||
|
||||
foreach (TableExcelRow row in audioSheet.Data.Rows)
|
||||
{
|
||||
if (idIndex < 0 || namesIndex < 0) continue;
|
||||
if (string.IsNullOrEmpty(row.StrList[idIndex])) continue;
|
||||
|
||||
uint id = Convert.ToUInt32(row.StrList[idIndex]);
|
||||
string rawNames = row.StrList[namesIndex];
|
||||
List<string> names = new(rawNames.Split(['|'], StringSplitOptions.RemoveEmptyEntries));
|
||||
|
||||
idToNames[id] = names;
|
||||
foreach (string name in names)
|
||||
{
|
||||
if (!nameToIds.TryGetValue(name, out List<uint> list))
|
||||
{
|
||||
list = [];
|
||||
nameToIds[name] = list;
|
||||
}
|
||||
list.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine($"{i1}[SuppressMessage(\"ReSharper\", \"ClassNeverInstantiated.Global\")]");
|
||||
sb.Append($"{i1}public class NameDictionaries\n{i1}{{\n");
|
||||
|
||||
// NameToId
|
||||
sb.Append($"{i2}public static readonly Dictionary<string, uint> NameToId = new()\n{i2}{{\n");
|
||||
foreach (var kv in nameToIds)
|
||||
{
|
||||
uint minId = uint.MaxValue;
|
||||
foreach (uint id in kv.Value)
|
||||
if (id < minId) minId = id;
|
||||
sb.Append($"{i3}{{ \"{kv.Key}\", {minId} }},\n");
|
||||
}
|
||||
sb.Append($"{i2}}};\n\n");
|
||||
|
||||
// AmbiguousNames
|
||||
sb.Append($"{i2}public static readonly HashSet<string> AmbiguousNames = new()\n{i2}{{\n");
|
||||
foreach (var kv in nameToIds)
|
||||
if (kv.Value.Count > 1)
|
||||
sb.Append($"{i3}\"{kv.Key}\",\n");
|
||||
sb.Append($"{i2}}};\n\n");
|
||||
|
||||
// SharedIdNames
|
||||
HashSet<string> sharedNames = [];
|
||||
foreach (var kv in idToNames)
|
||||
if (kv.Value.Count > 1)
|
||||
foreach (string name in kv.Value)
|
||||
sharedNames.Add(name);
|
||||
|
||||
sb.Append($"{i2}public static readonly HashSet<string> SharedIdNames = new()\n{i2}{{\n");
|
||||
foreach (string name in sharedNames)
|
||||
sb.Append($"{i3}\"{name}\",\n");
|
||||
sb.Append($"{i2}}};\n");
|
||||
|
||||
sb.Append($"{i1}}} //public class NameDictionaries\n");
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
// PARAMETERS
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private static void AppendParameters(
|
||||
StringBuilder sb, List<ParsedEnum> enumSheets,
|
||||
string i1, string i2, string i3)
|
||||
{
|
||||
sb.AppendLine($"{i1}[SuppressMessage(\"ReSharper\", \"ClassNeverInstantiated.Global\")]");
|
||||
sb.Append($"{i1}public class Parameters\n{i1}{{\n");
|
||||
|
||||
// EnumIds 嵌套静态类
|
||||
sb.Append($"{i2}public static class EnumIds\n{i2}{{\n");
|
||||
foreach (ParsedEnum parsedEnum in enumSheets)
|
||||
sb.Append($"{i3}public const uint {parsedEnum.EnumName}Id = {parsedEnum.Id};\n\n");
|
||||
|
||||
sb.Append($"\n{i3}public static void RegisterAllGameState()\n{i3}{{\n");
|
||||
foreach (ParsedEnum parsedEnum in enumSheets)
|
||||
sb.Append($"{i3}\tStateGroupRegistry.Register<{parsedEnum.EnumName}>({parsedEnum.Id});\n");
|
||||
sb.Append($"{i3}}}\n");
|
||||
sb.Append($"{i2}}}\n\n");
|
||||
|
||||
// enum 定义
|
||||
foreach (ParsedEnum parsedEnum in enumSheets)
|
||||
{
|
||||
sb.Append($"{i2}public enum {parsedEnum.EnumName}\n{i2}{{\n");
|
||||
foreach (ParsedEnumMember member in parsedEnum.Members)
|
||||
{
|
||||
string valuePart = member.Value.HasValue ? $" = {member.Value.Value}" : "";
|
||||
string descPart = string.IsNullOrEmpty(member.Desc) ? "" : $" // {member.Desc}";
|
||||
sb.Append($"{i3}{member.Key}{valuePart},{descPart}\n");
|
||||
}
|
||||
sb.Append($"{i2}}}\n\n");
|
||||
}
|
||||
|
||||
sb.Append($"{i1}}} //public class Parameters\n");
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
// 工具方法
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>遍历 AudioObject 行,构建 name -> minId 映射(处理资源复用)</summary>
|
||||
private static Dictionary<string, uint> BuildNameToMinId(
|
||||
ParsedSheet audioSheet, int idIndex, int namesIndex)
|
||||
{
|
||||
Dictionary<string, List<uint>> nameToIds = new();
|
||||
foreach (TableExcelRow row in audioSheet.Data.Rows)
|
||||
{
|
||||
if (idIndex < 0 || namesIndex < 0) continue;
|
||||
if (string.IsNullOrEmpty(row.StrList[idIndex])) continue;
|
||||
|
||||
uint id = Convert.ToUInt32(row.StrList[idIndex]);
|
||||
string rawNames = row.StrList[namesIndex];
|
||||
List<string> names = new(rawNames.Split(['|'], StringSplitOptions.RemoveEmptyEntries));
|
||||
|
||||
foreach (string name in names)
|
||||
{
|
||||
if (!nameToIds.TryGetValue(name, out List<uint> list))
|
||||
{
|
||||
list = [];
|
||||
nameToIds[name] = list;
|
||||
}
|
||||
list.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, uint> result = new();
|
||||
foreach (var kv in nameToIds)
|
||||
{
|
||||
uint minId = uint.MaxValue;
|
||||
foreach (uint id in kv.Value)
|
||||
if (id < minId) minId = id;
|
||||
result[kv.Key] = minId;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>将字符串中不合法的 C# 标识符字符替换为下划线</summary>
|
||||
private static string SanitizeIdentifier(string name)
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
foreach (char c in name)
|
||||
sb.Append(char.IsLetterOrDigit(c) || c == '_' ? c : '_');
|
||||
if (sb.Length > 0 && char.IsDigit(sb[0]))
|
||||
sb.Insert(0, '_');
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace ExcelTool.Parser
|
||||
{
|
||||
public static class GenEnums
|
||||
{
|
||||
/// <summary>
|
||||
/// 所有枚举写入同一个文件 AudioEnums.cs
|
||||
/// </summary>
|
||||
public static bool GenCSharpEnum(List<ParsedEnum> enumSheet, string outputDir, string nameSpace = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
sb.Append("/*\n * auto generated by tools(注意:千万不要手动修改本文件)\n * AudioEnums\n */\n\n");
|
||||
|
||||
if (!string.IsNullOrEmpty(nameSpace))
|
||||
sb.Append($"namespace {nameSpace}\n{{\n");
|
||||
|
||||
foreach (ParsedEnum parsedEnum in enumSheet)
|
||||
{
|
||||
sb.Append($"\tpublic enum {parsedEnum.EnumName}\n\t{{\n");
|
||||
|
||||
foreach (ParsedEnumMember member in parsedEnum.Members)
|
||||
{
|
||||
string valuePart = member.Value.HasValue ? $" = {member.Value.Value}" : "";
|
||||
string descPart = string.IsNullOrEmpty(member.Desc) ? "" : $" // {member.Desc}";
|
||||
sb.Append($"\t\t{member.Key}{valuePart},{descPart}\n");
|
||||
}
|
||||
|
||||
sb.Append("\t}\n\n");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(nameSpace))
|
||||
sb.Append("}\n");
|
||||
|
||||
FileManager.WriteToFile(Path.Combine(outputDir, "AudioEnums.cs"), sb.ToString());
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.ToString().WriteErrorLine();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将所有枚举的 Id 汇总,生成 EnumIds.cs 静态常量类
|
||||
/// </summary>
|
||||
public static bool GenEnumIds(List<ParsedEnum> enumSheet, string outputDir, string nameSpace = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
sb.Append("/*\n * auto generated by tools(注意:千万不要手动修改本文件)\n * EnumIds\n */\n\n");
|
||||
|
||||
if (!string.IsNullOrEmpty(nameSpace))
|
||||
sb.Append($"namespace {nameSpace}\n{{\n");
|
||||
|
||||
sb.Append("\tpublic static class EnumIds\n\t{\n");
|
||||
|
||||
foreach (ParsedEnum parsedEnum in enumSheet)
|
||||
sb.AppendLine($"\t\tpublic const uint {parsedEnum.EnumName} = {parsedEnum.Id};\n");
|
||||
|
||||
sb.AppendLine("\t\tpublic static void RegisterAllGameState()\n\t\t{");
|
||||
foreach (ParsedEnum parsedEnum in enumSheet)
|
||||
{
|
||||
sb.AppendLine($"\t\t\tStateGroupRegistry.Register<{parsedEnum.EnumName}>({parsedEnum.Id});");
|
||||
}
|
||||
sb.AppendLine("\t\t}");
|
||||
|
||||
sb.Append("\t}\n");
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(nameSpace))
|
||||
sb.Append("}\n");
|
||||
|
||||
FileManager.WriteToFile(Path.Combine(outputDir, "AudioEnumIds.cs"), sb.ToString());
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.ToString().WriteErrorLine();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,11 +44,6 @@ namespace ExcelTool.Parser
|
||||
|
||||
FileManager.WriteToFile(Path.Combine(outputDir, $"{sheet.SheetName}.cs"), sb.ToString());
|
||||
|
||||
// ── AudioObjectDefinitions 生成(仅针对 AudioObject 表) ─────────────────────
|
||||
if (sheet.SheetName == "AudioObject")
|
||||
{
|
||||
GenerateAudioObjectDefinitions(parsedSheets, outputDir, nameSpace);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
+5
-22
@@ -22,7 +22,6 @@ namespace ExcelTool
|
||||
rootCommand.AddOption(tablesOption);
|
||||
|
||||
rootCommand.SetHandler(ExcelProcess.Run, tablesOption);
|
||||
// TODO 单元测试
|
||||
|
||||
return await rootCommand.InvokeAsync(args);
|
||||
}
|
||||
@@ -53,8 +52,6 @@ namespace ExcelTool
|
||||
"== 说明:将exe放在xlsx目录中或者exe或者传入根目录 ==".WriteSuccessLine();
|
||||
"==========================================================".WriteSuccessLine();
|
||||
|
||||
// excels = dirInfo.GetFiles("*.xlsx", SearchOption.AllDirectories);
|
||||
|
||||
//读取
|
||||
foreach (TableEntry tableEntry in tableEntries)
|
||||
{
|
||||
@@ -89,21 +86,12 @@ namespace ExcelTool
|
||||
$"{fileName}CS模板生成失败".WriteErrorLine();
|
||||
}
|
||||
|
||||
// 生成CS枚举文件
|
||||
if (enumSheets.Count > 0)
|
||||
{
|
||||
bool enumRes = GenEnums.GenCSharpEnum(enumSheets, tableEntry.OutputCodeDir, tableEntry.Namespace);
|
||||
if (enumRes)
|
||||
$"{fileName} 枚举代码生成成功".WriteSuccessLine();
|
||||
// 生成 AudioConsts.cs(合并枚举、枚举ID、AudioObject定义和CUE映射)
|
||||
bool audioConstsRes = GenAudioConsts.Gen(sheets, enumSheets, tableEntry.OutputCodeDir, tableEntry.Namespace);
|
||||
if (audioConstsRes)
|
||||
$"{fileName} AudioConsts 生成成功".WriteSuccessLine();
|
||||
else
|
||||
$"{fileName} 枚举代码生成失败".WriteErrorLine();
|
||||
|
||||
bool enumIdsRes = GenEnums.GenEnumIds(enumSheets, tableEntry.OutputCodeDir, tableEntry.Namespace);
|
||||
if (enumIdsRes)
|
||||
$"{fileName} EnumIds 生成成功".WriteSuccessLine();
|
||||
else
|
||||
$"{fileName} EnumIds 生成失败".WriteErrorLine();
|
||||
}
|
||||
$"{fileName} AudioConsts 生成失败".WriteErrorLine();
|
||||
|
||||
//生成二进制文件,如果list或者vector数据为空则写入0,要根据类型来读取csv的字段数据强转成对应的数据类型然后写入
|
||||
res = TableExcelExportBytes.ExportToFile(sheets, tableEntry.OutputDataDir);
|
||||
@@ -118,11 +106,6 @@ namespace ExcelTool
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Test()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -285,11 +285,13 @@ pause
|
||||
* [x] 支持枚举类型
|
||||
* [x] 支持不同表不同namespace
|
||||
* [ ] 支持Excel数据配置规范性检测,例如手误配置不符合规范导致加载异常,例如大小写逗号(肉眼容易忽略),或者空格等等
|
||||
* [ ] ID不能重复
|
||||
* [ ] Music、Audio Container不能自引用
|
||||
* [ ] 同时播放的音乐BPM必须一致
|
||||
* [ ] Blend容器不能配置Haptic ID
|
||||
* [ ] 生成CueSheet避免magic number
|
||||
* [ ] ID不能重复 给出错误
|
||||
* [ ] Music、Audio Container不能自引用 给出错误
|
||||
* [ ] 同时播放的音乐BPM必须一致 给出错误
|
||||
* [ ] CueName不能包含空格` `,和`-`,不能以数字开头。给出警告
|
||||
* [ ] Blend容器不能配置Haptic ID 给出警告
|
||||
* [ ] SyncPoint功能性需要两个文件BPM、采样率皆一致。给出警告
|
||||
* [x] 生成CueSheet避免magic number
|
||||
* [ ] AudioObject支持Container嵌套
|
||||
|
||||
#### Unity客户端使用范例
|
||||
|
||||
Reference in New Issue
Block a user