MeowmentDesign/Assets/Editor/Design_Tools/ItemConfigEditor.cs
2026-02-01 15:37:46 +08:00

1795 lines
67 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using OfficeOpenXml;
using ArtResource;
using Debug = UnityEngine.Debug;
namespace DesignTools
{
/// <summary>
/// Item配置Editor工具
/// 读取Docs/config/Item.xlsx关联所有Art_SO资源
/// </summary>
public class ItemConfigEditor : EditorWindow
{
private const string ART_SO_PATH = "Assets/Art_SubModule/Art_SO";
private const string ITEM_EXCEL_NAME = "Item.xlsx";
private const string LIMITED_TIME_EVENT_EXCEL_NAME = "LimitedTimeEvent.xlsx";
private const string AVATAR_EXCEL_NAME = "Avatar.xlsx";
private const string FACE_EXCEL_NAME = "Face.xlsx";
private const string EMOJI_EXCEL_NAME = "Emoji.xlsx";
private const string LANGUAGE_EXCEL_NAME = "AllLanguage.xlsx";
private const string ITEM_SHEET_NAME = "Item";
private const string EVENT_SHEET_NAME = "Event";
private const string AVATAR_SHEET_NAME = "Avatar";
private const string FACE_SHEET_NAME = "Face";
private const string EMOJI_SHEET_NAME = "Emoji";
private const string LANGUAGE_SHEET_NAME = "client";
private const string DOCS_PATH_PREF_KEY = "ItemConfigEditor_DocsPath";
private const string DESIGN_SUBMODULE_PATH = "Assets/Design_SubModule";
// IType类型定义
private static readonly Dictionary<int, string> ITEM_TYPES = new Dictionary<int, string>
{
{ 1, "能量" },
{ 2, "星星" },
{ 3, "钻石" },
{ 97, "Playroom宠物道具" },
{ 98, "卡牌" },
{ 99, "背包道具" },
{ 100, "棋子" },
{ 101, "卡包" },
{ 102, "限时事件" },
{ 103, "小猪存钱罐" },
{ 104, "万能卡" },
{ 105, "头像框" },
{ 106, "活动代币" },
{ 107, "竞赛游戏代币" },
{ 108, "Pet Playroom拜访道具" },
{ 109, "表情" },
{ 110, "头像" },
{ 111, "Playroom装饰" },
{ 112, "Playroom服装" },
{ 113, "Playroom装饰套装" },
{ 114, "Playroom服装套装" },
{ 115, "Playroom道具宝箱" },
{ 116, "活动通行证代币道具" }
};
private string docsRootPath = "";
private List<ItemData> allItemDataList = new List<ItemData>();
private List<ItemData> filteredItemDataList = new List<ItemData>();
private Dictionary<int, string> limitedTimeEventDict = new Dictionary<int, string>(); // Id -> Name
private Dictionary<int, AvatarData> avatarDict = new Dictionary<int, AvatarData>(); // Id -> AvatarData
private Dictionary<int, FaceData> faceDict = new Dictionary<int, FaceData>(); // Id -> FaceData
private Dictionary<int, EmojiData> emojiDict = new Dictionary<int, EmojiData>(); // Id -> EmojiData
private Dictionary<string, Dictionary<string, string>> languageDict = new Dictionary<string, Dictionary<string, string>>();
private List<ArtTableSO> allArtTables = new List<ArtTableSO>();
private Dictionary<string, List<ArtTableSO>> artTablesByFolder = new Dictionary<string, List<ArtTableSO>>(); // 按文件夹分组的表格
private ArtTableSO headFrameResourceSO;
private ArtTableSO headResourceSO;
private ArtTableSO emojiResourceSO;
// Res选择状态缓存 (ItemData的Id -> 选择的组名)
private Dictionary<int, string> resSelectedFolderCache = new Dictionary<int, string>();
private Vector2 scrollPosition;
private bool isDataLoaded = false;
// 筛选和分页
private int selectedIType = -1;
private int currentPage = 0;
private int itemsPerPage = 20;
private int totalPages = 0;
[MenuItem("策划工具/Item配置")]
public static void ShowWindow()
{
var window = GetWindow<ItemConfigEditor>("Item配置");
window.minSize = new Vector2(1200, 700);
window.Show();
}
private void OnEnable()
{
// 读取上次保存的路径
if (EditorPrefs.HasKey(DOCS_PATH_PREF_KEY))
{
docsRootPath = EditorPrefs.GetString(DOCS_PATH_PREF_KEY);
}
}
private void OnGUI()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
EditorGUILayout.LabelField("Item配置工具", EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(5);
// Docs路径选择
EditorGUILayout.BeginVertical("box");
EditorGUILayout.LabelField("Docs项目根目录", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
docsRootPath = EditorGUILayout.TextField("路径", docsRootPath);
if (GUILayout.Button("选择文件夹", GUILayout.Width(100)))
{
string selectedPath = EditorUtility.OpenFolderPanel("选择Docs项目根目录", "", "");
if (!string.IsNullOrEmpty(selectedPath))
{
docsRootPath = selectedPath;
EditorPrefs.SetString(DOCS_PATH_PREF_KEY, docsRootPath);
}
}
EditorGUILayout.EndHorizontal();
if (GUILayout.Button("加载配置数据", GUILayout.Height(30)))
{
LoadData();
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space(5);
// 数据编辑区域
if (isDataLoaded)
{
DrawFilterAndDataEditor();
}
else
{
EditorGUILayout.HelpBox("请先选择Docs根目录并加载配置数据", MessageType.Info);
}
}
/// <summary>
/// 加载配置数据
/// </summary>
private void LoadData()
{
try
{
// 校验路径
if (string.IsNullOrEmpty(docsRootPath) || !Directory.Exists(docsRootPath))
{
EditorUtility.DisplayDialog("错误", "请选择有效的Docs根目录", "确定");
return;
}
// 1. 检查Docs是否为Git仓库并更新
if (!CheckAndUpdateDocsRepository())
{
return;
}
// 2. 检查Design_SubModule分支
if (!CheckAndSwitchDesignSubModuleBranch())
{
return;
}
// 3. 加载所有Art_SO资源
LoadAllArtTableSO();
// 4. 加载语言表
LoadLanguageData();
// 5. 加载LimitedTimeEvent表
LoadLimitedTimeEventData();
// 6. 加载Avatar表
LoadAvatarData();
// 7. 加载Face表
LoadFaceData();
// 8. 加载Emoji表
LoadEmojiData();
// 9. 加载Item表
LoadItemExcel();
isDataLoaded = true;
EditorUtility.DisplayDialog("成功", "配置数据加载成功!", "确定");
}
catch (Exception e)
{
EditorUtility.DisplayDialog("错误", $"加载数据失败: {e.Message}\n{e.StackTrace}", "确定");
isDataLoaded = false;
}
}
/// <summary>
/// 执行Git命令
/// </summary>
private string ExecuteGitCommand(string workingDirectory, string arguments, out bool success)
{
try
{
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "git",
Arguments = arguments,
WorkingDirectory = workingDirectory,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using (Process process = Process.Start(startInfo))
{
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();
process.WaitForExit();
success = process.ExitCode == 0;
return success ? output : error;
}
}
catch (Exception e)
{
success = false;
return $"执行Git命令失败: {e.Message}";
}
}
/// <summary>
/// 检查并更新Docs仓库
/// </summary>
private bool CheckAndUpdateDocsRepository()
{
string gitPath = Path.Combine(docsRootPath, ".git");
if (!Directory.Exists(gitPath))
{
EditorUtility.DisplayDialog("错误",
$"Docs目录不是Git仓库\n路径: {docsRootPath}\n\n请确保Docs项目已正确克隆",
"确定");
return false;
}
EditorUtility.DisplayProgressBar("检查更新", "正在检查Docs仓库远程更新...", 0.3f);
string fetchResult = ExecuteGitCommand(docsRootPath, "fetch", out bool fetchSuccess);
if (!fetchSuccess)
{
EditorUtility.ClearProgressBar();
EditorUtility.DisplayDialog("Git Fetch失败",
$"无法检查远程更新:\n{fetchResult}\n\n请在SourceTree或GitHubDesktop中检查网络连接和仓库状态",
"确定");
return false;
}
EditorUtility.DisplayProgressBar("检查更新", "正在检查是否有新提交...", 0.6f);
string statusResult = ExecuteGitCommand(docsRootPath, "status -uno", out bool statusSuccess);
if (!statusSuccess)
{
EditorUtility.ClearProgressBar();
EditorUtility.DisplayDialog("Git Status失败",
$"无法获取仓库状态:\n{statusResult}\n\n请在SourceTree或GitHubDesktop中检查仓库状态",
"确定");
return false;
}
if (statusResult.Contains("Changes not staged") || statusResult.Contains("Changes to be committed"))
{
EditorUtility.ClearProgressBar();
bool proceed = EditorUtility.DisplayDialog("警告:有未提交的更改",
"Docs仓库中有未提交的更改这可能导致拉取时产生冲突。\n\n建议先提交或暂存这些更改。\n\n是否继续加载配置不推荐",
"继续(不推荐)", "取消,前往处理");
if (!proceed)
{
Debug.Log("请在SourceTree或GitHubDesktop中处理未提交的更改");
return false;
}
}
if (statusResult.Contains("Your branch is behind"))
{
EditorUtility.DisplayProgressBar("更新中", "正在从远程拉取最新代码...", 0.8f);
string pullResult = ExecuteGitCommand(docsRootPath, "pull", out bool pullSuccess);
EditorUtility.ClearProgressBar();
if (!pullSuccess)
{
if (pullResult.Contains("CONFLICT") || pullResult.Contains("conflict"))
{
EditorUtility.DisplayDialog("拉取失败:存在冲突",
$"拉取远程更新时发生冲突:\n{pullResult}\n\n请在SourceTree或GitHubDesktop中解决冲突后再操作",
"确定");
}
else
{
EditorUtility.DisplayDialog("拉取失败",
$"无法拉取远程更新:\n{pullResult}\n\n请在SourceTree或GitHubDesktop中检查并解决问题",
"确定");
}
return false;
}
Debug.Log($"Docs仓库已更新到最新版本:\n{pullResult}");
EditorUtility.DisplayDialog("更新成功", "Docs仓库已更新到最新版本", "确定");
}
else
{
EditorUtility.ClearProgressBar();
Debug.Log("Docs仓库已是最新版本");
}
return true;
}
/// <summary>
/// 检查并切换Design_SubModule到main分支
/// </summary>
private bool CheckAndSwitchDesignSubModuleBranch()
{
string designSubModulePath = Path.Combine(Application.dataPath, "..", DESIGN_SUBMODULE_PATH);
designSubModulePath = Path.GetFullPath(designSubModulePath);
if (!Directory.Exists(designSubModulePath))
{
EditorUtility.DisplayDialog("错误",
$"Design_SubModule目录不存在:\n{designSubModulePath}\n\n请确保子模块已正确初始化",
"确定");
return false;
}
EditorUtility.DisplayProgressBar("检查分支", "正在检查Design_SubModule分支...", 0.5f);
string branchResult = ExecuteGitCommand(designSubModulePath, "branch --show-current", out bool branchSuccess);
if (!branchSuccess)
{
EditorUtility.ClearProgressBar();
EditorUtility.DisplayDialog("Git Branch失败",
$"无法获取当前分支:\n{branchResult}\n\n请在SourceTree或GitHubDesktop中检查Design_SubModule状态",
"确定");
return false;
}
string currentBranch = branchResult.Trim();
if (currentBranch != "main")
{
EditorUtility.DisplayProgressBar("切换分支", "正在切换到main分支...", 0.8f);
string checkoutResult = ExecuteGitCommand(designSubModulePath, "checkout main", out bool checkoutSuccess);
EditorUtility.ClearProgressBar();
if (!checkoutSuccess)
{
EditorUtility.DisplayDialog("切换分支失败",
$"无法切换到main分支:\n{checkoutResult}\n\n当前分支: {currentBranch}\n\n请在SourceTree或GitHubDesktop中手动切换到main分支",
"确定");
return false;
}
Debug.Log($"Design_SubModule已从 {currentBranch} 切换到 main 分支");
}
else
{
EditorUtility.ClearProgressBar();
Debug.Log("Design_SubModule已在main分支");
}
return true;
}
/// <summary>
/// 加载所有Art_SO资源
/// </summary>
private void LoadAllArtTableSO()
{
allArtTables.Clear();
artTablesByFolder.Clear();
string[] guids = AssetDatabase.FindAssets("t:ArtTableSO", new[] { ART_SO_PATH });
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
ArtTableSO tableSO = AssetDatabase.LoadAssetAtPath<ArtTableSO>(path);
if (tableSO != null)
{
allArtTables.Add(tableSO);
// 按文件夹分组
string folder = GetRelativeFolder(path);
if (!artTablesByFolder.ContainsKey(folder))
{
artTablesByFolder[folder] = new List<ArtTableSO>();
}
artTablesByFolder[folder].Add(tableSO);
}
}
// 加载特定资源
headFrameResourceSO = allArtTables.FirstOrDefault(x => x.TableName == "HeadFrameResource");
headResourceSO = allArtTables.FirstOrDefault(x => x.TableName == "HeadResource");
emojiResourceSO = allArtTables.FirstOrDefault(x => x.TableName == "EmojiResource");
Debug.Log($"加载了 {allArtTables.Count} 个ArtTableSO资源分为 {artTablesByFolder.Count} 个组");
}
/// <summary>
/// 获取相对文件夹路径
/// </summary>
private string GetRelativeFolder(string assetPath)
{
string relativePath = assetPath.Replace(ART_SO_PATH + "/", "");
int lastSlash = relativePath.LastIndexOf('/');
if (lastSlash >= 0)
{
return relativePath.Substring(0, lastSlash);
}
return "根目录";
}
/// <summary>
/// 加载语言数据
/// </summary>
private void LoadLanguageData()
{
languageDict.Clear();
string languageExcelPath = Path.Combine(docsRootPath, "config", LANGUAGE_EXCEL_NAME);
if (!File.Exists(languageExcelPath))
{
Debug.LogWarning($"未找到语言表: {languageExcelPath}");
return;
}
using (var package = new ExcelPackage(new FileInfo(languageExcelPath)))
{
var worksheet = package.Workbook.Worksheets[LANGUAGE_SHEET_NAME];
if (worksheet == null)
{
Debug.LogWarning($"语言表中未找到Sheet: {LANGUAGE_SHEET_NAME}");
return;
}
// 查找Key列和语言列
int keyColumnIndex = -1;
int zhCNColumnIndex = -1;
int columnCount = worksheet.Dimension.Columns;
for (int col = 1; col <= columnCount; col++)
{
string header = worksheet.Cells[1, col].Text;
if (header == "key")
{
keyColumnIndex = col;
}
else if (header == "zh_CN")
{
zhCNColumnIndex = col;
}
}
if (keyColumnIndex < 0)
{
Debug.LogWarning("语言表中未找到Key列");
return;
}
// 读取数据从第3行开始
int rowCount = worksheet.Dimension.Rows;
for (int row = 3; row <= rowCount; row++)
{
string key = worksheet.Cells[row, keyColumnIndex].Text;
if (string.IsNullOrEmpty(key)) continue;
if (!languageDict.ContainsKey(key))
{
languageDict[key] = new Dictionary<string, string>();
}
if (zhCNColumnIndex > 0)
{
languageDict[key]["zh_CN"] = worksheet.Cells[row, zhCNColumnIndex].Text;
}
}
}
}
/// <summary>
/// 加载限时事件数据
/// </summary>
private void LoadLimitedTimeEventData()
{
limitedTimeEventDict.Clear();
string eventExcelPath = Path.Combine(docsRootPath, "config", LIMITED_TIME_EVENT_EXCEL_NAME);
if (!File.Exists(eventExcelPath))
{
Debug.LogWarning($"未找到限时事件表: {eventExcelPath}");
return;
}
using (var package = new ExcelPackage(new FileInfo(eventExcelPath)))
{
var worksheet = package.Workbook.Worksheets[EVENT_SHEET_NAME];
if (worksheet == null)
{
Debug.LogWarning($"限时事件表中未找到Sheet: {EVENT_SHEET_NAME}");
return;
}
// 查找Id和Name列
int idCol = -1, nameCol = -1;
int columnCount = worksheet.Dimension.Columns;
for (int col = 1; col <= columnCount; col++)
{
string header = worksheet.Cells[1, col].Text;
if (header == "Id") idCol = col;
else if (header == "Name") nameCol = col;
}
if (idCol < 0 || nameCol < 0)
{
Debug.LogWarning("限时事件表结构不正确");
return;
}
// 读取数据从第3行开始
int rowCount = worksheet.Dimension.Rows;
for (int row = 3; row <= rowCount; row++)
{
string idText = worksheet.Cells[row, idCol].Text;
if (string.IsNullOrEmpty(idText)) continue;
if (int.TryParse(idText, out int id))
{
string name = worksheet.Cells[row, nameCol].Text;
limitedTimeEventDict[id] = name;
}
}
}
}
/// <summary>
/// 加载Avatar数据
/// </summary>
private void LoadAvatarData()
{
avatarDict.Clear();
string avatarExcelPath = Path.Combine(docsRootPath, "config", AVATAR_EXCEL_NAME);
if (!File.Exists(avatarExcelPath))
{
Debug.LogWarning($"未找到Avatar表: {avatarExcelPath}");
return;
}
using (var package = new ExcelPackage(new FileInfo(avatarExcelPath)))
{
var worksheet = package.Workbook.Worksheets[AVATAR_SHEET_NAME];
if (worksheet == null)
{
Debug.LogWarning($"Avatar表中未找到Sheet: {AVATAR_SHEET_NAME}");
return;
}
// 查找列
int idCol = -1, nameKeyCol = -1, iconCol = -1;
int columnCount = worksheet.Dimension.Columns;
for (int col = 1; col <= columnCount; col++)
{
string header = worksheet.Cells[1, col].Text;
if (header == "Id") idCol = col;
else if (header == "NameKey") nameKeyCol = col;
else if (header == "Icon") iconCol = col;
}
if (idCol < 0 || nameKeyCol < 0 || iconCol < 0)
{
Debug.LogWarning("Avatar表结构不正确");
return;
}
// 读取数据从第3行开始
int rowCount = worksheet.Dimension.Rows;
for (int row = 3; row <= rowCount; row++)
{
string idText = worksheet.Cells[row, idCol].Text;
if (string.IsNullOrEmpty(idText)) continue;
if (int.TryParse(idText, out int id))
{
string nameKey = worksheet.Cells[row, nameKeyCol].Text;
string iconText = worksheet.Cells[row, iconCol].Text;
int.TryParse(iconText, out int iconId);
avatarDict[id] = new AvatarData
{
Id = id,
NameKey = nameKey,
IconId = iconId
};
}
}
}
}
/// <summary>
/// 加载Face数据
/// </summary>
private void LoadFaceData()
{
faceDict.Clear();
string faceExcelPath = Path.Combine(docsRootPath, "config", FACE_EXCEL_NAME);
if (!File.Exists(faceExcelPath))
{
Debug.LogWarning($"未找到Face表: {faceExcelPath}");
return;
}
using (var package = new ExcelPackage(new FileInfo(faceExcelPath)))
{
var worksheet = package.Workbook.Worksheets[FACE_SHEET_NAME];
if (worksheet == null)
{
Debug.LogWarning($"Face表中未找到Sheet: {FACE_SHEET_NAME}");
return;
}
// 查找列
int idCol = -1, nameKeyCol = -1, iconCol = -1;
int columnCount = worksheet.Dimension.Columns;
for (int col = 1; col <= columnCount; col++)
{
string header = worksheet.Cells[1, col].Text;
if (header == "Id") idCol = col;
else if (header == "NameKey") nameKeyCol = col;
else if (header == "Icon") iconCol = col;
}
if (idCol < 0 || nameKeyCol < 0 || iconCol < 0)
{
Debug.LogWarning("Face表结构不正确");
return;
}
// 读取数据从第3行开始
int rowCount = worksheet.Dimension.Rows;
for (int row = 3; row <= rowCount; row++)
{
string idText = worksheet.Cells[row, idCol].Text;
if (string.IsNullOrEmpty(idText)) continue;
if (int.TryParse(idText, out int id))
{
string nameKey = worksheet.Cells[row, nameKeyCol].Text;
string iconText = worksheet.Cells[row, iconCol].Text;
int.TryParse(iconText, out int iconId);
faceDict[id] = new FaceData
{
Id = id,
NameKey = nameKey,
IconId = iconId
};
}
}
}
}
/// <summary>
/// 加载Emoji数据
/// </summary>
private void LoadEmojiData()
{
emojiDict.Clear();
string emojiExcelPath = Path.Combine(docsRootPath, "config", EMOJI_EXCEL_NAME);
if (!File.Exists(emojiExcelPath))
{
Debug.LogWarning($"未找到Emoji表: {emojiExcelPath}");
return;
}
using (var package = new ExcelPackage(new FileInfo(emojiExcelPath)))
{
var worksheet = package.Workbook.Worksheets[EMOJI_SHEET_NAME];
if (worksheet == null)
{
Debug.LogWarning($"Emoji表中未找到Sheet: {EMOJI_SHEET_NAME}");
return;
}
// 查找列
int idCol = -1, nameKeyCol = -1, iconCol = -1;
int columnCount = worksheet.Dimension.Columns;
for (int col = 1; col <= columnCount; col++)
{
string header = worksheet.Cells[1, col].Text;
if (header == "Id") idCol = col;
else if (header == "NameKey") nameKeyCol = col;
else if (header == "Icon") iconCol = col;
}
if (idCol < 0 || nameKeyCol < 0 || iconCol < 0)
{
Debug.LogWarning("Emoji表结构不正确");
return;
}
// 读取数据从第3行开始
int rowCount = worksheet.Dimension.Rows;
for (int row = 3; row <= rowCount; row++)
{
string idText = worksheet.Cells[row, idCol].Text;
if (string.IsNullOrEmpty(idText)) continue;
if (int.TryParse(idText, out int id))
{
string nameKey = worksheet.Cells[row, nameKeyCol].Text;
string iconText = worksheet.Cells[row, iconCol].Text;
int.TryParse(iconText, out int iconId);
emojiDict[id] = new EmojiData
{
Id = id,
NameKey = nameKey,
IconId = iconId
};
}
}
}
}
/// <summary>
/// 加载Item.xlsx
/// </summary>
private void LoadItemExcel()
{
allItemDataList.Clear();
string itemExcelPath = Path.Combine(docsRootPath, "config", ITEM_EXCEL_NAME);
if (!File.Exists(itemExcelPath))
{
throw new Exception($"未找到Item配置文件: {itemExcelPath}");
}
using (var package = new ExcelPackage(new FileInfo(itemExcelPath)))
{
var worksheet = package.Workbook.Worksheets[ITEM_SHEET_NAME];
if (worksheet == null)
{
throw new Exception($"Item.xlsx中未找到Sheet: {ITEM_SHEET_NAME}");
}
// 读取表头第1行
int idCol = -1, nameCol = -1, iTypeCol = -1, effectCol = -1, resCol = -1;
int columnCount = worksheet.Dimension.Columns;
for (int col = 1; col <= columnCount; col++)
{
string header = worksheet.Cells[1, col].Text;
if (header == "Id") idCol = col;
else if (header == "Name") nameCol = col;
else if (header == "IType") iTypeCol = col;
else if (header == "Effect") effectCol = col;
else if (header == "Res") resCol = col;
}
if (idCol < 0 || nameCol < 0 || iTypeCol < 0)
{
throw new Exception("Item.xlsx表结构不正确缺少必要列Id/Name/IType");
}
// 读取数据从第3行开始
int rowCount = worksheet.Dimension.Rows;
for (int row = 3; row <= rowCount; row++)
{
string idText = worksheet.Cells[row, idCol].Text;
if (string.IsNullOrEmpty(idText)) continue;
if (!int.TryParse(idText, out int id)) continue;
string name = worksheet.Cells[row, nameCol].Text;
string iTypeText = worksheet.Cells[row, iTypeCol].Text;
string effect = effectCol > 0 ? worksheet.Cells[row, effectCol].Text : "";
string res = resCol > 0 ? worksheet.Cells[row, resCol].Text : "";
if (!int.TryParse(iTypeText, out int iType)) continue;
var itemData = new ItemData
{
Id = id,
Name = name,
IType = iType,
Effect = effect,
Res = res
};
allItemDataList.Add(itemData);
}
}
}
/// <summary>
/// 绘制筛选和数据编辑器
/// </summary>
private void DrawFilterAndDataEditor()
{
EditorGUILayout.BeginVertical("box");
// IType筛选
EditorGUILayout.LabelField("Item类型筛选", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
// 创建下拉列表选项
var typeOptions = new List<string> { "请选择类型..." };
var typeValues = new List<int> { -1 };
// 排除103、104、108和97、98、111-115
foreach (var kvp in ITEM_TYPES.OrderBy(x => x.Key))
{
// 跳过不需要显示的类型
if (kvp.Key == 103 || kvp.Key == 104 || kvp.Key == 108 ||
kvp.Key == 97 || kvp.Key == 98 ||
(kvp.Key >= 111 && kvp.Key <= 115))
{
continue;
}
typeOptions.Add(kvp.Value);
typeValues.Add(kvp.Key);
}
int currentIndex = typeValues.IndexOf(selectedIType);
if (currentIndex < 0) currentIndex = 0;
int newIndex = EditorGUILayout.Popup("选择类型", currentIndex, typeOptions.ToArray());
if (newIndex != currentIndex)
{
selectedIType = typeValues[newIndex];
FilterItemsByType();
currentPage = 0;
}
if (selectedIType > 0)
{
EditorGUILayout.LabelField($"当前类型ID: {selectedIType}", GUILayout.Width(150));
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
EditorGUILayout.Space(5);
// 如果已选择类型,显示数据列表
if (selectedIType > 0)
{
DrawItemDataList();
}
else
{
EditorGUILayout.HelpBox("请先选择Item类型进行筛选", MessageType.Info);
}
}
/// <summary>
/// 根据IType筛选数据
/// </summary>
private void FilterItemsByType()
{
filteredItemDataList = allItemDataList.Where(x => x.IType == selectedIType).ToList();
totalPages = Mathf.CeilToInt((float)filteredItemDataList.Count / itemsPerPage);
}
/// <summary>
/// 绘制Item数据列表
/// </summary>
private void DrawItemDataList()
{
EditorGUILayout.BeginVertical("box");
// 顶部工具栏
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($"数据列表(共 {filteredItemDataList.Count} 条)", EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
GUI.backgroundColor = Color.cyan;
if (GUILayout.Button("+ 添加Item", GUILayout.Height(25), GUILayout.Width(100)))
{
AddNewItem();
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(3);
// 分页控制
DrawPagination();
EditorGUILayout.Space(3);
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.Height(400));
// 表头
DrawTableHeader();
// 数据行
int startIndex = currentPage * itemsPerPage;
int endIndex = Mathf.Min(startIndex + itemsPerPage, filteredItemDataList.Count);
for (int i = startIndex; i < endIndex; i++)
{
DrawItemDataRow(filteredItemDataList[i]);
}
EditorGUILayout.EndScrollView();
EditorGUILayout.Space(5);
// 分页控制(底部)
DrawPagination();
EditorGUILayout.EndVertical();
EditorGUILayout.Space(10);
// 保存按钮
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUI.backgroundColor = Color.green;
if (GUILayout.Button("保存配置到Excel", GUILayout.Height(30), GUILayout.Width(200)))
{
SaveData();
}
GUI.backgroundColor = Color.white;
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
/// <summary>
/// 绘制分页控制
/// </summary>
private void DrawPagination()
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("每页显示:", GUILayout.Width(70));
itemsPerPage = EditorGUILayout.IntField(itemsPerPage, GUILayout.Width(50));
itemsPerPage = Mathf.Max(1, itemsPerPage);
GUILayout.Space(20);
GUI.enabled = currentPage > 0;
if (GUILayout.Button("第一页", GUILayout.Width(60)))
{
currentPage = 0;
}
if (GUILayout.Button("上一页", GUILayout.Width(60)))
{
currentPage--;
}
GUI.enabled = true;
EditorGUILayout.LabelField($"第 {currentPage + 1} / {Mathf.Max(1, totalPages)} 页", GUILayout.Width(100));
int newPage = EditorGUILayout.IntField(currentPage + 1, GUILayout.Width(50)) - 1;
if (newPage != currentPage && newPage >= 0 && newPage < totalPages)
{
currentPage = newPage;
}
GUI.enabled = currentPage < totalPages - 1;
if (GUILayout.Button("下一页", GUILayout.Width(60)))
{
currentPage++;
}
if (GUILayout.Button("最后一页", GUILayout.Width(70)))
{
currentPage = totalPages - 1;
}
GUI.enabled = true;
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
/// <summary>
/// 绘制表头
/// </summary>
private void DrawTableHeader()
{
EditorGUILayout.BeginHorizontal("box");
EditorGUILayout.LabelField("Id", EditorStyles.boldLabel, GUILayout.Width(50));
EditorGUILayout.LabelField("Name", EditorStyles.boldLabel, GUILayout.Width(120));
EditorGUILayout.LabelField("IType", EditorStyles.boldLabel, GUILayout.Width(50));
EditorGUILayout.LabelField("Effect", EditorStyles.boldLabel, GUILayout.Width(200));
EditorGUILayout.LabelField("Res", EditorStyles.boldLabel, GUILayout.Width(200));
EditorGUILayout.LabelField("预览", EditorStyles.boldLabel, GUILayout.Width(100));
EditorGUILayout.LabelField("操作", EditorStyles.boldLabel, GUILayout.Width(60));
EditorGUILayout.EndHorizontal();
}
/// <summary>
/// 绘制单行数据
/// </summary>
private void DrawItemDataRow(ItemData data)
{
EditorGUILayout.BeginHorizontal("box");
// Id可编辑
data.Id = EditorGUILayout.IntField(data.Id, GUILayout.Width(50));
// Name可编辑
data.Name = EditorGUILayout.TextField(data.Name, GUILayout.Width(120));
// IType只读
GUI.enabled = false;
EditorGUILayout.IntField(data.IType, GUILayout.Width(50));
GUI.enabled = true;
// Effect根据IType不同显示不同内容
DrawEffectField(data);
// Res资源选择
DrawResField(data);
// 预览
DrawResPreview(data);
// 删除按钮
GUI.backgroundColor = Color.red;
if (GUILayout.Button("删除", GUILayout.Width(60)))
{
if (EditorUtility.DisplayDialog("确认删除", $"确定要删除ID为{data.Id}的Item吗", "删除", "取消"))
{
filteredItemDataList.Remove(data);
allItemDataList.Remove(data);
FilterItemsByType(); // 重新计算分页
}
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
}
/// <summary>
/// 绘制Effect字段
/// </summary>
private void DrawEffectField(ItemData data)
{
EditorGUILayout.BeginVertical(GUILayout.Width(200));
switch (data.IType)
{
case 100: // 棋子 - 不显示
case 1: // 能量
case 2: // 星星
case 3: // 钻石
EditorGUILayout.LabelField("-", GUILayout.Width(200));
break;
case 101: // 卡包 - 只读
case 106: // 活动代币 - 没有Effect
case 107: // 竞赛游戏代币 - 没有Effect
case 99: // 背包道具 - 没有Effect
EditorGUILayout.LabelField(string.IsNullOrEmpty(data.Effect) ? "-" : data.Effect, GUILayout.Width(200));
break;
case 102: // 限时事件
DrawLimitedTimeEventEffect(data);
break;
case 105: // 头像框 - 已改用Res字段
EditorGUILayout.LabelField("请使用Res字段配置", GUILayout.Width(200));
break;
case 110: // 头像 - 已改用Res字段
EditorGUILayout.LabelField("请使用Res字段配置", GUILayout.Width(200));
break;
case 109: // 表情 - 已改用Res字段
EditorGUILayout.LabelField("请使用Res字段配置", GUILayout.Width(200));
break;
default:
data.Effect = EditorGUILayout.TextField(data.Effect, GUILayout.Width(200));
break;
}
EditorGUILayout.EndVertical();
}
/// <summary>
/// 绘制限时事件Effect
/// </summary>
private void DrawLimitedTimeEventEffect(ItemData data)
{
// Effect格式: "eventId,seconds"
string[] parts = data.Effect.Split(',');
int eventId = 0;
int seconds = 0;
if (parts.Length >= 1) int.TryParse(parts[0], out eventId);
if (parts.Length >= 2) int.TryParse(parts[1], out seconds);
EditorGUILayout.BeginHorizontal();
// 事件选择下拉框
var eventOptions = new List<string> { "请选择..." };
var eventIds = new List<int> { 0 };
foreach (var kvp in limitedTimeEventDict.OrderBy(x => x.Key))
{
eventOptions.Add(kvp.Value);
eventIds.Add(kvp.Key);
}
int currentIndex = eventIds.IndexOf(eventId);
if (currentIndex < 0) currentIndex = 0;
int newIndex = EditorGUILayout.Popup(currentIndex, eventOptions.ToArray(), GUILayout.Width(120));
int newEventId = eventIds[newIndex];
// 秒数输入
int newSeconds = EditorGUILayout.IntField(seconds, GUILayout.Width(60));
// 显示时间格式
int minutes = newSeconds / 60;
int secs = newSeconds % 60;
GUI.enabled = false;
EditorGUILayout.TextField($"{minutes:D2}:{secs:D2}", GUILayout.Width(50));
GUI.enabled = true;
EditorGUILayout.EndHorizontal();
// 验证提示
if (newEventId > 0 && !limitedTimeEventDict.ContainsKey(newEventId))
{
EditorGUILayout.HelpBox("没有这个限时事件", MessageType.Error);
}
// 更新数据
data.Effect = $"{newEventId},{newSeconds}";
}
/// <summary>
/// 绘制头像框Effect
/// </summary>
private void DrawHeadFrameEffect(ItemData data)
{
// Effect格式: "avatarId,0"
string[] parts = string.IsNullOrEmpty(data.Effect) ? new string[0] : data.Effect.Split(',');
int avatarId = -1;
if (parts.Length >= 1) int.TryParse(parts[0], out avatarId);
// 创建下拉选项
var options = new List<string> { "请选择..." };
var ids = new List<int> { -1 };
foreach (var kvp in avatarDict.OrderBy(x => x.Key))
{
string name = kvp.Value.NameKey;
if (languageDict.ContainsKey(kvp.Value.NameKey) && languageDict[kvp.Value.NameKey].ContainsKey("zh_CN"))
{
name = languageDict[kvp.Value.NameKey]["zh_CN"];
}
options.Add($"{name} (ID:{kvp.Key})");
ids.Add(kvp.Key);
}
int currentIndex = ids.IndexOf(avatarId);
if (currentIndex < 0) currentIndex = 0;
EditorGUI.BeginChangeCheck();
int newIndex = EditorGUILayout.Popup(currentIndex, options.ToArray(), GUILayout.Width(200));
if (EditorGUI.EndChangeCheck())
{
int newAvatarId = ids[newIndex];
data.Effect = $"{newAvatarId},0";
}
}
/// <summary>
/// 绘制头像Effect
/// </summary>
private void DrawFaceEffect(ItemData data)
{
// Effect格式: "faceId,0"
string[] parts = string.IsNullOrEmpty(data.Effect) ? new string[0] : data.Effect.Split(',');
int faceId = -1;
if (parts.Length >= 1) int.TryParse(parts[0], out faceId);
// 创建下拉选项
var options = new List<string> { "请选择..." };
var ids = new List<int> { -1 };
foreach (var kvp in faceDict.OrderBy(x => x.Key))
{
string name = kvp.Value.NameKey;
if (languageDict.ContainsKey(kvp.Value.NameKey) && languageDict[kvp.Value.NameKey].ContainsKey("zh_CN"))
{
name = languageDict[kvp.Value.NameKey]["zh_CN"];
}
options.Add($"{name} (ID:{kvp.Key})");
ids.Add(kvp.Key);
}
int currentIndex = ids.IndexOf(faceId);
if (currentIndex < 0) currentIndex = 0;
EditorGUI.BeginChangeCheck();
int newIndex = EditorGUILayout.Popup(currentIndex, options.ToArray(), GUILayout.Width(200));
if (EditorGUI.EndChangeCheck())
{
int newFaceId = ids[newIndex];
data.Effect = $"{newFaceId},0";
}
}
/// <summary>
/// 绘制表情Effect
/// </summary>
private void DrawEmojiEffect(ItemData data)
{
// Effect格式: "emojiId,0"
string[] parts = string.IsNullOrEmpty(data.Effect) ? new string[0] : data.Effect.Split(',');
int emojiId = -1;
if (parts.Length >= 1) int.TryParse(parts[0], out emojiId);
// 创建下拉选项
var options = new List<string> { "请选择..." };
var ids = new List<int> { -1 };
foreach (var kvp in emojiDict.OrderBy(x => x.Key))
{
string name = kvp.Value.NameKey;
if (languageDict.ContainsKey(kvp.Value.NameKey) && languageDict[kvp.Value.NameKey].ContainsKey("zh_CN"))
{
name = languageDict[kvp.Value.NameKey]["zh_CN"];
}
options.Add($"{name} (ID:{kvp.Key})");
ids.Add(kvp.Key);
}
int currentIndex = ids.IndexOf(emojiId);
if (currentIndex < 0) currentIndex = 0;
EditorGUI.BeginChangeCheck();
int newIndex = EditorGUILayout.Popup(currentIndex, options.ToArray(), GUILayout.Width(200));
if (EditorGUI.EndChangeCheck())
{
int newEmojiId = ids[newIndex];
data.Effect = $"{newEmojiId},0";
}
}
/// <summary>
/// 绘制Res字段三级选择组->表->Item
/// </summary>
private void DrawResField(ItemData data)
{
EditorGUILayout.BeginVertical(GUILayout.Width(200));
// Res格式: "TableId,ItemId"
string[] parts = string.IsNullOrEmpty(data.Res) ? new string[0] : data.Res.Split(',');
int tableId = -1;
int itemId = -1;
if (parts.Length > 0) int.TryParse(parts[0], out tableId);
if (parts.Length > 1) int.TryParse(parts[1], out itemId);
// 查找当前表格和所属文件夹
ArtTableSO currentTable = null;
string currentFolder = "";
// 优先使用缓存的组选择
if (resSelectedFolderCache.ContainsKey(data.Id))
{
currentFolder = resSelectedFolderCache[data.Id];
}
// 如果有Res数据从Res推导组和表
if (tableId >= 0)
{
currentTable = allArtTables.FirstOrDefault(x => x.TableId == tableId);
if (currentTable != null)
{
string tablePath = AssetDatabase.GetAssetPath(currentTable);
string folderFromRes = GetRelativeFolder(tablePath);
// 如果没有缓存或缓存不一致,更新缓存
if (string.IsNullOrEmpty(currentFolder) || currentFolder != folderFromRes)
{
currentFolder = folderFromRes;
resSelectedFolderCache[data.Id] = currentFolder;
}
}
}
// 第一级:选择组
var folderOptions = new List<string> { "未选择" };
folderOptions.AddRange(artTablesByFolder.Keys.OrderBy(k => k));
int currentFolderIndex = 0;
if (!string.IsNullOrEmpty(currentFolder))
{
currentFolderIndex = folderOptions.IndexOf(currentFolder);
if (currentFolderIndex < 0) currentFolderIndex = 0;
}
EditorGUI.BeginChangeCheck();
int newFolderIndex = EditorGUILayout.Popup(currentFolderIndex, folderOptions.ToArray(), GUILayout.Width(200));
bool folderChanged = EditorGUI.EndChangeCheck();
if (folderChanged)
{
if (newFolderIndex > 0 && newFolderIndex < folderOptions.Count)
{
currentFolder = folderOptions[newFolderIndex];
// 保存到缓存
resSelectedFolderCache[data.Id] = currentFolder;
// 组改变时清空表和Item选择
currentTable = null;
tableId = -1;
itemId = -1;
}
else if (newFolderIndex == 0)
{
// 选择"未选择",清空所有
currentFolder = "";
if (resSelectedFolderCache.ContainsKey(data.Id))
{
resSelectedFolderCache.Remove(data.Id);
}
currentTable = null;
data.Res = "";
EditorGUILayout.EndVertical();
return;
}
}
else if (newFolderIndex > 0 && newFolderIndex < folderOptions.Count)
{
currentFolder = folderOptions[newFolderIndex];
}
else if (newFolderIndex == 0)
{
// 当前是"未选择"状态
EditorGUILayout.EndVertical();
return;
}
// 第二级:选择表
List<ArtTableSO> tablesInFolder = artTablesByFolder.ContainsKey(currentFolder)
? artTablesByFolder[currentFolder]
: new List<ArtTableSO>();
var tableOptions = new List<string> { "未选择" };
tableOptions.AddRange(tablesInFolder.Select(t => $"{t.TableName}(ID:{t.TableId})"));
int currentTableIndex = 0;
if (currentTable != null && !folderChanged)
{
int foundIndex = tablesInFolder.FindIndex(t => t.TableId == currentTable.TableId);
if (foundIndex >= 0)
{
currentTableIndex = foundIndex + 1; // +1 因为有"未选择"选项
}
}
EditorGUI.BeginChangeCheck();
int newTableIndex = EditorGUILayout.Popup(currentTableIndex, tableOptions.ToArray(), GUILayout.Width(200));
bool tableSelectionChanged = EditorGUI.EndChangeCheck();
if (tableSelectionChanged)
{
if (newTableIndex > 0 && newTableIndex <= tablesInFolder.Count)
{
currentTable = tablesInFolder[newTableIndex - 1]; // -1 因为有"未选择"选项
// 表格改变时清空Item选择
data.Res = $"{currentTable.TableId},-1";
itemId = -1;
}
else if (newTableIndex == 0)
{
// 选择"未选择"
currentTable = null;
data.Res = "";
EditorGUILayout.EndVertical();
return;
}
}
else if (newTableIndex > 0 && newTableIndex <= tablesInFolder.Count)
{
currentTable = tablesInFolder[newTableIndex - 1];
}
else
{
// 当前表是"未选择"状态
EditorGUILayout.EndVertical();
return;
}
// 第三级选择Item
if (currentTable != null && currentTable.Items != null && currentTable.Items.Count > 0)
{
var itemOptions = new List<string> { "未选择" };
itemOptions.AddRange(currentTable.Items.Select(item => $"{item.Name}(ID:{item.Id})"));
int currentItemIndex = 0;
if (itemId >= 0 && !tableSelectionChanged)
{
int foundIndex = currentTable.Items.FindIndex(item => item.Id == itemId);
if (foundIndex >= 0)
{
currentItemIndex = foundIndex + 1; // +1 因为有"未选择"选项
}
}
EditorGUI.BeginChangeCheck();
int newItemIndex = EditorGUILayout.Popup(currentItemIndex, itemOptions.ToArray(), GUILayout.Width(200));
if (EditorGUI.EndChangeCheck())
{
if (newItemIndex > 0 && newItemIndex <= currentTable.Items.Count)
{
data.Res = $"{currentTable.TableId},{currentTable.Items[newItemIndex - 1].Id}"; // -1 因为有"未选择"选项
}
else if (newItemIndex == 0)
{
// 选择"未选择"
data.Res = $"{currentTable.TableId},-1";
}
}
}
EditorGUILayout.EndVertical();
}
/// <summary>
/// 绘制资源预览统一使用Res字段
/// </summary>
private void DrawResPreview(ItemData data)
{
Sprite previewSprite = null;
string tipMessage = "";
// 统一从Res字段获取预览
previewSprite = GetResPreview(data);
if (previewSprite != null)
{
Rect rect = GUILayoutUtility.GetRect(80, 80, GUILayout.Width(100));
GUI.DrawTexture(rect, previewSprite.texture, ScaleMode.ScaleToFit);
}
else
{
if (!string.IsNullOrEmpty(tipMessage))
{
GUILayout.Box(tipMessage, GUILayout.Width(100), GUILayout.Height(80));
}
else
{
GUILayout.Box("无预览", GUILayout.Width(100), GUILayout.Height(80));
}
}
}
/// <summary>
/// 获取头像框预览
/// </summary>
private Sprite GetHeadFramePreview(ItemData data)
{
if (headFrameResourceSO == null) return null;
if (string.IsNullOrEmpty(data.Effect)) return null;
string[] parts = data.Effect.Split(',');
if (parts.Length < 1 || !int.TryParse(parts[0], out int avatarId)) return null;
if (avatarId < 0) return null;
if (!avatarDict.ContainsKey(avatarId)) return null;
int iconId = avatarDict[avatarId].IconId;
var item = headFrameResourceSO.Items.FirstOrDefault(x => x.Id == iconId);
return item?.Sprite;
}
/// <summary>
/// 获取头像预览
/// </summary>
private Sprite GetFacePreview(ItemData data)
{
if (headResourceSO == null) return null;
if (string.IsNullOrEmpty(data.Effect)) return null;
string[] parts = data.Effect.Split(',');
if (parts.Length < 1 || !int.TryParse(parts[0], out int faceId)) return null;
if (faceId < 0) return null;
if (!faceDict.ContainsKey(faceId)) return null;
int iconId = faceDict[faceId].IconId;
var item = headResourceSO.Items.FirstOrDefault(x => x.Id == iconId);
return item?.Sprite;
}
/// <summary>
/// 获取表情预览
/// </summary>
private Sprite GetEmojiPreview(ItemData data)
{
if (emojiResourceSO == null) return null;
if (string.IsNullOrEmpty(data.Effect)) return null;
string[] parts = data.Effect.Split(',');
if (parts.Length < 1 || !int.TryParse(parts[0], out int emojiId)) return null;
if (emojiId < 0) return null;
if (!emojiDict.ContainsKey(emojiId)) return null;
int iconId = emojiDict[emojiId].IconId;
var item = emojiResourceSO.Items.FirstOrDefault(x => x.Id == iconId);
return item?.Sprite;
}
/// <summary>
/// 获取Res资源预览
/// </summary>
private Sprite GetResPreview(ItemData data)
{
if (string.IsNullOrEmpty(data.Res)) return null;
string[] parts = data.Res.Split(',');
if (parts.Length < 2) return null;
if (!int.TryParse(parts[0], out int tableId)) return null;
if (!int.TryParse(parts[1], out int itemId)) return null;
var table = allArtTables.FirstOrDefault(x => x.TableId == tableId);
if (table == null) return null;
var item = table.Items.FirstOrDefault(x => x.Id == itemId);
return item?.Sprite;
}
/// <summary>
/// 添加新Item
/// </summary>
private void AddNewItem()
{
// 找到当前IType的最大ID
int maxId = 0;
foreach (var item in allItemDataList.Where(x => x.IType == selectedIType))
{
if (item.Id > maxId) maxId = item.Id;
}
// 自增ID并检查重复
int newId = maxId + 1;
while (allItemDataList.Any(x => x.Id == newId))
{
newId++;
}
var newItem = new ItemData
{
Id = newId,
Name = $"新Item_{newId}",
IType = selectedIType,
Effect = "",
Res = ""
};
allItemDataList.Add(newItem);
FilterItemsByType();
// 跳转到最后一页
currentPage = totalPages - 1;
}
/// <summary>
/// 保存数据到Excel
/// </summary>
private void SaveData()
{
try
{
// ID重复检查
var duplicateIds = allItemDataList.GroupBy(x => x.Id)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
if (duplicateIds.Count > 0)
{
EditorUtility.DisplayDialog("错误",
$"存在重复的ID: {string.Join(", ", duplicateIds)}\n请修正后再保存",
"确定");
return;
}
// 验证限时事件
foreach (var item in allItemDataList.Where(x => x.IType == 102))
{
if (!string.IsNullOrEmpty(item.Effect))
{
string[] parts = item.Effect.Split(',');
if (parts.Length >= 1 && int.TryParse(parts[0], out int eventId))
{
if (eventId > 0 && !limitedTimeEventDict.ContainsKey(eventId))
{
EditorUtility.DisplayDialog("错误",
$"ID {item.Id} 的限时事件ID {eventId} 不存在\n请修正后再保存",
"确定");
return;
}
}
}
}
string itemExcelPath = Path.Combine(docsRootPath, "config", ITEM_EXCEL_NAME);
using (var package = new ExcelPackage(new FileInfo(itemExcelPath)))
{
var worksheet = package.Workbook.Worksheets[ITEM_SHEET_NAME];
// 找到列索引
int idCol = -1, nameCol = -1, iTypeCol = -1, effectCol = -1, resCol = -1;
int columnCount = worksheet.Dimension.Columns;
for (int col = 1; col <= columnCount; col++)
{
string header = worksheet.Cells[1, col].Text;
if (header == "Id") idCol = col;
else if (header == "Name") nameCol = col;
else if (header == "IType") iTypeCol = col;
else if (header == "Effect") effectCol = col;
else if (header == "Res") resCol = col;
}
// 创建Id到行号的映射读取现有数据
var existingRowMap = new Dictionary<int, int>();
int existingRowCount = worksheet.Dimension.Rows;
for (int row = 3; row <= existingRowCount; row++)
{
string idText = worksheet.Cells[row, idCol].Text;
if (!string.IsNullOrEmpty(idText) && int.TryParse(idText, out int id))
{
existingRowMap[id] = row;
}
}
// 更新或新增数据
int nextNewRow = existingRowCount + 1;
foreach (var item in allItemDataList.OrderBy(x => x.Id))
{
int targetRow;
// 如果该ID已存在更新对应行否则添加新行
if (existingRowMap.ContainsKey(item.Id))
{
targetRow = existingRowMap[item.Id];
}
else
{
targetRow = nextNewRow;
nextNewRow++;
}
// 只更新我们管理的列
worksheet.Cells[targetRow, idCol].Value = item.Id;
worksheet.Cells[targetRow, nameCol].Value = item.Name;
worksheet.Cells[targetRow, iTypeCol].Value = item.IType;
if (effectCol > 0)
{
worksheet.Cells[targetRow, effectCol].Value = item.Effect;
}
if (resCol > 0)
{
worksheet.Cells[targetRow, resCol].Value = item.Res;
}
}
// 删除已不存在的数据行(只清空我们管理的列)
var currentIds = allItemDataList.Select(x => x.Id).ToHashSet();
for (int row = 3; row <= existingRowCount; row++)
{
string idText = worksheet.Cells[row, idCol].Text;
if (!string.IsNullOrEmpty(idText) && int.TryParse(idText, out int id))
{
if (!currentIds.Contains(id))
{
// 只清空我们管理的列,保留其他列
worksheet.Cells[row, idCol].Value = null;
worksheet.Cells[row, nameCol].Value = null;
worksheet.Cells[row, iTypeCol].Value = null;
if (effectCol > 0) worksheet.Cells[row, effectCol].Value = null;
if (resCol > 0) worksheet.Cells[row, resCol].Value = null;
}
}
}
package.Save();
}
EditorUtility.DisplayDialog("成功", "配置已保存到Excel文件", "确定");
}
catch (Exception e)
{
EditorUtility.DisplayDialog("错误", $"保存失败: {e.Message}\n{e.StackTrace}", "确定");
}
}
/// <summary>
/// Item数据类
/// </summary>
private class ItemData
{
public int Id;
public string Name;
public int IType;
public string Effect;
public string Res;
}
/// <summary>
/// Avatar数据类
/// </summary>
private class AvatarData
{
public int Id;
public string NameKey;
public int IconId;
}
/// <summary>
/// Face数据类
/// </summary>
private class FaceData
{
public int Id;
public string NameKey;
public int IconId;
}
/// <summary>
/// Emoji数据类
/// </summary>
private class EmojiData
{
public int Id;
public string NameKey;
public int IconId;
}
}
}