diff --git a/Scripts/Editor/Design_Tools/Scene/DecorateCostPlannerEditor.cs b/Scripts/Editor/Design_Tools/Scene/DecorateCostPlannerEditor.cs
new file mode 100644
index 0000000..aef998a
--- /dev/null
+++ b/Scripts/Editor/Design_Tools/Scene/DecorateCostPlannerEditor.cs
@@ -0,0 +1,949 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using OfficeOpenXml;
+using UnityEditor;
+using UnityEditor.UIElements;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+namespace DesignTools.Scene
+{
+ ///
+ /// 装饰场景消耗/批次配置工具
+ /// 读取 Docs/config/DecorateCost.xlsx 的 DecorateCost Sheet,供策划编辑每步消耗与批次。
+ ///
+ public class DecorateCostPlannerEditor : BaseDesignToolEditor
+ {
+ private static readonly string[] BATCH_NAMES =
+ {
+ string.Empty,
+ "first",
+ "second",
+ "third",
+ "four",
+ "five",
+ "six",
+ "seven",
+ "eight",
+ "nine",
+ "ten",
+ "eleven",
+ "twelve",
+ "thirteen",
+ "fourteen",
+ "fifteen",
+ "sixteen",
+ "seventeen",
+ "eighteen",
+ "nineteen",
+ "twenty",
+ "twentyone",
+ "twentytwo",
+ "twentythree",
+ "twentyfour",
+ "twentyfive",
+ "twentysix",
+ "twentyseven",
+ "twentyeight",
+ "twentynine",
+ "thirty",
+ "thirtyone",
+ "thirtytwo",
+ "thirtythree",
+ "thirtyfour",
+ "thirtyfive",
+ "thirtysix",
+ "thirtyseven",
+ "thirtyeight",
+ "thirtynine",
+ "forty",
+ "fortyone"
+ };
+
+ private const string DECORATE_COST_EXCEL_NAME = "DecorateCost.xlsx";
+ private const string DECORATE_COST_SHEET_NAME = "DecorateCost";
+ private const int DATA_START_ROW = 3;
+ private const int TOTAL_COL_COUNT = 17;
+
+ private const int COL_ID = 1;
+ private const int COL_AREA_ID = 2;
+ private const int COL_SORT_ID = 3;
+ private const int COL_COST_COUNT = 4;
+ private const int COL_POS = 7;
+
+ private readonly List allRows = new List();
+ private readonly Dictionary rowIndexMap = new Dictionary();
+ private readonly HashSet dirtyRowIndexes = new HashSet();
+ private List availableAreaIds = new List();
+ private List currentAreaStepRows = new List();
+
+ private int selectedAreaId = -1;
+ private int areaIdInput = 2;
+ private int currentAreaExpectedStepCount;
+
+ [MenuItem("策划工具/场景/装饰场景消耗与批次")]
+ public static void ShowWindow()
+ {
+ var window = GetWindow("装饰场景消耗与批次");
+ window.minSize = window.GetMinWindowSize();
+ window.Show();
+ }
+
+ protected override string GetDocsPathPrefKey()
+ {
+ return "DecorateCostPlannerEditor_DocsPath";
+ }
+
+ protected override string GetWindowTitle()
+ {
+ return "装饰场景消耗与批次工具";
+ }
+
+ protected override Vector2 GetMinWindowSize()
+ {
+ return new Vector2(920, 680);
+ }
+
+ protected override void LoadConfigData()
+ {
+ LoadDecorateCostExcel();
+ currentAreaStepRows.Clear();
+ currentAreaExpectedStepCount = 0;
+ selectedAreaId = -1;
+ areaIdInput = GetDefaultAreaId();
+
+ if (availableAreaIds.Count == 0)
+ {
+ EditorUtility.DisplayDialog("提示", "DecorateCost 中没有可编辑的 AreaId 数据。", "确定");
+ return;
+ }
+
+ EditorApplication.delayCall += ShowAreaInputDialog;
+ }
+
+ protected override void DrawDataEditor()
+ {
+ DrawAreaSelector();
+ EditorGUILayout.Space(6);
+
+ if (selectedAreaId < 0)
+ {
+ EditorGUILayout.HelpBox("请先输入并加载 AreaId。AreaId=1 不允许编辑;AreaId=2-5 为 20 步;AreaId>=6 为 25 步。", MessageType.Info);
+ return;
+ }
+
+ DrawAreaSummary();
+ EditorGUILayout.Space(6);
+ DrawQuickActions();
+ EditorGUILayout.Space(6);
+ DrawStepTable();
+ }
+
+ protected override void SaveDataToExcel()
+ {
+ SaveDecorateCostExcel();
+ }
+
+ private void LoadDecorateCostExcel()
+ {
+ allRows.Clear();
+ rowIndexMap.Clear();
+ dirtyRowIndexes.Clear();
+
+ string excelPath = GetDocsConfigFilePath(DECORATE_COST_EXCEL_NAME);
+ if (!File.Exists(excelPath))
+ {
+ throw new FileNotFoundException($"未找到 {DECORATE_COST_EXCEL_NAME}", excelPath);
+ }
+
+ using (ExcelPackage package = new ExcelPackage(new FileInfo(excelPath)))
+ {
+ ExcelWorksheet worksheet = package.Workbook.Worksheets[DECORATE_COST_SHEET_NAME];
+ if (worksheet == null)
+ {
+ throw new Exception($"未找到 Sheet: {DECORATE_COST_SHEET_NAME}");
+ }
+
+ if (worksheet.Dimension == null)
+ {
+ availableAreaIds = new List();
+ return;
+ }
+
+ int rowCount = worksheet.Dimension.End.Row;
+ for (int row = DATA_START_ROW; row <= rowCount; row++)
+ {
+ string idText = worksheet.Cells[row, COL_ID].Text.Trim();
+ string areaIdText = worksheet.Cells[row, COL_AREA_ID].Text.Trim();
+ string sortIdText = worksheet.Cells[row, COL_SORT_ID].Text.Trim();
+
+ if (string.IsNullOrEmpty(idText) && string.IsNullOrEmpty(areaIdText) && string.IsNullOrEmpty(sortIdText))
+ {
+ continue;
+ }
+
+ string[] fields = new string[TOTAL_COL_COUNT];
+ for (int col = 1; col <= TOTAL_COL_COUNT; col++)
+ {
+ fields[col - 1] = worksheet.Cells[row, col].Text ?? string.Empty;
+ }
+
+ DecorateCostExcelRow dataRow = new DecorateCostExcelRow(row, fields);
+ allRows.Add(dataRow);
+ rowIndexMap[row] = dataRow;
+ }
+ }
+
+ availableAreaIds = allRows
+ .Select(row => row.AreaId)
+ .Where(areaId => areaId > 1)
+ .Distinct()
+ .OrderBy(areaId => areaId)
+ .ToList();
+ }
+
+ private void DrawAreaSelector()
+ {
+ EditorGUILayout.BeginVertical("box");
+ EditorGUILayout.LabelField("AreaId 选择", EditorStyles.boldLabel);
+
+ EditorGUILayout.BeginHorizontal();
+ areaIdInput = EditorGUILayout.IntField("AreaId", areaIdInput, GUILayout.Width(240));
+
+ if (GUILayout.Button("加载 AreaId", GUILayout.Width(120), GUILayout.Height(24)))
+ {
+ TrySelectArea(areaIdInput, true);
+ }
+
+ if (GUILayout.Button("重新输入", GUILayout.Width(100), GUILayout.Height(24)))
+ {
+ ShowAreaInputDialog();
+ }
+
+ GUILayout.FlexibleSpace();
+ EditorGUILayout.EndHorizontal();
+
+ string previewText = availableAreaIds.Count <= 15
+ ? string.Join(", ", availableAreaIds)
+ : string.Join(", ", availableAreaIds.Take(15)) + " ...";
+ EditorGUILayout.HelpBox($"可编辑 AreaId:{previewText}", MessageType.None);
+ EditorGUILayout.EndVertical();
+ }
+
+ private void DrawAreaSummary()
+ {
+ List missingSortIds = GetMissingSortIds();
+ int actualCount = currentAreaStepRows.Count;
+ string summary = $"当前 AreaId:{selectedAreaId}\n期望步数:{currentAreaExpectedStepCount}\n实际可编辑步数:{actualCount}\n未保存修改:{dirtyRowIndexes.Count} 行";
+
+ if (missingSortIds.Count > 0)
+ {
+ string missingText = string.Join(", ", missingSortIds.Take(10));
+ if (missingSortIds.Count > 10)
+ {
+ missingText += " ...";
+ }
+
+ EditorGUILayout.HelpBox(summary + $"\n\n⚠ 缺失步骤:{missingText}", MessageType.Warning);
+ return;
+ }
+
+ EditorGUILayout.HelpBox(summary, MessageType.Info);
+ }
+
+ private void DrawQuickActions()
+ {
+ using (new EditorGUI.DisabledScope(currentAreaStepRows.Count == 0 || currentAreaExpectedStepCount <= 0))
+ {
+ EditorGUILayout.BeginHorizontal();
+
+ GUI.backgroundColor = new Color(0.78f, 0.92f, 1f);
+ if (GUILayout.Button("快捷设置资源消耗", GUILayout.Width(180), GUILayout.Height(28)))
+ {
+ DecorateCostPlannerBatchCostWindow.ShowWindow(currentAreaExpectedStepCount, ApplyBatchCostSettings);
+ }
+
+ GUI.backgroundColor = new Color(1f, 0.9f, 0.65f);
+ if (GUILayout.Button("快捷设置步骤批次", GUILayout.Width(180), GUILayout.Height(28)))
+ {
+ DecorateCostPlannerBatchSkipWindow.ShowWindow(currentAreaExpectedStepCount, ApplyBatchBatchSettings);
+ }
+
+ GUI.backgroundColor = Color.white;
+ GUILayout.FlexibleSpace();
+ EditorGUILayout.EndHorizontal();
+ }
+
+ GUI.backgroundColor = Color.white;
+ }
+
+ private void DrawStepTable()
+ {
+ EditorGUILayout.BeginVertical("box");
+ EditorGUILayout.LabelField("步骤配置", EditorStyles.boldLabel);
+ EditorGUILayout.Space(4);
+
+ EditorGUILayout.BeginHorizontal();
+ EditorGUILayout.LabelField("SortId", EditorStyles.boldLabel, GUILayout.Width(70));
+ EditorGUILayout.LabelField("CostCount", EditorStyles.boldLabel, GUILayout.Width(120));
+ EditorGUILayout.LabelField("步骤批次", EditorStyles.boldLabel, GUILayout.Width(140));
+ EditorGUILayout.LabelField("批次映射", EditorStyles.boldLabel, GUILayout.Width(120));
+ EditorGUILayout.LabelField("Title", EditorStyles.boldLabel, GUILayout.Width(260));
+ EditorGUILayout.EndHorizontal();
+
+ EditorGUILayout.Space(2);
+ scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
+
+ foreach (DecorateCostExcelRow row in currentAreaStepRows.OrderBy(item => item.SortId))
+ {
+ DrawSingleStepRow(row);
+ }
+
+ EditorGUILayout.EndScrollView();
+ EditorGUILayout.EndVertical();
+ }
+
+ private void DrawSingleStepRow(DecorateCostExcelRow row)
+ {
+ EditorGUILayout.BeginHorizontal("box");
+ EditorGUILayout.LabelField(row.SortId.ToString(), GUILayout.Width(70));
+
+ EditorGUI.BeginChangeCheck();
+ int newCostCount = EditorGUILayout.IntField(row.CostCount, GUILayout.Width(120));
+ if (EditorGUI.EndChangeCheck())
+ {
+ row.CostCount = Mathf.Max(0, newCostCount);
+ MarkRowDirty(row);
+ }
+
+ EditorGUI.BeginChangeCheck();
+ int newBatchValue = EditorGUILayout.IntField(row.BatchValue, GUILayout.Width(140));
+ if (EditorGUI.EndChangeCheck())
+ {
+ row.BatchValue = Mathf.Max(0, newBatchValue);
+ MarkRowDirty(row);
+ }
+
+ EditorGUILayout.LabelField(GetBatchDisplayName(row.BatchValue), GUILayout.Width(120));
+
+ EditorGUILayout.LabelField(string.IsNullOrEmpty(row.Title) ? "-" : row.Title, GUILayout.Width(260));
+ GUILayout.FlexibleSpace();
+ EditorGUILayout.EndHorizontal();
+ }
+
+ private void ApplyBatchCostSettings(int[] costs)
+ {
+ if (costs == null || costs.Length == 0)
+ {
+ return;
+ }
+
+ int updatedCount = 0;
+ List missingSortIds = new List();
+
+ for (int step = 1; step <= currentAreaExpectedStepCount; step++)
+ {
+ DecorateCostExcelRow row = currentAreaStepRows.FirstOrDefault(item => item.SortId == step);
+ if (row == null)
+ {
+ missingSortIds.Add(step);
+ continue;
+ }
+
+ int groupIndex = Mathf.Clamp((step - 1) / 5, 0, costs.Length - 1);
+ if (row.CostCount != costs[groupIndex])
+ {
+ row.CostCount = Mathf.Max(0, costs[groupIndex]);
+ MarkRowDirty(row);
+ updatedCount++;
+ }
+ }
+
+ ShowBatchApplyResult("资源消耗", updatedCount, missingSortIds);
+ }
+
+ private void ApplyBatchBatchSettings(int[] batches)
+ {
+ if (batches == null || batches.Length == 0)
+ {
+ return;
+ }
+
+ int updatedCount = 0;
+ List missingSortIds = new List();
+
+ for (int step = 1; step <= currentAreaExpectedStepCount; step++)
+ {
+ DecorateCostExcelRow row = currentAreaStepRows.FirstOrDefault(item => item.SortId == step);
+ if (row == null)
+ {
+ missingSortIds.Add(step);
+ continue;
+ }
+
+ int batchValue = step - 1 < batches.Length ? Mathf.Max(0, batches[step - 1]) : 0;
+ if (row.BatchValue != batchValue)
+ {
+ row.BatchValue = batchValue;
+ MarkRowDirty(row);
+ updatedCount++;
+ }
+ }
+
+ ShowBatchApplyResult("步骤批次", updatedCount, missingSortIds);
+ }
+
+ private void ShowBatchApplyResult(string label, int updatedCount, List missingSortIds)
+ {
+ string message = $"已应用{label}快捷设置,更新 {updatedCount} 步。";
+ if (missingSortIds.Count > 0)
+ {
+ message += $"\n\n缺失步骤:{string.Join(", ", missingSortIds)}";
+ }
+
+ EditorUtility.DisplayDialog("完成", message, "确定");
+ }
+
+ private void SaveDecorateCostExcel()
+ {
+ if (dirtyRowIndexes.Count == 0)
+ {
+ EditorUtility.DisplayDialog("提示", "当前没有需要保存的修改。", "确定");
+ return;
+ }
+
+ string excelPath = GetDocsConfigFilePath(DECORATE_COST_EXCEL_NAME);
+ if (!File.Exists(excelPath))
+ {
+ EditorUtility.DisplayDialog("错误", $"未找到文件:{excelPath}", "确定");
+ return;
+ }
+
+ try
+ {
+ using (ExcelPackage package = new ExcelPackage(new FileInfo(excelPath)))
+ {
+ ExcelWorksheet worksheet = package.Workbook.Worksheets[DECORATE_COST_SHEET_NAME];
+ if (worksheet == null)
+ {
+ EditorUtility.DisplayDialog("错误", $"未找到 Sheet:{DECORATE_COST_SHEET_NAME}", "确定");
+ return;
+ }
+
+ foreach (int rowIndex in dirtyRowIndexes.OrderBy(index => index))
+ {
+ if (!rowIndexMap.TryGetValue(rowIndex, out DecorateCostExcelRow row))
+ {
+ continue;
+ }
+
+ worksheet.Cells[row.RowIndex, COL_COST_COUNT].Value = row.CostCount;
+ worksheet.Cells[row.RowIndex, COL_POS].Value = string.IsNullOrEmpty(row.Pos) ? null : row.Pos;
+ }
+
+ package.Save();
+ }
+
+ int savedCount = dirtyRowIndexes.Count;
+ dirtyRowIndexes.Clear();
+ AssetDatabase.Refresh();
+ EditorUtility.DisplayDialog("成功", $"已保存 {savedCount} 行修改到 {DECORATE_COST_EXCEL_NAME}。", "确定");
+ }
+ catch (Exception e)
+ {
+ EditorUtility.DisplayDialog("错误", $"保存失败:{e.Message}\n{e.StackTrace}", "确定");
+ }
+ }
+
+ private void ShowAreaInputDialog()
+ {
+ if (availableAreaIds == null || availableAreaIds.Count == 0)
+ {
+ return;
+ }
+
+ DecorateCostAreaInputWindow.ShowWindow(areaIdInput, TrySelectArea);
+ }
+
+ private bool TrySelectArea(int areaId)
+ {
+ return TrySelectArea(areaId, true);
+ }
+
+ private bool TrySelectArea(int areaId, bool showDialog)
+ {
+ if (areaId == 1)
+ {
+ if (showDialog)
+ {
+ EditorUtility.DisplayDialog("限制", "AreaId 1 为旧版特殊场景,不能在此工具中编辑。", "确定");
+ }
+ return false;
+ }
+
+ int expectedStepCount = GetExpectedStepCount(areaId);
+ if (expectedStepCount <= 0)
+ {
+ if (showDialog)
+ {
+ EditorUtility.DisplayDialog("错误", "请输入有效的 AreaId。AreaId=2-5 为 20 步,AreaId>=6 为 25 步。", "确定");
+ }
+ return false;
+ }
+
+ bool hasArea = allRows.Any(row => row.AreaId == areaId);
+ if (!hasArea)
+ {
+ if (showDialog)
+ {
+ EditorUtility.DisplayDialog("未找到", $"DecorateCost 中不存在 AreaId={areaId} 的配置。", "确定");
+ }
+ return false;
+ }
+
+ selectedAreaId = areaId;
+ areaIdInput = areaId;
+ currentAreaExpectedStepCount = expectedStepCount;
+ currentAreaStepRows = allRows
+ .Where(row => row.AreaId == areaId && row.SortId != 0)
+ .OrderBy(row => row.SortId)
+ .ToList();
+
+ if (showDialog)
+ {
+ string message = $"已加载 AreaId={areaId}\n期望步数:{currentAreaExpectedStepCount}\n可编辑步骤:{currentAreaStepRows.Count}";
+ List missingSortIds = GetMissingSortIds();
+ if (missingSortIds.Count > 0)
+ {
+ message += $"\n\n⚠ 缺失步骤:{string.Join(", ", missingSortIds)}";
+ }
+
+ EditorUtility.DisplayDialog("完成", message, "确定");
+ }
+
+ Repaint();
+ return true;
+ }
+
+ private List GetMissingSortIds()
+ {
+ if (currentAreaExpectedStepCount <= 0)
+ {
+ return new List();
+ }
+
+ HashSet existingIds = new HashSet(currentAreaStepRows.Select(row => row.SortId));
+ List missingIds = new List();
+ for (int sortId = 1; sortId <= currentAreaExpectedStepCount; sortId++)
+ {
+ if (!existingIds.Contains(sortId))
+ {
+ missingIds.Add(sortId);
+ }
+ }
+
+ return missingIds;
+ }
+
+ private int GetDefaultAreaId()
+ {
+ return availableAreaIds.FirstOrDefault();
+ }
+
+ private int GetExpectedStepCount(int areaId)
+ {
+ if (areaId >= 2 && areaId <= 5)
+ {
+ return 20;
+ }
+
+ if (areaId >= 6)
+ {
+ return 25;
+ }
+
+ return 0;
+ }
+
+ private void MarkRowDirty(DecorateCostExcelRow row)
+ {
+ dirtyRowIndexes.Add(row.RowIndex);
+ }
+
+ private static string GetBatchDisplayName(int batchValue)
+ {
+ if (batchValue <= 0)
+ {
+ return "空";
+ }
+
+ if (batchValue < BATCH_NAMES.Length)
+ {
+ return BATCH_NAMES[batchValue];
+ }
+
+ return $"第{batchValue}批";
+ }
+
+ private static int ParseBatchValue(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return 0;
+ }
+
+ string trimmedValue = value.Trim();
+ if (int.TryParse(trimmedValue, out int numericValue))
+ {
+ return Mathf.Max(0, numericValue);
+ }
+
+ for (int index = 1; index < BATCH_NAMES.Length; index++)
+ {
+ if (string.Equals(BATCH_NAMES[index], trimmedValue, StringComparison.OrdinalIgnoreCase))
+ {
+ return index;
+ }
+ }
+
+ string normalizedValue = trimmedValue.Replace("-", string.Empty).Replace(" ", string.Empty).ToLowerInvariant();
+ for (int index = 1; index < BATCH_NAMES.Length; index++)
+ {
+ string normalizedBatchName = BATCH_NAMES[index].Replace("-", string.Empty).Replace(" ", string.Empty).ToLowerInvariant();
+ if (normalizedBatchName == normalizedValue)
+ {
+ return index;
+ }
+ }
+
+ switch (normalizedValue)
+ {
+ case "fourth": return 4;
+ case "fifth": return 5;
+ case "sixth": return 6;
+ case "seventh": return 7;
+ case "eighth": return 8;
+ case "ninth": return 9;
+ case "tenth": return 10;
+ case "eleventh": return 11;
+ case "twelfth": return 12;
+ case "thirteenth": return 13;
+ case "fourteenth": return 14;
+ case "fifteenth": return 15;
+ case "sixteenth": return 16;
+ case "seventeenth": return 17;
+ case "eighteenth": return 18;
+ case "nineteenth": return 19;
+ case "twentieth": return 20;
+ case "twentyfirst": return 21;
+ case "twentysecond": return 22;
+ case "twentythird": return 23;
+ case "twentyfourth": return 24;
+ case "twentyfifth": return 25;
+ case "twentysixth": return 26;
+ case "twentyseventh": return 27;
+ case "twentyeighth": return 28;
+ case "twentyninth": return 29;
+ case "thirtieth": return 30;
+ case "thirtyfirst": return 31;
+ case "thirtysecond": return 32;
+ case "thirtythird": return 33;
+ case "thirtyfourth": return 34;
+ case "thirtyfifth": return 35;
+ case "thirtysixth": return 36;
+ case "thirtyseventh": return 37;
+ case "thirtyeighth": return 38;
+ case "thirtyninth": return 39;
+ case "fortieth": return 40;
+ case "fortyfirst": return 41;
+ default: return 0;
+ }
+ }
+
+ private static string FormatBatchValue(int batchValue)
+ {
+ if (batchValue <= 0)
+ {
+ return string.Empty;
+ }
+
+ if (batchValue < BATCH_NAMES.Length)
+ {
+ return BATCH_NAMES[batchValue];
+ }
+
+ return batchValue.ToString();
+ }
+
+ private sealed class DecorateCostExcelRow
+ {
+ private readonly string[] fields;
+
+ public DecorateCostExcelRow(int rowIndex, string[] fields)
+ {
+ RowIndex = rowIndex;
+ this.fields = fields ?? new string[TOTAL_COL_COUNT];
+ }
+
+ public int RowIndex { get; }
+
+ public int AreaId => ParseInt(fields[COL_AREA_ID - 1]);
+
+ public int SortId => ParseInt(fields[COL_SORT_ID - 1]);
+
+ public string Title => fields[4];
+
+ public int CostCount
+ {
+ get => ParseInt(fields[COL_COST_COUNT - 1]);
+ set => fields[COL_COST_COUNT - 1] = value.ToString();
+ }
+
+ public string Pos
+ {
+ get => fields[COL_POS - 1] ?? string.Empty;
+ set => fields[COL_POS - 1] = value ?? string.Empty;
+ }
+
+ public int BatchValue
+ {
+ get => ParseBatchValue(Pos);
+ set => Pos = FormatBatchValue(value);
+ }
+
+ private static int ParseInt(string value)
+ {
+ return int.TryParse(value, out int result) ? result : 0;
+ }
+ }
+ }
+
+ ///
+ /// AreaId 输入窗口
+ ///
+ public class DecorateCostAreaInputWindow : EditorWindow
+ {
+ private Func onConfirm;
+ private string areaIdText = string.Empty;
+
+ public static void ShowWindow(int defaultAreaId, Func confirmCallback)
+ {
+ DecorateCostAreaInputWindow window = CreateInstance();
+ window.titleContent = new GUIContent("输入 AreaId");
+ window.minSize = new Vector2(320, 120);
+ window.maxSize = new Vector2(320, 120);
+ window.areaIdText = defaultAreaId > 0 ? defaultAreaId.ToString() : string.Empty;
+ window.onConfirm = confirmCallback;
+ window.ShowUtility();
+ }
+
+ private void OnGUI()
+ {
+ EditorGUILayout.Space(10);
+ EditorGUILayout.LabelField("请输入要编辑的 AreaId", EditorStyles.boldLabel);
+ EditorGUILayout.HelpBox("AreaId=1 不允许编辑;AreaId=2-5 为 20 步;AreaId>=6 为 25 步。", MessageType.Info);
+
+ GUI.SetNextControlName("AreaIdField");
+ areaIdText = EditorGUILayout.TextField("AreaId", areaIdText);
+
+ EditorGUILayout.Space(10);
+ EditorGUILayout.BeginHorizontal();
+ GUILayout.FlexibleSpace();
+
+ if (GUILayout.Button("确定", GUILayout.Width(90), GUILayout.Height(24)))
+ {
+ Confirm();
+ }
+
+ if (GUILayout.Button("取消", GUILayout.Width(90), GUILayout.Height(24)))
+ {
+ Close();
+ }
+
+ EditorGUILayout.EndHorizontal();
+
+ if (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Return)
+ {
+ Confirm();
+ Event.current.Use();
+ }
+ }
+
+ private void OnFocus()
+ {
+ EditorGUI.FocusTextInControl("AreaIdField");
+ }
+
+ private void Confirm()
+ {
+ if (!int.TryParse(areaIdText, out int areaId))
+ {
+ EditorUtility.DisplayDialog("错误", "请输入有效的整数 AreaId。", "确定");
+ return;
+ }
+
+ if (onConfirm == null || onConfirm.Invoke(areaId))
+ {
+ Close();
+ }
+ }
+ }
+
+ ///
+ /// DecorateCost 独立的批量设置资源消耗弹窗。
+ ///
+ public class DecorateCostPlannerBatchCostWindow : EditorWindow
+ {
+ private int groupCount = 5;
+ private int stepCount = 25;
+ private IntegerField[] costFields = Array.Empty();
+ private Action onConfirm;
+
+ public static void ShowWindow(int stepCount, Action confirmCallback)
+ {
+ DecorateCostPlannerBatchCostWindow window = CreateInstance();
+ window.titleContent = new GUIContent("批量设置资源消耗");
+ window.minSize = new Vector2(300, 250);
+ window.stepCount = Mathf.Max(1, stepCount);
+ window.groupCount = Mathf.CeilToInt(window.stepCount / 5f);
+ window.costFields = new IntegerField[window.groupCount];
+ window.onConfirm = confirmCallback;
+ window.ShowUtility();
+ }
+
+ private void CreateGUI()
+ {
+ VisualElement root = rootVisualElement;
+ root.Clear();
+ root.style.paddingTop = 10;
+ root.style.paddingBottom = 10;
+ root.style.paddingLeft = 10;
+ root.style.paddingRight = 10;
+
+ Label title = new Label($"输入{groupCount}组资源消耗数值\n每5个步骤使用同一数值(当前共{stepCount}步)");
+ title.style.fontSize = 14;
+ title.style.marginBottom = 10;
+ title.style.unityTextAlign = TextAnchor.MiddleCenter;
+ root.Add(title);
+
+ for (int i = 0; i < groupCount; i++)
+ {
+ int startStep = i * 5 + 1;
+ int endStep = Mathf.Min((i + 1) * 5, stepCount);
+ IntegerField field = new IntegerField($"第{startStep}-{endStep}步:");
+ field.value = 0;
+ field.style.marginBottom = 5;
+ costFields[i] = field;
+ root.Add(field);
+ }
+
+ VisualElement buttonRow = new VisualElement();
+ buttonRow.style.flexDirection = FlexDirection.Row;
+ buttonRow.style.marginTop = 15;
+
+ UnityEngine.UIElements.Button confirmBtn = new UnityEngine.UIElements.Button(OnConfirmClicked) { text = "确定" };
+ confirmBtn.style.flexGrow = 1;
+ confirmBtn.style.marginRight = 5;
+ buttonRow.Add(confirmBtn);
+
+ UnityEngine.UIElements.Button cancelBtn = new UnityEngine.UIElements.Button(Close) { text = "取消" };
+ cancelBtn.style.flexGrow = 1;
+ buttonRow.Add(cancelBtn);
+
+ root.Add(buttonRow);
+ }
+
+ private void OnConfirmClicked()
+ {
+ int[] costs = new int[groupCount];
+ for (int i = 0; i < groupCount; i++)
+ {
+ costs[i] = costFields[i].value;
+ }
+
+ onConfirm?.Invoke(costs);
+ Close();
+ }
+ }
+
+ ///
+ /// DecorateCost 独立的批量设置步骤批次弹窗。
+ ///
+ public class DecorateCostPlannerBatchSkipWindow : EditorWindow
+ {
+ private int stepCount = 25;
+ private IntegerField[] skipFields = Array.Empty();
+ private ScrollView scrollView;
+ private Action onConfirm;
+
+ public static void ShowWindow(int stepCount, Action confirmCallback)
+ {
+ DecorateCostPlannerBatchSkipWindow window = CreateInstance();
+ window.titleContent = new GUIContent("批量设置批次");
+ window.minSize = new Vector2(350, 500);
+ window.stepCount = Mathf.Max(1, stepCount);
+ window.skipFields = new IntegerField[window.stepCount];
+ window.onConfirm = confirmCallback;
+ window.ShowUtility();
+ }
+
+ private void CreateGUI()
+ {
+ VisualElement root = rootVisualElement;
+ root.Clear();
+ root.style.paddingTop = 10;
+ root.style.paddingBottom = 10;
+ root.style.paddingLeft = 10;
+ root.style.paddingRight = 10;
+
+ Label title = new Label($"输入{stepCount}个数字设置批次\n0=空 1=first 2=second 3=third...");
+ title.style.fontSize = 14;
+ title.style.marginBottom = 10;
+ title.style.unityTextAlign = TextAnchor.MiddleCenter;
+ root.Add(title);
+
+ scrollView = new ScrollView();
+ scrollView.style.flexGrow = 1;
+
+ for (int i = 0; i < stepCount; i++)
+ {
+ IntegerField field = new IntegerField($"步骤{i + 1}:");
+ field.value = 0;
+ field.style.marginBottom = 3;
+ skipFields[i] = field;
+ scrollView.Add(field);
+ }
+
+ root.Add(scrollView);
+
+ VisualElement buttonRow = new VisualElement();
+ buttonRow.style.flexDirection = FlexDirection.Row;
+ buttonRow.style.marginTop = 10;
+
+ UnityEngine.UIElements.Button confirmBtn = new UnityEngine.UIElements.Button(OnConfirmClicked) { text = "确定" };
+ confirmBtn.style.flexGrow = 1;
+ confirmBtn.style.marginRight = 5;
+ buttonRow.Add(confirmBtn);
+
+ UnityEngine.UIElements.Button cancelBtn = new UnityEngine.UIElements.Button(Close) { text = "取消" };
+ cancelBtn.style.flexGrow = 1;
+ buttonRow.Add(cancelBtn);
+
+ root.Add(buttonRow);
+ }
+
+ private void OnConfirmClicked()
+ {
+ int[] skips = new int[stepCount];
+ for (int i = 0; i < stepCount; i++)
+ {
+ skips[i] = skipFields[i].value;
+ }
+
+ onConfirm?.Invoke(skips);
+ Close();
+ }
+ }
+}
diff --git a/Scripts/Editor/Design_Tools/Scene/DecorateCostPlannerEditor.cs.meta b/Scripts/Editor/Design_Tools/Scene/DecorateCostPlannerEditor.cs.meta
new file mode 100644
index 0000000..ae3430f
--- /dev/null
+++ b/Scripts/Editor/Design_Tools/Scene/DecorateCostPlannerEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a80667981aa125e489cabc41b4db38c0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: