Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 19dffd6c7f | |||
| 20b0f65e6a | |||
| 79e6cd2a53 | |||
| 493ecfeb11 | |||
| b0afff73ac | |||
| fb7beedbad | |||
| 6f2cc57eac | |||
| 965998b56f | |||
| 0f32860526 | |||
| 424178719b |
@@ -0,0 +1,66 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
## Build & Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet build # .NET 10.0 SDK (pinned in global.json)
|
||||||
|
dotnet run -- --tables path/to/__tables__.xlsx # pass tables config
|
||||||
|
```
|
||||||
|
|
||||||
|
The tool is a single-project console app (`ExcelTool.csproj` → `ExcelTool.sln`). No tests, no CI.
|
||||||
|
|
||||||
|
## Excel Sheet Convention
|
||||||
|
|
||||||
|
| Row (0-indexed) | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| 0 | Field names |
|
||||||
|
| 1 | Field types (case-insensitive) |
|
||||||
|
| 2 | Field descriptions (→ XML doc comments) |
|
||||||
|
| 5+ | Data rows |
|
||||||
|
|
||||||
|
- Sheets whose name starts with `#` are skipped entirely.
|
||||||
|
- The sheet named `__enums__` is parsed specially — not as a data table but as enum definitions.
|
||||||
|
- Empty data rows are skipped automatically.
|
||||||
|
|
||||||
|
## The `__tables__.xlsx` Master Config
|
||||||
|
|
||||||
|
Column layout (row 0-3 are ignored; data starts at row 4, 0-indexed):
|
||||||
|
|
||||||
|
| Col | Field | Example |
|
||||||
|
|---|---|---|
|
||||||
|
| A | Input .xlsx path | `AudioConfigs.xlsx` |
|
||||||
|
| B | Namespace | `OCES.Audio` |
|
||||||
|
| C | Output code dir | `../hola_unity/Assets/Src/Config/` |
|
||||||
|
| D | Output data dir | `../hola_unity/Assets/Resources/Config/` |
|
||||||
|
|
||||||
|
Paths in the config are relative to the directory containing `__tables__.xlsx` unless rooted.
|
||||||
|
|
||||||
|
## Type System
|
||||||
|
|
||||||
|
All supported types are registered in `TypeRegistry.cs`. To add a new type, add a `Register(...)` call in `RegisterAll()`. The registry drives both binary serialization and C# code generation — one change fixes both.
|
||||||
|
|
||||||
|
Supported types: `int`, `uint`, `short`, `ushort`, `sbyte`, `byte`, `float`, `double`, `long`, `bool`, `string`, `vector` (→ `List<float>`, 3 components), `vectorlist` (→ `List<List<float>>`), `xxxlist` (e.g., `intlist`), `list<T>` generic syntax.
|
||||||
|
|
||||||
|
Any type **not** in the registry is treated as an enum and serialized as a single `byte`.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
Per parsed sheet, three artifacts are generated:
|
||||||
|
1. `{SheetName}.cs` — data model class + `{SheetName}Config` container (both `partial`, implementing `IBinarySerializable`)
|
||||||
|
2. `AudioConsts.cs` — enums/Cues/NameDictionaries (only if `AudioObject` sheet or `__enums__` sheet exists)
|
||||||
|
3. `{SheetName}.bytes` — binary serialized data for Unity `Resources.Load<TextAsset>`
|
||||||
|
|
||||||
|
The first field of every data sheet is assumed to be `Id` (used as dictionary key in `*Config.QueryById()`).
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
|---|---|
|
||||||
|
| `Program.cs` | CLI entrypoint (`--tables` option) |
|
||||||
|
| `TablesConfig.cs` | Parses `__tables__.xlsx` into `TableEntry` list |
|
||||||
|
| `ExcelHelper.cs` | Parses individual .xlsx → `ParsedSheet` / `ParsedEnum` |
|
||||||
|
| `TypeRegistry.cs` | Central type registry (binary write + codegen) |
|
||||||
|
| `Parser/GenModels.cs` | Generates `{SheetName}.cs` |
|
||||||
|
| `Parser/GenAudioConsts.cs` | Generates `AudioConsts.cs` |
|
||||||
|
| `Parser/TableExcelExportBytes.cs` | Generates `.bytes` files |
|
||||||
|
| `FileManager.cs` | File I/O utilities |
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
using NPOI.XSSF.UserModel;
|
using NPOI.XSSF.UserModel;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using ExcelTool.Parser;
|
using ExcelTool.Parser;
|
||||||
|
|
||||||
@@ -82,6 +83,10 @@ namespace ExcelTool
|
|||||||
IRow row = sheet.GetRow(i);
|
IRow row = sheet.GetRow(i);
|
||||||
if (row == null) continue;
|
if (row == null) continue;
|
||||||
|
|
||||||
|
|
||||||
|
// ID为空则跳过该行
|
||||||
|
if (string.IsNullOrEmpty(GetCellValue(row.GetCell(0)))) continue;
|
||||||
|
|
||||||
TableExcelRow tableExcelRow = new();
|
TableExcelRow tableExcelRow = new();
|
||||||
for (int j = 0; j < headers.Count; j++)
|
for (int j = 0; j < headers.Count; j++)
|
||||||
tableExcelRow.Add(GetCellValue(row.GetCell(j)));
|
tableExcelRow.Add(GetCellValue(row.GetCell(j)));
|
||||||
@@ -147,7 +152,7 @@ namespace ExcelTool
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static string GetCellValue(ICell cell)
|
internal static string GetCellValue(ICell cell)
|
||||||
{
|
{
|
||||||
if (cell == null)
|
if (cell == null)
|
||||||
return "";
|
return "";
|
||||||
@@ -158,13 +163,13 @@ namespace ExcelTool
|
|||||||
return cell.StringCellValue;
|
return cell.StringCellValue;
|
||||||
|
|
||||||
case CellType.Numeric:
|
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:
|
case CellType.Boolean:
|
||||||
return cell.BooleanCellValue ? "1" : "0";
|
return cell.BooleanCellValue ? "1" : "0";
|
||||||
|
|
||||||
case CellType.Formula:
|
case CellType.Formula:
|
||||||
return cell.ToString();
|
return cell.StringCellValue;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
@@ -12,8 +12,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="NPOI" Version="2.7.6" />
|
<PackageReference Include="NPOI" Version="2.7.6" />
|
||||||
<PackageReference Include="Spire.XLS" Version="12.7.1" />
|
|
||||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||||
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
|
<PackageReference Include="System.Security.Cryptography.Xml" Version="10.0.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -91,7 +91,7 @@ namespace ExcelTool
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var parts = value.Split(',');
|
var parts = value.Split('|');
|
||||||
bw.Write(parts.Length);
|
bw.Write(parts.Length);
|
||||||
foreach (var p in parts)
|
foreach (var p in parts)
|
||||||
elemDesc.WriteBinary(bw, p);
|
elemDesc.WriteBinary(bw, p);
|
||||||
|
|||||||
@@ -0,0 +1,267 @@
|
|||||||
|
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]);
|
||||||
|
if (id == 0) continue; // 跳过ID=0的行
|
||||||
|
|
||||||
|
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]);
|
||||||
|
if (id == 0) continue; // 跳过ID=0的行
|
||||||
|
|
||||||
|
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,83 +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> enumSheets, 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 enumSheet in enumSheets)
|
|
||||||
sb.Append($"\t\tpublic const uint {enumSheet.EnumName} = {enumSheet.Id};\n");
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -43,6 +43,7 @@ namespace ExcelTool.Parser
|
|||||||
sb.Append("}\n");
|
sb.Append("}\n");
|
||||||
|
|
||||||
FileManager.WriteToFile(Path.Combine(outputDir, $"{sheet.SheetName}.cs"), sb.ToString());
|
FileManager.WriteToFile(Path.Combine(outputDir, $"{sheet.SheetName}.cs"), sb.ToString());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -161,7 +162,7 @@ namespace ExcelTool.Parser
|
|||||||
sb.Append($"\tList<{name}> m_{camel}InfoList;\n\n");
|
sb.Append($"\tList<{name}> m_{camel}InfoList;\n\n");
|
||||||
|
|
||||||
sb.Append($"\tpublic List<{name}> {name}List()\n\t{{\n");
|
sb.Append($"\tpublic List<{name}> {name}List()\n\t{{\n");
|
||||||
sb.Append($"\t\tthis.m_{camel}InfoList ??= new List<{name}>(m_{camel}Infos.Values);\n");
|
sb.Append($"\t\tthis.m_{camel}InfoList ??= new List<{name}>(this.m_{camel}Infos.Values);\n");
|
||||||
sb.Append($"\t\treturn this.m_{camel}InfoList;\n\t}}\n\n");
|
sb.Append($"\t\treturn this.m_{camel}InfoList;\n\t}}\n\n");
|
||||||
|
|
||||||
sb.Append($"\tpublic void DeSerialize(BinaryReader reader)\n\t{{\n");
|
sb.Append($"\tpublic void DeSerialize(BinaryReader reader)\n\t{{\n");
|
||||||
@@ -183,6 +184,101 @@ namespace ExcelTool.Parser
|
|||||||
sb.Append("\t}\n}\n");
|
sb.Append("\t}\n}\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void GenerateAudioObjectDefinitions(List<ParsedSheet> parsedSheets, string outputDir, string nameSpace)
|
||||||
|
{
|
||||||
|
ParsedSheet audioSheet = parsedSheets.Find(s => s.SheetName == "AudioObject");
|
||||||
|
if (audioSheet == null) return;
|
||||||
|
|
||||||
|
Dictionary<uint, List<string>> idToNames = new();
|
||||||
|
Dictionary<string, List<uint>> nameToIds = new();
|
||||||
|
|
||||||
|
int idIndex = audioSheet.Headers.FindIndex(h => h.FieldName == "Id");
|
||||||
|
int namesIndex = audioSheet.Headers.FindIndex(h => h.FieldName == "Name");
|
||||||
|
|
||||||
|
foreach (TableExcelRow row in audioSheet.Data.Rows)
|
||||||
|
{
|
||||||
|
// 根据 Headers 找到列索引
|
||||||
|
if (idIndex < 0 || namesIndex < 0) continue;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(row.StrList[idIndex])) continue;
|
||||||
|
|
||||||
|
uint id = Convert.ToUInt32(row.StrList[idIndex]);
|
||||||
|
if (id == 0) continue; // 跳过ID=0的行
|
||||||
|
|
||||||
|
// Names 是用分隔符(例如 '|')拼接的字符串
|
||||||
|
string rawNames = row.StrList[namesIndex];
|
||||||
|
List<string> names = new(rawNames.Split(['|'], StringSplitOptions.RemoveEmptyEntries));
|
||||||
|
|
||||||
|
idToNames[id] = names; // 同一个ID对应了多少个不同的名字(Container)
|
||||||
|
|
||||||
|
foreach (string name in names)
|
||||||
|
{
|
||||||
|
if (!nameToIds.TryGetValue(name, out List<uint> list))
|
||||||
|
{
|
||||||
|
list = [];
|
||||||
|
nameToIds[name] = list;
|
||||||
|
}
|
||||||
|
list.Add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new();
|
||||||
|
sb.Append("/* auto generated, do not modify */\n");
|
||||||
|
sb.Append("using System.Collections.Generic;\n\n");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(nameSpace))
|
||||||
|
sb.Append($"namespace {nameSpace}\n{{\n");
|
||||||
|
|
||||||
|
sb.Append("public static class AudioObjectDefinitions\n{\n");
|
||||||
|
|
||||||
|
// NameToId
|
||||||
|
sb.Append("\tpublic static readonly Dictionary<string, uint> NameToId = new()\n\t{\n");
|
||||||
|
foreach (var kv in nameToIds)
|
||||||
|
{
|
||||||
|
uint minId = uint.MaxValue;
|
||||||
|
foreach (uint id in kv.Value)
|
||||||
|
if (id < minId) minId = id;
|
||||||
|
|
||||||
|
sb.Append($"\t\t{{ \"{kv.Key}\", {minId} }},\n");
|
||||||
|
}
|
||||||
|
sb.Append("\t};\n\n");
|
||||||
|
|
||||||
|
// AmbiguousNames
|
||||||
|
sb.Append("\tpublic static readonly HashSet<string> AmbiguousNames = new()\n\t{\n");
|
||||||
|
foreach (var kv in nameToIds)
|
||||||
|
{
|
||||||
|
if (kv.Value.Count > 1)
|
||||||
|
sb.Append($"\t\t\"{kv.Key}\",\n");
|
||||||
|
}
|
||||||
|
sb.Append("\t};\n\n");
|
||||||
|
|
||||||
|
// SharedIdNames
|
||||||
|
HashSet<string> sharedNames = [];
|
||||||
|
foreach (KeyValuePair<uint, List<string>> keyValuePair in idToNames)
|
||||||
|
{
|
||||||
|
if (keyValuePair.Value.Count > 1)
|
||||||
|
{
|
||||||
|
foreach (string name in keyValuePair.Value)
|
||||||
|
sharedNames.Add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append("\tpublic static readonly HashSet<string> SharedIdNames = new()\n\t{\n");
|
||||||
|
|
||||||
|
foreach (string name in sharedNames)
|
||||||
|
{
|
||||||
|
sb.Append($"\t\t\"{name}\",\n");
|
||||||
|
}
|
||||||
|
sb.Append("\t};\n");
|
||||||
|
|
||||||
|
sb.Append("}\n");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(nameSpace))
|
||||||
|
sb.Append("}\n");
|
||||||
|
|
||||||
|
FileManager.WriteToFile(Path.Combine(outputDir, "AudioObjectDefinitions.cs"), sb.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
// ──────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
// 工具方法
|
// 工具方法
|
||||||
// ──────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace ExcelTool.Parser
|
namespace ExcelTool.Parser
|
||||||
{
|
{
|
||||||
public class ParsedEnumMember
|
public class ParsedEnumMember
|
||||||
{
|
{
|
||||||
/// <summary>成员名,如 "Home"</summary>
|
public string Key
|
||||||
public string Key { get; set; }
|
{
|
||||||
|
get;
|
||||||
|
set { field = ParsedEnum.NormalizeIdentifier(value); }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>显式值,为 null 时代码生成时自动递增(不写值)</summary>
|
/// <summary>显式值,为 null 时代码生成时自动递增(不写值)</summary>
|
||||||
public int? Value { get; set; }
|
public int? Value { get; set; }
|
||||||
@@ -19,9 +24,43 @@ namespace ExcelTool.Parser
|
|||||||
/// <summary>A 列 Id,供外部系统通过 EnumIds 常量类引用</summary>
|
/// <summary>A 列 Id,供外部系统通过 EnumIds 常量类引用</summary>
|
||||||
public uint Id { get; set; }
|
public uint Id { get; set; }
|
||||||
|
|
||||||
/// <summary>枚举类型名,如 "GameState"</summary>
|
public string EnumName
|
||||||
public string EnumName { get; set; }
|
{
|
||||||
|
get;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
field = NormalizeIdentifier(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public List<ParsedEnumMember> Members { get; set; } = new();
|
public static string NormalizeIdentifier(string input)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] parts = Regex.Split(input, @"[\s_-]+")
|
||||||
|
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
for (int i = 0; i < parts.Length; i++)
|
||||||
|
{
|
||||||
|
string part = parts[i];
|
||||||
|
|
||||||
|
if (part.Length == 1)
|
||||||
|
{
|
||||||
|
parts[i] = char.ToUpperInvariant(part[0]).ToString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parts[i] = char.ToUpperInvariant(part[0]) + part.Substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Concat(parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ParsedEnumMember> Members { get; set; } = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,7 @@ namespace ExcelTool.Parser
|
|||||||
{
|
{
|
||||||
public class TableExcelRow
|
public class TableExcelRow
|
||||||
{
|
{
|
||||||
public List<string> StrList { get; set; }
|
public List<string> StrList { get; set; } = [];
|
||||||
public TableExcelRow()
|
|
||||||
{
|
|
||||||
StrList = new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(string str)
|
public void Add(string str)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
namespace ExcelTool.Parser
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 导出格式
|
|
||||||
/// </summary>
|
|
||||||
public enum TableExportFormat
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 未知
|
|
||||||
/// </summary>
|
|
||||||
Unknown = 0,
|
|
||||||
/// <summary>
|
|
||||||
/// 二进制
|
|
||||||
/// </summary>
|
|
||||||
Bytes,
|
|
||||||
/// <summary>
|
|
||||||
/// Json格式
|
|
||||||
/// </summary>
|
|
||||||
Json,
|
|
||||||
/// <summary>
|
|
||||||
/// Xml格式
|
|
||||||
/// </summary>
|
|
||||||
Xml,
|
|
||||||
/// <summary>
|
|
||||||
/// Lua格式
|
|
||||||
/// </summary>
|
|
||||||
Lua,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+56
-68
@@ -12,36 +12,16 @@ namespace ExcelTool
|
|||||||
{
|
{
|
||||||
static async Task<int> Main(string[] args)
|
static async Task<int> Main(string[] args)
|
||||||
{
|
{
|
||||||
Option<DirectoryInfo> inputOption = new(
|
Option<FileInfo> tablesOption = new(
|
||||||
name: "--input",
|
name: "--tables",
|
||||||
description: "Excel 文件所在目录",
|
description: "__tables__.xlsx 的路径",
|
||||||
getDefaultValue: () => new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory)
|
getDefaultValue: () => new FileInfo(AppDomain.CurrentDomain.BaseDirectory)
|
||||||
);
|
);
|
||||||
|
|
||||||
Option<DirectoryInfo> outputCodeDirOption = new(
|
|
||||||
name: "--outputCodeDir",
|
|
||||||
description: "CS 模板代码输出目录"
|
|
||||||
);
|
|
||||||
|
|
||||||
Option<DirectoryInfo> outputDataDirOption = new(
|
|
||||||
name: "--outputDataDir",
|
|
||||||
description: "二进制数据输出目录"
|
|
||||||
);
|
|
||||||
|
|
||||||
Option<string> namespaceOption = new(
|
|
||||||
name: "--namespace",
|
|
||||||
description: "生成代码的命名空间",
|
|
||||||
getDefaultValue: () => ""
|
|
||||||
);
|
|
||||||
|
|
||||||
RootCommand rootCommand = new();
|
RootCommand rootCommand = new();
|
||||||
rootCommand.AddOption(inputOption);
|
rootCommand.AddOption(tablesOption);
|
||||||
rootCommand.AddOption(outputCodeDirOption);
|
|
||||||
rootCommand.AddOption(outputDataDirOption);
|
|
||||||
rootCommand.AddOption(namespaceOption);
|
|
||||||
|
|
||||||
rootCommand.SetHandler(ExcelProcess.Run, inputOption, outputCodeDirOption, outputDataDirOption, namespaceOption);
|
rootCommand.SetHandler(ExcelProcess.Run, tablesOption);
|
||||||
// TODO 单元测试
|
|
||||||
|
|
||||||
return await rootCommand.InvokeAsync(args);
|
return await rootCommand.InvokeAsync(args);
|
||||||
}
|
}
|
||||||
@@ -49,18 +29,21 @@ namespace ExcelTool
|
|||||||
|
|
||||||
static class ExcelProcess
|
static class ExcelProcess
|
||||||
{
|
{
|
||||||
public static void Run(DirectoryInfo inputDir, DirectoryInfo? outputCodeDir, DirectoryInfo? outputDataDir,
|
public static void Run(FileInfo tablesFile)
|
||||||
string nameSpace)
|
|
||||||
{
|
{
|
||||||
string path = inputDir.FullName;
|
if(!tablesFile.Exists)
|
||||||
string codeDir = outputCodeDir?.FullName ?? outputDataDir?.FullName ?? path;
|
|
||||||
string dataDir = outputDataDir?.FullName ?? outputCodeDir?.FullName ?? path;
|
|
||||||
|
|
||||||
DirectoryInfo dirInfo = new(path);
|
|
||||||
FileInfo[] excels = dirInfo.GetFiles("*.xlsx", SearchOption.AllDirectories);
|
|
||||||
if (excels.Length <= 0)
|
|
||||||
{
|
{
|
||||||
"当前exe目录或者目标目录没有excels文件,请重新设置目录".WriteErrorLine();
|
$"未在 {tablesFile} 处找到文件!".WriteErrorLine();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string path = tablesFile.FullName;
|
||||||
|
List<TableEntry> tableEntries = TablesConfig.Parse(path);
|
||||||
|
|
||||||
|
if (tableEntries.Count <= 0)
|
||||||
|
{
|
||||||
|
"__tables__.xlsx内没有数据".WriteErrorLine();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -69,51 +52,56 @@ namespace ExcelTool
|
|||||||
"== 说明:将exe放在xlsx目录中或者exe或者传入根目录 ==".WriteSuccessLine();
|
"== 说明:将exe放在xlsx目录中或者exe或者传入根目录 ==".WriteSuccessLine();
|
||||||
"==========================================================".WriteSuccessLine();
|
"==========================================================".WriteSuccessLine();
|
||||||
|
|
||||||
excels = dirInfo.GetFiles("*.xlsx", SearchOption.AllDirectories);
|
|
||||||
|
|
||||||
//读取
|
//读取
|
||||||
foreach (FileInfo file in excels)
|
foreach (TableEntry tableEntry in tableEntries)
|
||||||
{
|
{
|
||||||
if (file.Name.StartsWith("~$")) continue;
|
if (!Path.Exists(tableEntry.InputFile))
|
||||||
|
|
||||||
List<ParsedSheet> sheets = ExcelHelper.ParseAllSheets(file.FullName, out List<ParsedEnum>? enumSheets);
|
|
||||||
|
|
||||||
//生成CS文件
|
|
||||||
bool res = GenModels.GenCSharpModel(sheets, codeDir, nameSpace);
|
|
||||||
if (res)
|
|
||||||
{
|
{
|
||||||
$"{file.Name}CS模板生成成功".WriteSuccessLine();
|
$"{tableEntry.InputFile} 不存在!".WriteWarningLine();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (!Path.Exists(tableEntry.OutputCodeDir))
|
||||||
{
|
{
|
||||||
$"{file.Name}CS模板生成失败".WriteErrorLine();
|
Directory.CreateDirectory(tableEntry.OutputCodeDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Path.Exists(tableEntry.OutputDataDir))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(tableEntry.OutputDataDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成CS枚举文件
|
string fileName = Path.GetFileName(tableEntry.InputFile);
|
||||||
if (enumSheets.Count > 0)
|
|
||||||
{
|
List<ParsedSheet> sheets = ExcelHelper.ParseAllSheets(tableEntry.InputFile, out List<ParsedEnum>? enumSheets);
|
||||||
bool enumRes = GenEnums.GenCSharpEnum(enumSheets, codeDir, nameSpace);
|
|
||||||
if (enumRes)
|
|
||||||
$"{file.Name} 枚举代码生成成功".WriteSuccessLine();
|
|
||||||
else
|
|
||||||
$"{file.Name} 枚举代码生成失败".WriteErrorLine();
|
|
||||||
|
|
||||||
bool enumIdsRes = GenEnums.GenEnumIds(enumSheets, codeDir, nameSpace);
|
//生成CS文件
|
||||||
if (enumIdsRes)
|
bool res = GenModels.GenCSharpModel(sheets, tableEntry.OutputCodeDir, tableEntry.Namespace);
|
||||||
$"{file.Name} EnumIds 生成成功".WriteSuccessLine();
|
|
||||||
else
|
|
||||||
$"{file.Name} EnumIds 生成失败".WriteErrorLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
//生成二进制文件,如果list或者vector数据为空则写入0,要根据类型来读取csv的字段数据强转成对应的数据类型然后写入
|
|
||||||
res = TableExcelExportBytes.ExportToFile(sheets, dataDir);
|
|
||||||
if (res)
|
if (res)
|
||||||
{
|
{
|
||||||
$"{file.Name}二进制数据生成成功".WriteSuccessLine();
|
$"{fileName}CS模板生成成功".WriteSuccessLine();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$"{file.Name}二进制数据生成失败".WriteErrorLine();
|
$"{fileName}CS模板生成失败".WriteErrorLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成 AudioConsts.cs(合并枚举、枚举ID、AudioObject定义和CUE映射)
|
||||||
|
bool audioConstsRes = GenAudioConsts.Gen(sheets, enumSheets, tableEntry.OutputCodeDir, tableEntry.Namespace);
|
||||||
|
if (audioConstsRes)
|
||||||
|
$"{fileName} AudioConsts 生成成功".WriteSuccessLine();
|
||||||
|
else
|
||||||
|
$"{fileName} AudioConsts 生成失败".WriteErrorLine();
|
||||||
|
|
||||||
|
//生成二进制文件,如果list或者vector数据为空则写入0,要根据类型来读取csv的字段数据强转成对应的数据类型然后写入
|
||||||
|
res = TableExcelExportBytes.ExportToFile(sheets, tableEntry.OutputDataDir);
|
||||||
|
if (res)
|
||||||
|
{
|
||||||
|
$"{fileName}二进制数据生成成功".WriteSuccessLine();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$"{fileName}二进制数据生成失败".WriteErrorLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using NPOI.SS.UserModel;
|
||||||
|
using NPOI.XSSF.UserModel;
|
||||||
|
|
||||||
|
namespace ExcelTool;
|
||||||
|
|
||||||
|
public static class TablesConfig
|
||||||
|
{
|
||||||
|
internal static List<TableEntry> Parse(string tablesXlsxPath)
|
||||||
|
{
|
||||||
|
List<TableEntry> tableEntries = [];
|
||||||
|
|
||||||
|
string tablesDir = Path.GetDirectoryName(Path.GetFullPath(tablesXlsxPath))!;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using FileStream fs = File.OpenRead(tablesXlsxPath);
|
||||||
|
XSSFWorkbook workbook = new(fs);
|
||||||
|
|
||||||
|
ISheet sheet = workbook.GetSheetAt(0);
|
||||||
|
|
||||||
|
for (int i = 4; i <= sheet.LastRowNum; i++)
|
||||||
|
{
|
||||||
|
IRow row = sheet.GetRow(i);
|
||||||
|
if (row == null || string.IsNullOrEmpty(ExcelHelper.GetCellValue(row.GetCell(0)))) continue;
|
||||||
|
|
||||||
|
TableEntry tableEntry = new()
|
||||||
|
{
|
||||||
|
InputFile = ResolveDir(ExcelHelper.GetCellValue(row.GetCell(0))),
|
||||||
|
Namespace = ExcelHelper.GetCellValue(row.GetCell(1)),
|
||||||
|
OutputCodeDir = ResolveDir(ExcelHelper.GetCellValue(row.GetCell(2))),
|
||||||
|
OutputDataDir = ResolveDir(ExcelHelper.GetCellValue(row.GetCell(3))),
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
tableEntries.Add(tableEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
exception.ToString().WriteErrorLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableEntries;
|
||||||
|
|
||||||
|
string ResolveDir(string raw)
|
||||||
|
{
|
||||||
|
if (raw != null)
|
||||||
|
{
|
||||||
|
return Path.IsPathRooted(raw)
|
||||||
|
? raw
|
||||||
|
: Path.GetFullPath(Path.Combine(tablesDir, raw));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TableEntry
|
||||||
|
{
|
||||||
|
public string InputFile { get; init; } // e.g. "AudioConfigs.xlsx"
|
||||||
|
public string Namespace { get; init; } // e.g. "OCES.Audio"
|
||||||
|
public string OutputCodeDir { get; init; }
|
||||||
|
public string OutputDataDir { get; init; }
|
||||||
|
}
|
||||||
@@ -182,7 +182,7 @@ namespace ExcelTool
|
|||||||
WriteBinary = (bw, v) =>
|
WriteBinary = (bw, v) =>
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(v)) { bw.Write(0); return; }
|
if (string.IsNullOrEmpty(v)) { bw.Write(0); return; }
|
||||||
var parts = v.Split(',');
|
var parts = v.Split('|');
|
||||||
bw.Write(parts.Length);
|
bw.Write(parts.Length);
|
||||||
foreach (var p in parts) writeElem(bw, p);
|
foreach (var p in parts) writeElem(bw, p);
|
||||||
},
|
},
|
||||||
@@ -226,9 +226,9 @@ namespace ExcelTool
|
|||||||
"\t\telse\n" +
|
"\t\telse\n" +
|
||||||
"\t\t{\n" +
|
"\t\t{\n" +
|
||||||
$"\t\t\twriter.Write({name}.Count);\n" +
|
$"\t\t\twriter.Write({name}.Count);\n" +
|
||||||
$"\t\t\tfor (int i = 0; i < {name}.Count; i++)\n" +
|
$"\t\t\tforeach (var t in {name})\n" +
|
||||||
"\t\t\t{\n" +
|
"\t\t\t{\n" +
|
||||||
$"\t\t\t\twriter.Write({name}[i]);\n" +
|
$"\t\t\t\twriter.Write(t);\n" +
|
||||||
"\t\t\t}\n" +
|
"\t\t\t}\n" +
|
||||||
"\t\t}\n";
|
"\t\t}\n";
|
||||||
}
|
}
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
Copyright 2026 Oliver Wong
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -1,11 +1,6 @@
|
|||||||
# ExcelTool(Unity打表工具)
|
# ExcelTool
|
||||||
|
|
||||||
通用Excel打表工具
|
|
||||||
|
|
||||||
#### 前言
|
|
||||||
|
|
||||||
目前项目中用的csv转json工具,会带来严重的gc性能问题,例如启动加载慢,影响帧率等问题,转成的json数据,我们需要手动写对应的解析模板代码,所以针对以上问题,本通用打表工具应运而生,用二进制数据替代json数据会比较好的解决性能问题,而且能够自动生成对应的模板代码,节省了写重复代码的时间,而且二进制数据比json读取速度更快,文件大小更小。
|
|
||||||
|
|
||||||
|
本项目基于 https://github.com/dingxiaowei/ExcelTool 开发。用于 [AudioSystem](https://github.com/htw128/UnityAudioExtend), [HapticSystem](https://github.com/htw128/UnityAudioExtend#Haptic) 导出数据使用。
|
||||||
|
|
||||||
#### 功能
|
#### 功能
|
||||||
|
|
||||||
@@ -22,16 +17,9 @@
|
|||||||
* 支持自定义字段 vector,例如[1,2,3]
|
* 支持自定义字段 vector,例如[1,2,3]
|
||||||
* 支持自定义字段 list,其实是vector数组,例如[[1,2,3],[2,3,4],[4,5,6]]
|
* 支持自定义字段 list,其实是vector数组,例如[[1,2,3],[2,3,4],[4,5,6]]
|
||||||
|
|
||||||
#### 效果
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
相比较json,二进制文件大小只有其1/4
|
|
||||||
|
|
||||||
##### 自动生成的模板代码
|
##### 自动生成的模板代码
|
||||||
|
|
||||||
```
|
```c#
|
||||||
/*
|
/*
|
||||||
* auto generated by tools(注意:千万不要手动修改本文件)
|
* auto generated by tools(注意:千万不要手动修改本文件)
|
||||||
* avatarguideTest
|
* avatarguideTest
|
||||||
@@ -197,7 +185,7 @@ public partial class avatarguideTestConfig : IBinarySerializable
|
|||||||
|
|
||||||
##### 解析二进制文件
|
##### 解析二进制文件
|
||||||
|
|
||||||
```
|
```c#
|
||||||
IBinarySerializable newavList = new avatarguideTestList();
|
IBinarySerializable newavList = new avatarguideTestList();
|
||||||
var readOK = FileManager.ReadBinaryDataFromFile(Path.Combine(path, "avatarguideTest.dat"), ref newavList);
|
var readOK = FileManager.ReadBinaryDataFromFile(Path.Combine(path, "avatarguideTest.dat"), ref newavList);
|
||||||
if (readOK)
|
if (readOK)
|
||||||
@@ -216,7 +204,7 @@ else
|
|||||||
|
|
||||||
将二进制文件(后缀必须是.bytes)放在Resources目录,然后通过Resources.Load接口加载
|
将二进制文件(后缀必须是.bytes)放在Resources目录,然后通过Resources.Load接口加载
|
||||||
|
|
||||||
```
|
```c#
|
||||||
var bytes = Resources.Load<TextAsset>("avatarguideTest");
|
var bytes = Resources.Load<TextAsset>("avatarguideTest");
|
||||||
if (bytes == null)
|
if (bytes == null)
|
||||||
{
|
{
|
||||||
@@ -242,7 +230,7 @@ else
|
|||||||
|
|
||||||
#### 泛型读表
|
#### 泛型读表
|
||||||
|
|
||||||
```
|
```c#
|
||||||
T ReadTable<T>(string tableName) where T : IBinarySerializable, new()
|
T ReadTable<T>(string tableName) where T : IBinarySerializable, new()
|
||||||
{
|
{
|
||||||
var bytes = LoadTextAsset(tableName);
|
var bytes = LoadTextAsset(tableName);
|
||||||
@@ -265,7 +253,7 @@ return default(T);
|
|||||||
|
|
||||||
##### 查询数据
|
##### 查询数据
|
||||||
|
|
||||||
```
|
```c#
|
||||||
var avatarguideTest = (avatarguideTestConfig)newavList;
|
var avatarguideTest = (avatarguideTestConfig)newavList;
|
||||||
var obj = avatarguideTest.QueryById(2);
|
var obj = avatarguideTest.QueryById(2);
|
||||||
if (obj != null && obj.Count() > 0)
|
if (obj != null && obj.Count() > 0)
|
||||||
@@ -274,7 +262,7 @@ if (obj != null && obj.Count() > 0)
|
|||||||
|
|
||||||
#### 自动化拷贝批处理
|
#### 自动化拷贝批处理
|
||||||
|
|
||||||
```
|
```batch
|
||||||
@echo off
|
@echo off
|
||||||
|
|
||||||
echo "开始拷贝jsons"
|
echo "开始拷贝jsons"
|
||||||
@@ -292,20 +280,19 @@ pause
|
|||||||
|
|
||||||
上面的config类采用的是partical class 就是为了方便扩展自定义的方法,如果想要添加自定义方法也定义一个partical class文件作为扩展即可
|
上面的config类采用的是partical class 就是为了方便扩展自定义的方法,如果想要添加自定义方法也定义一个partical class文件作为扩展即可
|
||||||
|
|
||||||
#### 待扩展的功能
|
#### TODO
|
||||||
|
|
||||||
目前还不是最理想的状态,还有很多可以扩展完善的功能,如下:
|
|
||||||
|
|
||||||
* [x] 支持枚举类型
|
* [x] 支持枚举类型
|
||||||
* [ ] 支持生成各种类型的文件,例如json、xml、proto、lua等
|
* [x] 支持不同表不同namespace
|
||||||
* [ ] 支持生成各种语法的模板代码
|
|
||||||
* [ ] 支持Excel数据配置规范性检测,例如手误配置不符合规范导致加载异常,例如大小写逗号(肉眼容易忽略),或者空格等等
|
* [ ] 支持Excel数据配置规范性检测,例如手误配置不符合规范导致加载异常,例如大小写逗号(肉眼容易忽略),或者空格等等
|
||||||
* [ ] 支持Excel字段客户端服务器可选项
|
* [ ] ID不能重复 给出错误
|
||||||
* [ ] 支持更多自定义数据类型扩展
|
* [ ] Music、Audio Container不能自引用 给出错误
|
||||||
|
* [ ] 同时播放的音乐BPM必须一致 给出错误
|
||||||
#### 代码
|
* [ ] CueName不能包含空格` `,和`-`,不能以数字开头。给出警告
|
||||||
|
* [ ] Blend容器不能配置Haptic ID 给出警告
|
||||||
https://github.com/dingxiaowei/ExcelTool 喜欢麻烦点个star吧
|
* [ ] SyncPoint功能性需要两个文件BPM、采样率皆一致。给出警告
|
||||||
|
* [x] 生成CueSheet避免magic number
|
||||||
|
* [ ] AudioObject支持Container嵌套
|
||||||
|
|
||||||
#### Unity客户端使用范例
|
#### Unity客户端使用范例
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user