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

1586 lines
62 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 System.Text;
using UnityEditor;
using UnityEngine;
using OfficeOpenXml;
using ArtResource;
using Debug = UnityEngine.Debug;
namespace DesignTools.Scene
{
/// <summary>
/// 场景进度奖励配置Editor工具
/// 读取Docs/config/IndoorProgress.xlsx和Item.xlsx
/// </summary>
public class IndoorProgressEditor : EditorWindow
{
private const string ART_SO_PATH = "Assets/Art_SubModule/Art_SO";
private const string INDOOR_PROGRESS_EXCEL_NAME = "IndoorProgress.xlsx";
private const string ITEM_EXCEL_NAME = "Item.xlsx";
private const string INDOOR_PROGRESS_SHEET_NAME = "IndoorProgress";
private const string ITEM_SHEET_NAME = "Item";
private const string DOCS_PATH_PREF_KEY = "IndoorProgressEditor_DocsPath";
private const string DESIGN_SUBMODULE_PATH = "Assets/Design_SubModule";
// IType类型定义从ItemConfigEditor复制
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, "活动通行证代币道具" }
};
// 特殊Item的映射关系
private static readonly Dictionary<int, string> MY_ITEM_DICT = new Dictionary<int, string>()
{
[100001] = "Energy",
[100002] = "Star",
[100003] = "Diamond",
[100004] = "Cardpack1",
[100005] = "Cardpack2",
[100006] = "Cardpack3",
[100007] = "Cardpack4",
[100008] = "Cardpack5",
[100011] = "LimitEvent4",
[100012] = "LimitEvent2",
[100013] = "LimitEvent3",
[100014] = "LimitEvent1",
[100015] = "LimitEvent5",
[100016] = "LimitEvent6",
[100017] = "LimitEvent7",
[100018] = "LimitEvent9",
[100019] = "LimitEvent10",
[100020] = "LimitEvent8",
[100022] = "PurplePig",
[101149] = "LimitEvent2",
[101150] = "LimitEvent3",
[101151] = "LimitEvent4",
[1021] = "705",
[1006] = "702",
[1007] = "703",
[100035] = "Battery",
[101445] = "BagItemBox1",
[101446] = "BagItemBox2",
[101447] = "BagItemBox3",
};
private string docsRootPath = "";
private List<IndoorProgressData> allProgressDataList = new List<IndoorProgressData>();
private List<IndoorProgressData> filteredProgressDataList = new List<IndoorProgressData>();
private Dictionary<int, ItemData> itemDict = new Dictionary<int, ItemData>(); // Id -> ItemData
private List<ArtTableSO> allArtTables = new List<ArtTableSO>();
private Vector2 scrollPosition;
private bool isDataLoaded = false;
private int selectedScene = -1;
private string sceneInputText = "";
// 道具奖励临时编辑状态按数据ID存储
private Dictionary<int, int> tempItemRewardTypeIndex = new Dictionary<int, int>();
// 区域奖励临时编辑状态按数据ID存储
private Dictionary<int, int> tempAreaRewardTypeIndex = new Dictionary<int, int>();
private Dictionary<int, int> tempAreaRewardItemId = new Dictionary<int, int>();
private Dictionary<int, int> tempAreaRewardNum = new Dictionary<int, int>();
[MenuItem("策划工具/场景/场景进度奖励")]
public static void ShowWindow()
{
var window = GetWindow<IndoorProgressEditor>("场景进度奖励配置");
window.minSize = new Vector2(1400, 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("场景进度奖励配置工具", 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)
{
DrawSceneFilterAndDataEditor();
}
else
{
EditorGUILayout.HelpBox("请先选择Docs根目录并加载配置数据", MessageType.Info);
}
}
/// <summary>
/// 加载配置数据
/// </summary>
private void LoadData()
{
if (string.IsNullOrEmpty(docsRootPath))
{
EditorUtility.DisplayDialog("错误", "请先选择Docs根目录", "确定");
return;
}
try
{
// 检查并更新Docs仓库
if (!CheckAndUpdateDocsRepository())
{
return;
}
// 检查并切换Design_SubModule到main分支
if (!CheckAndSwitchDesignSubModuleBranch())
{
return;
}
// 加载所有Art_SO资源
LoadAllArtTableSO();
// 加载Item数据
LoadItemData();
// 加载IndoorProgress数据
LoadIndoorProgressData();
isDataLoaded = true;
selectedScene = -1;
sceneInputText = "";
filteredProgressDataList.Clear();
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
{
var processInfo = new ProcessStartInfo
{
FileName = "git",
Arguments = arguments,
WorkingDirectory = workingDirectory,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using (var process = Process.Start(processInfo))
{
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("错误",
$"所选目录不是Git仓库{docsRootPath}\n请确保选择的是Docs项目的根目录。",
"确定");
return false;
}
string fetchResult = ExecuteGitCommand(docsRootPath, "fetch", out bool fetchSuccess);
if (!fetchSuccess)
{
bool continueAnyway = EditorUtility.DisplayDialog("警告",
$"Git fetch失败{fetchResult}\n\n是否继续",
"继续", "取消");
return continueAnyway;
}
string statusResult = ExecuteGitCommand(docsRootPath, "status", out bool statusSuccess);
if (!statusSuccess)
{
EditorUtility.DisplayDialog("错误",
$"Git status检查失败{statusResult}",
"确定");
return false;
}
if (statusResult.Contains("Changes not staged") || statusResult.Contains("Changes to be committed"))
{
bool proceed = EditorUtility.DisplayDialog("提示",
"Docs仓库有未提交的修改。\n\n建议先提交或暂存这些修改。\n\n是否继续",
"继续", "取消");
if (!proceed)
{
return false;
}
}
if (statusResult.Contains("Your branch is behind"))
{
bool doPull = EditorUtility.DisplayDialog("提示",
"Docs仓库有远程更新。\n\n是否执行git pull拉取最新代码\n建议执行以获取最新配置",
"执行Pull", "跳过");
if (doPull)
{
string pullResult = ExecuteGitCommand(docsRootPath, "pull", out bool pullSuccess);
if (!pullSuccess)
{
if (pullResult.Contains("CONFLICT") || pullResult.Contains("conflict"))
{
EditorUtility.DisplayDialog("错误",
$"Git pull遇到冲突\n{pullResult}\n\n请先在SourceTree或其他Git工具中解决冲突。",
"确定");
}
else
{
EditorUtility.DisplayDialog("错误",
$"Git pull失败{pullResult}",
"确定");
}
return false;
}
EditorUtility.DisplayDialog("成功", "已拉取最新代码!", "确定");
}
}
else
{
Debug.Log("Docs仓库已是最新");
}
return true;
}
/// <summary>
/// 检查并切换Design_SubModule到main分支
/// </summary>
private bool CheckAndSwitchDesignSubModuleBranch()
{
string designSubModulePath = Path.Combine(Application.dataPath.Replace("/Assets", ""), DESIGN_SUBMODULE_PATH);
designSubModulePath = designSubModulePath.Replace("\\", "/");
if (!Directory.Exists(designSubModulePath))
{
EditorUtility.DisplayDialog("错误",
$"Design_SubModule目录不存在{designSubModulePath}",
"确定");
return false;
}
string branchResult = ExecuteGitCommand(designSubModulePath, "rev-parse --abbrev-ref HEAD", out bool branchSuccess);
if (!branchSuccess)
{
EditorUtility.DisplayDialog("错误",
$"无法获取Design_SubModule当前分支{branchResult}",
"确定");
return false;
}
string currentBranch = branchResult.Trim();
if (currentBranch != "main")
{
bool doCheckout = EditorUtility.DisplayDialog("提示",
$"Design_SubModule当前在 '{currentBranch}' 分支。\n\n是否切换到main分支",
"切换到main", "取消");
if (doCheckout)
{
string checkoutResult = ExecuteGitCommand(designSubModulePath, "checkout main", out bool checkoutSuccess);
if (!checkoutSuccess)
{
EditorUtility.DisplayDialog("错误",
$"切换到main分支失败{checkoutResult}",
"确定");
return false;
}
EditorUtility.DisplayDialog("成功", "已切换到main分支", "确定");
}
else
{
return false;
}
}
else
{
Debug.Log("Design_SubModule已在main分支");
}
return true;
}
/// <summary>
/// 加载所有Art_SO资源
/// </summary>
private void LoadAllArtTableSO()
{
allArtTables.Clear();
string[] guids = AssetDatabase.FindAssets("t:ArtTableSO", new[] { ART_SO_PATH });
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
ArtTableSO artTable = AssetDatabase.LoadAssetAtPath<ArtTableSO>(path);
if (artTable != null)
{
allArtTables.Add(artTable);
}
}
Debug.Log($"加载了 {allArtTables.Count} 个ArtTableSO资源");
}
/// <summary>
/// 加载Item数据
/// </summary>
private void LoadItemData()
{
itemDict.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, 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 == "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 res = resCol > 0 ? worksheet.Cells[row, resCol].Text : "";
if (!int.TryParse(iTypeText, out int iType)) continue;
itemDict[id] = new ItemData
{
Id = id,
Name = name,
IType = iType,
Res = res
};
}
}
Debug.Log($"加载了 {itemDict.Count} 个Item数据");
}
/// <summary>
/// 加载IndoorProgress数据
/// </summary>
private void LoadIndoorProgressData()
{
allProgressDataList.Clear();
string progressExcelPath = Path.Combine(docsRootPath, "config", INDOOR_PROGRESS_EXCEL_NAME);
if (!File.Exists(progressExcelPath))
{
throw new Exception($"未找到IndoorProgress配置文件: {progressExcelPath}");
}
using (var package = new ExcelPackage(new FileInfo(progressExcelPath)))
{
var worksheet = package.Workbook.Worksheets[INDOOR_PROGRESS_SHEET_NAME];
if (worksheet == null)
{
throw new Exception($"IndoorProgress.xlsx中未找到Sheet: {INDOOR_PROGRESS_SHEET_NAME}");
}
// 读取表头第1行
int idCol = -1, sceneCol = -1, lvCol = -1, itemCol = -1, emitCol = -1,
rewardCol = -1, bigRewardCol = -1, areaRewardCol = -1, partCol = -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 == "Scene") sceneCol = col;
else if (header == "Lv") lvCol = col;
else if (header == "Item") itemCol = col;
else if (header == "Emit") emitCol = col;
else if (header == "Reward") rewardCol = col;
else if (header == "BigReward") bigRewardCol = col;
else if (header == "AreaReward") areaRewardCol = col;
else if (header == "Part") partCol = col;
}
if (idCol < 0 || sceneCol < 0 || lvCol < 0)
{
throw new Exception("IndoorProgress.xlsx表结构不正确缺少必要列Id/Scene/Lv");
}
// 读取数据从第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 sceneText = worksheet.Cells[row, sceneCol].Text;
string lvText = worksheet.Cells[row, lvCol].Text;
if (!int.TryParse(sceneText, out int scene)) continue;
if (!int.TryParse(lvText, out int lv)) continue;
string item = itemCol > 0 ? worksheet.Cells[row, itemCol].Text : "";
string emit = emitCol > 0 ? worksheet.Cells[row, emitCol].Text : "";
string reward = rewardCol > 0 ? worksheet.Cells[row, rewardCol].Text : "";
string bigReward = bigRewardCol > 0 ? worksheet.Cells[row, bigRewardCol].Text : "";
string areaReward = areaRewardCol > 0 ? worksheet.Cells[row, areaRewardCol].Text : "";
string partText = partCol > 0 ? worksheet.Cells[row, partCol].Text : "";
int.TryParse(partText, out int part);
var progressData = new IndoorProgressData
{
Id = id,
Scene = scene,
Lv = lv,
Item = item,
Emit = emit,
Reward = reward,
BigReward = bigReward,
AreaReward = areaReward,
Part = part
};
allProgressDataList.Add(progressData);
}
}
Debug.Log($"加载了 {allProgressDataList.Count} 条IndoorProgress数据");
}
/// <summary>
/// 绘制场景筛选和数据编辑器
/// </summary>
private void DrawSceneFilterAndDataEditor()
{
EditorGUILayout.BeginVertical("box");
// 场景筛选
EditorGUILayout.LabelField("场景筛选", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
sceneInputText = EditorGUILayout.TextField("输入场景ID", sceneInputText, GUILayout.Width(200));
if (GUILayout.Button("确认加载场景", GUILayout.Width(120)))
{
if (int.TryParse(sceneInputText, out int sceneId))
{
selectedScene = sceneId;
FilterProgressByScene();
}
else
{
EditorUtility.DisplayDialog("错误", "请输入有效的场景ID数字", "确定");
}
}
if (selectedScene > 0)
{
EditorGUILayout.LabelField($"当前场景: {selectedScene}", EditorStyles.boldLabel, GUILayout.Width(150));
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
if (selectedScene > 0 && filteredProgressDataList.Count > 0)
{
EditorGUILayout.Space(10);
// 新增场景按钮
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("+ 新增场景", GUILayout.Height(30), GUILayout.Width(150)))
{
AddNewScene();
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(5);
// 数据列表
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
EditorGUILayout.BeginVertical("box");
DrawProgressDataList();
EditorGUILayout.EndVertical();
EditorGUILayout.EndScrollView();
EditorGUILayout.Space(10);
// 保存按钮
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("保存配置到Excel", GUILayout.Height(40), GUILayout.Width(200)))
{
SaveData();
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
else if (selectedScene > 0)
{
EditorGUILayout.Space(10);
EditorGUILayout.HelpBox($"场景 {selectedScene} 没有数据", MessageType.Warning);
// 新增场景按钮
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("+ 新增场景", GUILayout.Height(30), GUILayout.Width(150)))
{
AddNewScene();
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
else
{
EditorGUILayout.Space(10);
EditorGUILayout.HelpBox("请输入场景ID并点击确认加载", MessageType.Info);
}
}
/// <summary>
/// 根据场景筛选数据
/// </summary>
private void FilterProgressByScene()
{
filteredProgressDataList = allProgressDataList
.Where(x => x.Scene == selectedScene)
.OrderBy(x => x.Lv)
.ToList();
}
/// <summary>
/// 绘制进度数据列表
/// </summary>
private void DrawProgressDataList()
{
// 表头
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
EditorGUILayout.LabelField("ID", EditorStyles.boldLabel, GUILayout.Width(40));
EditorGUILayout.LabelField("Lv", EditorStyles.boldLabel, GUILayout.Width(40));
EditorGUILayout.LabelField("道具奖励", EditorStyles.boldLabel, GUILayout.Width(450));
EditorGUILayout.LabelField("区域奖励", EditorStyles.boldLabel, GUILayout.Width(450));
EditorGUILayout.LabelField("零件", EditorStyles.boldLabel, GUILayout.Width(60));
EditorGUILayout.LabelField("操作", EditorStyles.boldLabel, GUILayout.Width(60));
EditorGUILayout.EndHorizontal();
// 数据行
for (int i = 0; i < filteredProgressDataList.Count; i++)
{
DrawProgressDataRow(filteredProgressDataList[i], i);
}
}
/// <summary>
/// 绘制单行进度数据
/// </summary>
private void DrawProgressDataRow(IndoorProgressData data, int index)
{
EditorGUILayout.BeginHorizontal("box");
// ID不可编辑
EditorGUILayout.LabelField(data.Id.ToString(), GUILayout.Width(40));
// Lv可编辑
EditorGUI.BeginChangeCheck();
int newLv = EditorGUILayout.IntField(data.Lv, GUILayout.Width(40));
if (EditorGUI.EndChangeCheck())
{
data.Lv = newLv;
}
// 道具奖励Item/Emit/Reward一体
EditorGUILayout.BeginVertical(GUILayout.Width(450));
DrawItemRewardEditor(data);
EditorGUILayout.EndVertical();
// 区域奖励AreaReward/BigReward
EditorGUILayout.BeginVertical(GUILayout.Width(450));
DrawAreaRewardEditor(data);
EditorGUILayout.EndVertical();
// 零件数量
EditorGUI.BeginChangeCheck();
int newPart = EditorGUILayout.IntField(data.Part, GUILayout.Width(60));
if (EditorGUI.EndChangeCheck())
{
data.Part = newPart;
}
// 操作按钮
if (GUILayout.Button("删除", GUILayout.Width(60)))
{
if (EditorUtility.DisplayDialog("确认删除",
$"确定要删除ID {data.Id} (场景{data.Scene}-Lv{data.Lv}) 的数据吗?",
"删除", "取消"))
{
filteredProgressDataList.RemoveAt(index);
allProgressDataList.Remove(data);
}
}
EditorGUILayout.EndHorizontal();
}
/// <summary>
/// 绘制道具奖励编辑器Item/Emit/Reward
/// </summary>
private void DrawItemRewardEditor(IndoorProgressData data)
{
// 解析当前数据
var itemReward = ParseItemReward(data.Item);
EditorGUILayout.BeginHorizontal();
// 选择IType
var typeOptions = new List<string> { "无" };
var typeValues = new List<int> { -1 };
foreach (var kvp in ITEM_TYPES.OrderBy(x => x.Key))
{
typeOptions.Add($"{kvp.Value} ({kvp.Key})");
typeValues.Add(kvp.Key);
}
// 初始化临时状态:如果有数据就用数据的类型,如果没数据就用临时保存的选择
if (!tempItemRewardTypeIndex.ContainsKey(data.Id))
{
if (itemReward.HasValue)
{
int itemType = GetItemType(itemReward.Value.Id);
tempItemRewardTypeIndex[data.Id] = typeValues.IndexOf(itemType);
}
else
{
tempItemRewardTypeIndex[data.Id] = 0; // 默认"无"
}
}
else if (itemReward.HasValue)
{
// 如果有数据,同步临时状态
int itemType = GetItemType(itemReward.Value.Id);
int typeIndex = typeValues.IndexOf(itemType);
if (typeIndex >= 0)
{
tempItemRewardTypeIndex[data.Id] = typeIndex;
}
}
int currentTypeIndex = tempItemRewardTypeIndex[data.Id];
if (currentTypeIndex < 0) currentTypeIndex = 0;
EditorGUI.BeginChangeCheck();
int newTypeIndex = EditorGUILayout.Popup(currentTypeIndex, typeOptions.ToArray(), GUILayout.Width(120));
bool typeChanged = EditorGUI.EndChangeCheck();
if (typeChanged)
{
tempItemRewardTypeIndex[data.Id] = newTypeIndex;
}
if (newTypeIndex == 0)
{
// 选择"无"
if (itemReward.HasValue)
{
data.Item = "";
data.Emit = "";
data.Reward = "";
}
}
else
{
int selectedType = typeValues[newTypeIndex];
// 如果类型改变,且当前有数据但类型不匹配,清空数据
if (typeChanged && itemReward.HasValue && GetItemType(itemReward.Value.Id) != selectedType)
{
data.Item = "";
data.Emit = "";
data.Reward = "";
itemReward = null;
}
// 根据类型筛选Item
var itemsOfType = itemDict.Values.Where(x => x.IType == selectedType).ToList();
if (itemsOfType.Count > 0)
{
// IType=100(棋子)使用输入框+刷新按钮,其他使用下拉菜单
if (selectedType == 100)
{
// 输入ID
EditorGUILayout.LabelField("ID:", GUILayout.Width(25));
// 当前ID如果类型改变重置为0否则使用现有值
int inputId = 0;
if (!typeChanged && itemReward.HasValue && GetItemType(itemReward.Value.Id) == 100)
{
inputId = itemReward.Value.Id;
}
EditorGUI.BeginChangeCheck();
inputId = EditorGUILayout.IntField(inputId, GUILayout.Width(60));
bool idChanged = EditorGUI.EndChangeCheck();
// 数量
EditorGUILayout.LabelField("数量:", GUILayout.Width(35));
int num = itemReward.HasValue ? itemReward.Value.Num : 1;
EditorGUI.BeginChangeCheck();
num = EditorGUILayout.IntField(num, GUILayout.Width(40));
bool numChanged = EditorGUI.EndChangeCheck();
// 刷新按钮
if (GUILayout.Button("刷新", GUILayout.Width(50)))
{
if (inputId > 0 && itemDict.ContainsKey(inputId) && itemDict[inputId].IType == 100)
{
UpdateItemReward(data, inputId, num);
}
else
{
EditorUtility.DisplayDialog("错误", $"ID {inputId} 不存在或不是棋子类型", "确定");
}
}
// 如果ID或数量改变自动更新
if (idChanged || numChanged)
{
if (inputId > 0 && itemDict.ContainsKey(inputId) && itemDict[inputId].IType == 100)
{
UpdateItemReward(data, inputId, num);
}
}
// 显示预览图
if (inputId > 0 && itemDict.TryGetValue(inputId, out var item) && item.IType == 100)
{
DrawItemPreview(item);
}
else
{
EditorGUILayout.LabelField("暂无预览", GUILayout.Width(60));
}
}
else
{
// 其他类型使用下拉菜单
var itemOptions = itemsOfType.Select(x => $"{x.Name} ({x.Id})").ToList();
var itemIds = itemsOfType.Select(x => x.Id).ToList();
int currentItemIndex = 0;
if (!typeChanged && itemReward.HasValue)
{
currentItemIndex = itemIds.IndexOf(itemReward.Value.Id);
if (currentItemIndex < 0) currentItemIndex = 0;
}
EditorGUI.BeginChangeCheck();
int newItemIndex = EditorGUILayout.Popup(currentItemIndex, itemOptions.ToArray(), GUILayout.Width(180));
int selectedItemId = itemIds[newItemIndex];
// 数量
int num = itemReward.HasValue ? itemReward.Value.Num : 1;
num = EditorGUILayout.IntField(num, GUILayout.Width(40));
// 更新数据
if (EditorGUI.EndChangeCheck() || typeChanged || !itemReward.HasValue)
{
UpdateItemReward(data, selectedItemId, num);
}
// 显示预览图
if (itemDict.TryGetValue(selectedItemId, out var item2))
{
DrawItemPreview(item2);
}
}
}
else
{
EditorGUILayout.LabelField("该类型无可用Item", GUILayout.Width(150));
}
}
EditorGUILayout.EndHorizontal();
}
/// <summary>
/// 绘制区域奖励编辑器AreaReward/BigReward
/// </summary>
private void DrawAreaRewardEditor(IndoorProgressData data)
{
// 判断当前Lv是否可以配置AreaReward
bool canConfigArea = CanConfigAreaReward(data.Scene, data.Lv);
if (!canConfigArea)
{
// 不显示任何内容,保持空白
return;
}
// 解析当前AreaReward
var areaRewards = ParseAreaReward(data.AreaReward);
EditorGUILayout.BeginVertical();
// 显示现有奖励
for (int i = 0; i < areaRewards.Count; i++)
{
EditorGUILayout.BeginHorizontal();
var reward = areaRewards[i];
// 显示Item信息和图片
if (itemDict.TryGetValue(reward.Id, out var rewardItem))
{
EditorGUILayout.LabelField($"{rewardItem.Name}", GUILayout.Width(100));
// 显示预览图
DrawItemPreview(rewardItem);
}
else
{
EditorGUILayout.LabelField($"ID:{reward.Id}", GUILayout.Width(100));
EditorGUILayout.LabelField("无图", GUILayout.Width(50));
}
// 数量
EditorGUILayout.LabelField("x", GUILayout.Width(10));
EditorGUI.BeginChangeCheck();
int newNum = EditorGUILayout.IntField(reward.Num, GUILayout.Width(40));
if (EditorGUI.EndChangeCheck())
{
areaRewards[i] = new ItemReward { Id = reward.Id, Num = newNum };
UpdateAreaReward(data, areaRewards);
}
// 删除按钮
if (GUILayout.Button("-", GUILayout.Width(30)))
{
areaRewards.RemoveAt(i);
UpdateAreaReward(data, areaRewards);
break;
}
EditorGUILayout.EndHorizontal();
}
// 添加新奖励UI
DrawAddAreaRewardUI(data, areaRewards);
EditorGUILayout.EndVertical();
}
/// <summary>
/// 判断是否可以配置AreaReward
/// </summary>
private bool CanConfigAreaReward(int scene, int lv)
{
if (scene == 1)
{
// 场景1: 20, 29, 36, 44
return lv == 20 || lv == 29 || lv == 36 || lv == 44;
}
else if (scene >= 2 && scene <= 5)
{
// 场景2-5: 20
return lv == 20;
}
else
{
// 其他场景: 25
return lv == 25;
}
}
/// <summary>
/// 绘制添加区域奖励UI
/// </summary>
private void DrawAddAreaRewardUI(IndoorProgressData data, List<ItemReward> areaRewards)
{
EditorGUILayout.BeginHorizontal();
// 选择IType
var typeOptions = new List<string> { "选择类型..." };
var typeValues = new List<int> { -1 };
foreach (var kvp in ITEM_TYPES.OrderBy(x => x.Key))
{
typeOptions.Add($"{kvp.Value} ({kvp.Key})");
typeValues.Add(kvp.Key);
}
// 使用临时变量存储当前选择
if (!tempAreaRewardTypeIndex.ContainsKey(data.Id))
{
tempAreaRewardTypeIndex[data.Id] = 0;
}
if (!tempAreaRewardItemId.ContainsKey(data.Id))
{
tempAreaRewardItemId[data.Id] = 0;
}
if (!tempAreaRewardNum.ContainsKey(data.Id))
{
tempAreaRewardNum[data.Id] = 1;
}
int currentTypeIndex = tempAreaRewardTypeIndex[data.Id];
EditorGUI.BeginChangeCheck();
int newTypeIndex = EditorGUILayout.Popup(currentTypeIndex, typeOptions.ToArray(), GUILayout.Width(120));
bool typeChanged = EditorGUI.EndChangeCheck();
if (typeChanged)
{
tempAreaRewardTypeIndex[data.Id] = newTypeIndex;
tempAreaRewardItemId[data.Id] = 0; // 重置ItemId
}
if (newTypeIndex > 0)
{
int selectedType = typeValues[newTypeIndex];
// 根据类型筛选Item
var itemsOfType = itemDict.Values.Where(x => x.IType == selectedType).ToList();
if (itemsOfType.Count > 0)
{
// IType=100(棋子)使用输入框+添加按钮
if (selectedType == 100)
{
EditorGUILayout.LabelField("ID:", GUILayout.Width(25));
int inputId = tempAreaRewardItemId[data.Id];
inputId = EditorGUILayout.IntField(inputId, GUILayout.Width(60));
tempAreaRewardItemId[data.Id] = inputId;
EditorGUILayout.LabelField("数量:", GUILayout.Width(35));
int num = tempAreaRewardNum[data.Id];
num = EditorGUILayout.IntField(num, GUILayout.Width(40));
tempAreaRewardNum[data.Id] = num;
if (GUILayout.Button("添加", GUILayout.Width(50)))
{
if (itemDict.ContainsKey(inputId) && itemDict[inputId].IType == 100)
{
areaRewards.Add(new ItemReward { Id = inputId, Num = num });
UpdateAreaReward(data, areaRewards);
// 重置
tempAreaRewardTypeIndex[data.Id] = 0;
tempAreaRewardItemId[data.Id] = 0;
tempAreaRewardNum[data.Id] = 1;
}
else
{
EditorUtility.DisplayDialog("错误", $"ID {inputId} 不存在或不是棋子类型", "确定");
}
}
// 显示预览图
if (itemDict.TryGetValue(inputId, out var previewItem) && previewItem.IType == 100)
{
DrawItemPreview(previewItem);
}
else
{
EditorGUILayout.LabelField("暂无预览", GUILayout.Width(60));
}
}
else
{
// 其他类型使用下拉菜单
var itemOptions = itemsOfType.Select(x => $"{x.Name} ({x.Id})").ToList();
var itemIds = itemsOfType.Select(x => x.Id).ToList();
int savedItemId = tempAreaRewardItemId[data.Id];
int currentItemIndex = itemIds.IndexOf(savedItemId);
if (currentItemIndex < 0) currentItemIndex = 0;
EditorGUI.BeginChangeCheck();
int newItemIndex = EditorGUILayout.Popup(currentItemIndex, itemOptions.ToArray(), GUILayout.Width(150));
if (EditorGUI.EndChangeCheck())
{
tempAreaRewardItemId[data.Id] = itemIds[newItemIndex];
}
int selectedItemId = itemIds[newItemIndex];
// 数量
int num = tempAreaRewardNum[data.Id];
num = EditorGUILayout.IntField(num, GUILayout.Width(40));
tempAreaRewardNum[data.Id] = num;
if (GUILayout.Button("添加", GUILayout.Width(50)))
{
areaRewards.Add(new ItemReward { Id = selectedItemId, Num = num });
UpdateAreaReward(data, areaRewards);
// 重置
tempAreaRewardTypeIndex[data.Id] = 0;
tempAreaRewardItemId[data.Id] = 0;
tempAreaRewardNum[data.Id] = 1;
}
// 显示预览图
if (itemDict.TryGetValue(selectedItemId, out var previewItem2))
{
DrawItemPreview(previewItem2);
}
}
}
else
{
EditorGUILayout.LabelField("该类型无可用Item", GUILayout.Width(120));
}
}
EditorGUILayout.EndHorizontal();
}
/// <summary>
/// 解析Item奖励
/// </summary>
private ItemReward? ParseItemReward(string itemJson)
{
if (string.IsNullOrEmpty(itemJson)) return null;
try
{
// 格式: [{"Id":83,"Num":1}]
itemJson = itemJson.Trim();
if (!itemJson.StartsWith("[") || !itemJson.EndsWith("]"))
return null;
itemJson = itemJson.Substring(1, itemJson.Length - 2); // 去掉[]
// 简单解析
var idMatch = System.Text.RegularExpressions.Regex.Match(itemJson, @"""Id"":(\d+)");
var numMatch = System.Text.RegularExpressions.Regex.Match(itemJson, @"""Num"":(\d+)");
if (idMatch.Success && numMatch.Success)
{
return new ItemReward
{
Id = int.Parse(idMatch.Groups[1].Value),
Num = int.Parse(numMatch.Groups[1].Value)
};
}
}
catch { }
return null;
}
/// <summary>
/// 解析区域奖励
/// </summary>
private List<ItemReward> ParseAreaReward(string areaRewardJson)
{
var result = new List<ItemReward>();
if (string.IsNullOrEmpty(areaRewardJson)) return result;
try
{
// 格式: [{"Id":100001,"Num":25}] 或 [{"Id":100001,"Num":50},{"Id":100021,"Num":1}]
var matches = System.Text.RegularExpressions.Regex.Matches(areaRewardJson, @"\{""Id"":(\d+),""Num"":(\d+)\}");
foreach (System.Text.RegularExpressions.Match match in matches)
{
result.Add(new ItemReward
{
Id = int.Parse(match.Groups[1].Value),
Num = int.Parse(match.Groups[2].Value)
});
}
}
catch { }
return result;
}
/// <summary>
/// 获取Item的类型
/// </summary>
private int GetItemType(int itemId)
{
if (itemDict.TryGetValue(itemId, out var item))
{
return item.IType;
}
return -1;
}
/// <summary>
/// 更新Item奖励
/// </summary>
private void UpdateItemReward(IndoorProgressData data, int itemId, int num)
{
// Item格式: [{"Id":83,"Num":1}]
data.Item = $"[{{\"Id\":{itemId},\"Num\":{num}}}]";
// Emit格式: 83
data.Emit = itemId.ToString();
// Reward格式: 83=1
data.Reward = $"{itemId}={num}";
}
/// <summary>
/// 更新区域奖励
/// </summary>
private void UpdateAreaReward(IndoorProgressData data, List<ItemReward> areaRewards)
{
if (areaRewards.Count == 0)
{
data.AreaReward = "";
data.BigReward = "";
// 如果这是最后一个AreaReward位置需要清空Lv=1的BigReward
if (IsLastAreaRewardPosition(data.Scene, data.Lv))
{
var lv1Data = allProgressDataList.FirstOrDefault(x => x.Scene == data.Scene && x.Lv == 1);
if (lv1Data != null)
{
lv1Data.BigReward = "";
}
}
return;
}
// AreaReward格式: [{"Id":100001,"Num":25}] 或 [{"Id":100001,"Num":50},{"Id":100021,"Num":1}]
var areaRewardItems = areaRewards.Select(r => $"{{\"Id\":{r.Id},\"Num\":{r.Num}}}");
data.AreaReward = $"[{string.Join(",", areaRewardItems)}]";
// 当前行的BigReward清空只有Lv=1才有BigReward
data.BigReward = "";
// 如果这是最后一个AreaReward位置更新Lv=1的BigReward
if (IsLastAreaRewardPosition(data.Scene, data.Lv))
{
var lv1Data = allProgressDataList.FirstOrDefault(x => x.Scene == data.Scene && x.Lv == 1);
if (lv1Data != null)
{
// BigReward格式: Energy=25 或 Energy=50,PurplePig=1,101451=1
var bigRewardItems = new List<string>();
bool hasUnmappedItem = false;
foreach (var reward in areaRewards)
{
if (MY_ITEM_DICT.TryGetValue(reward.Id, out string mappedName))
{
bigRewardItems.Add($"{mappedName}={reward.Num}");
}
else
{
bigRewardItems.Add($"{reward.Id}={reward.Num}");
hasUnmappedItem = true;
}
}
lv1Data.BigReward = string.Join(",", bigRewardItems);
// 如果有未映射的Item显示警告
if (hasUnmappedItem)
{
Debug.LogWarning($"场景{data.Scene}的AreaReward包含未映射的ItemId可能会出问题");
}
}
}
}
/// <summary>
/// 判断当前Lv是否为最后一个AreaReward位置
/// </summary>
private bool IsLastAreaRewardPosition(int scene, int lv)
{
if (scene == 1)
{
// 场景1的最后一个是44
return lv == 44;
}
else if (scene >= 2 && scene <= 5)
{
// 场景2-5的最后一个是20
return lv == 20;
}
else
{
// 其他场景的最后一个是25
return lv == 25;
}
}
/// <summary>
/// 绘制Item预览
/// </summary>
private void DrawItemPreview(ItemData item)
{
if (string.IsNullOrEmpty(item.Res))
{
EditorGUILayout.LabelField("无图", GUILayout.Width(50));
return;
}
// 解析Res格式: "tableId,itemId"
var parts = item.Res.Split(',');
if (parts.Length != 2)
{
EditorGUILayout.LabelField("无图", GUILayout.Width(50));
return;
}
if (!int.TryParse(parts[0], out int tableId)) return;
if (!int.TryParse(parts[1], out int itemId)) return;
// 查找对应的ArtTableSO
var artTable = allArtTables.FirstOrDefault(x => x.TableId == tableId);
if (artTable == null)
{
EditorGUILayout.LabelField("无图", GUILayout.Width(50));
return;
}
var artItem = artTable.Items.FirstOrDefault(x => x.Id == itemId);
if (artItem == null || artItem.Sprite == null)
{
EditorGUILayout.LabelField("无图", GUILayout.Width(50));
return;
}
// 显示预览图
Rect rect = GUILayoutUtility.GetRect(50, 50, GUILayout.Width(50), GUILayout.Height(50));
GUI.DrawTexture(rect, artItem.Sprite.texture, ScaleMode.ScaleToFit);
}
/// <summary>
/// 新增场景
/// </summary>
private void AddNewScene()
{
// 找到最大的Scene ID
int maxScene = allProgressDataList.Count > 0 ? allProgressDataList.Max(x => x.Scene) : 0;
int newScene = maxScene + 1;
// 找到最大的ID
int maxId = allProgressDataList.Count > 0 ? allProgressDataList.Max(x => x.Id) : 0;
// 创建25个新记录
for (int lv = 1; lv <= 25; lv++)
{
maxId++;
var newData = new IndoorProgressData
{
Id = maxId,
Scene = newScene,
Lv = lv,
Item = "",
Emit = "",
Reward = "",
BigReward = "",
AreaReward = "",
Part = 0
};
// 如果是Lv25添加默认的AreaReward
if (lv == 25)
{
// 默认给25能量
newData.AreaReward = "[{\"Id\":100001,\"Num\":25}]";
newData.BigReward = "Energy=25";
}
allProgressDataList.Add(newData);
}
// 切换到新场景
selectedScene = newScene;
sceneInputText = newScene.ToString();
FilterProgressByScene();
EditorUtility.DisplayDialog("成功", $"已创建场景 {newScene}包含25个步骤Lv 1-25", "确定");
}
/// <summary>
/// 保存数据
/// </summary>
private void SaveData()
{
try
{
string progressExcelPath = Path.Combine(docsRootPath, "config", INDOOR_PROGRESS_EXCEL_NAME);
if (!File.Exists(progressExcelPath))
{
EditorUtility.DisplayDialog("错误", $"未找到IndoorProgress配置文件: {progressExcelPath}", "确定");
return;
}
using (var package = new ExcelPackage(new FileInfo(progressExcelPath)))
{
var worksheet = package.Workbook.Worksheets[INDOOR_PROGRESS_SHEET_NAME];
if (worksheet == null)
{
EditorUtility.DisplayDialog("错误", $"IndoorProgress.xlsx中未找到Sheet: {INDOOR_PROGRESS_SHEET_NAME}", "确定");
return;
}
// 查找列索引
int idCol = -1, sceneCol = -1, lvCol = -1, itemCol = -1, emitCol = -1,
rewardCol = -1, bigRewardCol = -1, areaRewardCol = -1, partCol = -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 == "Scene") sceneCol = col;
else if (header == "Lv") lvCol = col;
else if (header == "Item") itemCol = col;
else if (header == "Emit") emitCol = col;
else if (header == "Reward") rewardCol = col;
else if (header == "BigReward") bigRewardCol = col;
else if (header == "AreaReward") areaRewardCol = col;
else if (header == "Part") partCol = col;
}
// 更新和删除数据从第3行开始
int rowCount = worksheet.Dimension.Rows;
var processedIds = new HashSet<int>();
// 第一遍:更新现有行或标记删除
for (int row = rowCount; row >= 3; row--)
{
string idText = worksheet.Cells[row, idCol].Text;
if (string.IsNullOrEmpty(idText)) continue;
if (!int.TryParse(idText, out int id)) continue;
// 查找对应的数据
var progressData = allProgressDataList.Find(x => x.Id == id);
if (progressData != null)
{
// 更新数据
worksheet.Cells[row, sceneCol].Value = progressData.Scene;
worksheet.Cells[row, lvCol].Value = progressData.Lv;
worksheet.Cells[row, itemCol].Value = progressData.Item;
worksheet.Cells[row, emitCol].Value = progressData.Emit;
worksheet.Cells[row, rewardCol].Value = progressData.Reward;
worksheet.Cells[row, bigRewardCol].Value = progressData.BigReward;
worksheet.Cells[row, areaRewardCol].Value = progressData.AreaReward;
worksheet.Cells[row, partCol].Value = progressData.Part > 0 ? progressData.Part.ToString() : "";
processedIds.Add(id);
}
else
{
// 删除行
worksheet.DeleteRow(row);
}
}
// 第二遍:添加新行
int currentRow = worksheet.Dimension?.Rows ?? 2;
foreach (var progressData in allProgressDataList.OrderBy(x => x.Id))
{
if (!processedIds.Contains(progressData.Id))
{
// 这是新增的数据
currentRow++;
worksheet.Cells[currentRow, idCol].Value = progressData.Id;
worksheet.Cells[currentRow, sceneCol].Value = progressData.Scene;
worksheet.Cells[currentRow, lvCol].Value = progressData.Lv;
worksheet.Cells[currentRow, itemCol].Value = progressData.Item;
worksheet.Cells[currentRow, emitCol].Value = progressData.Emit;
worksheet.Cells[currentRow, rewardCol].Value = progressData.Reward;
worksheet.Cells[currentRow, bigRewardCol].Value = progressData.BigReward;
worksheet.Cells[currentRow, areaRewardCol].Value = progressData.AreaReward;
worksheet.Cells[currentRow, partCol].Value = progressData.Part > 0 ? progressData.Part.ToString() : "";
}
}
// 保存文件
package.Save();
}
// 提示成功并提醒推送
EditorUtility.DisplayDialog("保存成功",
"配置已保存到Excel文件\n\n" +
"⚠️ 重要提醒:\n" +
"请及时在SourceTree或GitHubDesktop中\n" +
"1. 提交(Commit)本次修改\n" +
"2. 推送(Push)到远程仓库\n\n" +
"避免与其他策划产生冲突!",
"我知道了");
}
catch (Exception e)
{
EditorUtility.DisplayDialog("错误", $"保存数据失败: {e.Message}\n{e.StackTrace}", "确定");
}
}
/// <summary>
/// 场景进度数据类
/// </summary>
private class IndoorProgressData
{
public int Id;
public int Scene;
public int Lv;
public string Item;
public string Emit;
public string Reward;
public string BigReward;
public string AreaReward;
public int Part;
}
/// <summary>
/// Item数据类
/// </summary>
private class ItemData
{
public int Id;
public string Name;
public int IType;
public string Res;
}
/// <summary>
/// Item奖励结构
/// </summary>
private struct ItemReward
{
public int Id;
public int Num;
}
}
}