工具修改

This commit is contained in:
zhang hongbo 2026-02-04 10:34:08 +08:00
parent 811527ae24
commit 74449e09a2
13 changed files with 1 additions and 5986 deletions

@ -1 +1 @@
Subproject commit e1f7ea568b27493475d39eeef68fb4407d43928f
Subproject commit ab2ea01d2aede712e859db9c794e14f284f3a275

View File

@ -1,273 +0,0 @@
# DecorateConfigEditor 修改清单
## 版本更新日期2025-12-26
---
## 📋 功能限制
### 1. AreaId 限制
- **限制内容**:禁止编辑 AreaId = 1 或 2 的配置
- **实现位置**
- `OnLoadConfigClicked()` - 读取配置时检查
- `OnSaveConfigClicked()` - 保存配置时检查
- `CreateNewAreaConfig()` - 创建新场景时检查
- **提示信息**"AreaId 1 和 2 包含特殊处理,不支持通过此工具编辑。如需修改请联系编辑器作者。"
- **原因**AreaId 1和2包含特殊处理逻辑避免误操作
### 2. 步骤数量限制
- **限制内容**:步骤数量固定为 25不允许修改
- **实现位置**
- `BindUIControls()` - UI加载时禁用步骤数量输入框
- `OnLoadConfigClicked()` - 强制设置为25
- `CreateNewAreaConfig()` - 新场景默认25步
- UXML界面 - 添加提示文字:"不可修改,如需调整请联系编辑器作者"
- **UI表现**StepCountField 输入框禁用(灰色不可编辑)
### 3. 不存在的AreaId处理
- **处理逻辑**
- 读取配置时如果指定的AreaId不存在弹出提示框
- 提示信息:"未找到AreaId=X的配置。如需创建新场景请点击'创建新场景'按钮。"
- **不进行任何自动创建**,只提示用户
- **实现位置**`OnLoadConfigClicked()`
### 4. 创建新场景流程
- **新增按钮**"创建新场景"按钮(在配置管理区域)
- **操作流程**
1. 点击"创建新场景"按钮
2. 弹出输入对话框要求输入AreaId
3. 验证AreaId不能是1或2不能是已存在的
4. 创建25个默认步骤配置
5. 自动记录bg初始状态如果bg节点已设置
6. 允许用户进行编辑
7. **此时配置未保存到文件**,需要手动点击"保存配置"
- **新增类**`InputDialogWindow` - 用于输入AreaId的弹窗
---
## 🎯 Spine相关特殊处理
### 1. UI中隐藏Spine操作
- **实现位置**`ParseAndCreateActionGroups()`
- **处理逻辑**
```csharp
// 跳过所有spine相关操作用户不可见不可编辑
if (actionType.Contains("spine")) continue;
```
- **支持的spine类型**
- `init_spine` - 初始化spine
- `replace_spine` - 替换spine
- 任何包含"spine"关键字的操作
### 2. 保存时保留Spine配置
- **实现位置**`UpdateRowActionString()`
- **处理逻辑**
1. 从原始Action字符串中提取所有spine操作
2. 将spine操作放在新Action字符串的**最前面**
3. 后面跟用户编辑的其他操作
- **代码示例**
```csharp
// 保存原有的spine操作放在最前面
List<string> spineActions = new List<string>();
if (!string.IsNullOrEmpty(row.Action))
{
string[] oldActions = row.Action.Split('@');
foreach (string oldAction in oldActions)
{
if (oldAction.Contains("spine"))
{
spineActions.Add(oldAction);
}
}
}
// 先添加spine操作
actionStrings.AddRange(spineActions);
```
### 3. 场景还原时跳过Spine
- **实现位置**
- `ApplyInitAction()` - 跳过 init_spine
- `ApplyStepAction()` - 跳过所有spine操作
- **效果**用户在预览场景时spine相关操作不会执行但配置保持完整
---
## ✨ 新增功能
### 1. reset_pos 操作支持
- **操作格式**`reset_pos#节点路径,x=y`
- **示例**`reset_pos#bg/clearObj/my_sg_2,-156=-838`
- **功能**重置指定GameObject的位置RectTransform.anchoredPosition
#### UI实现
- **类型**固定2个字段
- ObjectField选择目标GameObject
- TextField输入位置坐标格式x=y-156=-838
- **位置**与clear、add、replace、replace_image并列
- **添加按钮**"+ ResetPos"
#### 代码实现:
1. **CreateActionGroup()** - 创建reset_pos UI组
```csharp
else if (actionType == "reset_pos")
{
ObjectField objField = new ObjectField("目标节点:");
TextField posField = new TextField("位置(x=y):");
}
```
2. **ParseAndCreateActionGroups()** - 解析现有reset_pos配置
```csharp
else if (actionType == "reset_pos")
{
string[] pathParts = paths.Split(',');
// 解析节点和位置
}
```
3. **UpdateRowActionString()** - 生成reset_pos字符串
```csharp
else if (actionType == "reset_pos")
{
actionStrings.Add($"reset_pos#{targetPath},{position}");
}
```
4. **ApplyResetPosAction()** - 应用reset_pos操作
```csharp
private void ApplyResetPosAction(string paths)
{
// 解析x=y格式
string[] posParts = positionStr.Split('=');
rt.anchoredPosition = new Vector2(x, y);
}
```
### 2. 只读Action字符串显示
- **位置**:每个步骤编辑区域的顶部
- **显示内容**当前步骤的完整Action配置字符串
- **特性**
- 只读(不可编辑)
- 灰色显示opacity: 0.7
- 多行显示multiline
- 实时更新:当用户编辑操作组时,自动更新显示
- **UI元素名称**`ActionDisplay`
- **更新位置**`UpdateRowActionString()` 末尾
---
## 🔧 代码结构优化
### 新增文件
无新增文件,所有修改在现有文件中
### 新增类
1. **InputDialogWindow**EditorWindow
- 用途创建新场景时输入AreaId
- 包含字段:
- `message` - 提示信息
- `inputValue` - 用户输入的值
- `confirmed` - 是否确认
- 方法:`OnGUI()` - 绘制输入界面
### 新增方法
1. **OnCreateNewSceneClicked()** - 处理"创建新场景"按钮点击
2. **ShowInputDialog()** - 显示输入对话框
3. **ApplyResetPosAction()** - 应用reset_pos操作
### 修改的方法
1. **BindUIControls()** - 禁用步骤数量输入框
2. **OnLoadConfigClicked()** - 添加AreaId和配置存在检查
3. **OnSaveConfigClicked()** - 添加AreaId限制检查
4. **CreateNewAreaConfig()** - 改为弹窗输入AreaId添加验证
5. **ParseAndCreateActionGroups()** - 跳过spine支持reset_pos
6. **CreateActionGroup()** - 支持reset_pos类型
7. **UpdateRowActionString()** - 保留spine操作支持reset_pos
8. **ApplyStepAction()** - 跳过所有spine操作
9. **ApplyInitAction()** - 跳过init_spine
10. **CreateStepEditItem()** - 添加只读Action显示
---
## 📝 配置文件格式说明
### Action字符串格式
```
操作1@操作2@操作3...
```
### 支持的操作类型
| 操作类型 | 格式 | 示例 | UI编辑 |
|---------|------|------|--------|
| clear | `clear#路径1,路径2,...` | `clear#bg/clearObj/3,bg/clearObj/4` | ✅ 可编辑 |
| add | `add#路径1,路径2,...` | `add#bg/addObj/5,bg/addObj/6` | ✅ 可编辑 |
| replace | `replace#旧路径,新路径` | `replace#bg/clearObj/1,bg/addObj/1` | ✅ 可编辑 |
| replace_image | `replace_image#节点路径,图片路径` | `replace_image#bg,BG/s3_bg_2` | ✅ 可编辑 |
| reset_pos | `reset_pos#节点路径,x=y` | `reset_pos#bg/clearObj/2,-156=-838` | ✅ 可编辑 |
| init_spine | `init_spine#...` | - | ❌ 自动保留 |
| replace_spine | `replace_spine#...` | - | ❌ 自动保留 |
### Spine操作处理策略
- **读取时**spine操作不显示在UI中
- **编辑时**用户无法看到和修改spine操作
- **保存时**spine操作放在Action字符串的最前面原样保留
- **预览时**spine操作不执行避免spine加载问题
---
## ⚠️ 注意事项
1. **AreaId限制**
- AreaId 1和2不可通过编辑器修改
- 如需修改,需要直接编辑配置文件或联系编辑器作者
2. **步骤数量固定**
- 所有场景固定25步
- 如需修改步骤数量,需要修改编辑器代码
3. **Spine无感知**
- 用户在UI中完全看不到spine相关配置
- 但spine配置不会丢失保存时会自动保留
4. **新场景创建**
- 创建新场景后,配置仅在内存中,需手动保存
- 保存前可以预览场景效果
5. **reset_pos坐标格式**
- 必须使用 `x=y` 格式(使用等号=分隔)
- 示例:`-156=-838` 或 `100=200`
- 不支持其他分隔符(如逗号或冒号)
6. **配置完整性**
- 只读Action显示帮助验证配置正确性
- 建议每次编辑后检查只读显示的内容
---
## 🐛 已知问题
---
## 📞 联系方式
如需对编辑器进行调整或遇到问题,请联系编辑器作者(你)。
---
## 📊 测试检查清单
- [ ] 尝试编辑AreaId=1验证拒绝
- [ ] 尝试编辑AreaId=2验证拒绝
- [ ] 尝试修改步骤数量,验证输入框禁用
- [ ] 读取不存在的AreaId验证提示正确
- [ ] 创建新场景输入AreaId=1验证拒绝
- [ ] 创建新场景输入AreaId=3验证成功
- [ ] 创建新场景输入已存在的AreaId验证拒绝
- [ ] 编辑包含spine的配置验证spine不显示在UI
- [ ] 保存包含spine的配置验证spine保留在最前面
- [ ] 添加reset_pos操作验证可正常添加和编辑
- [ ] 预览包含reset_pos的步骤验证位置正确设置
- [ ] 编辑操作后验证只读Action显示实时更新
- [ ] 新建场景不保存直接关闭,验证配置文件未改变

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 0c1336f834fb30c45986701ba8b43f3e
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 7ab35bf45404d564cb87c85fac02577f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,139 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<engine:UXML
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:engine="UnityEngine.UIElements"
xmlns:editor="UnityEditor.UIElements"
xsi:noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd">
<engine:VisualElement style="flex-grow: 1; padding: 10px;">
<!-- 状态栏 -->
<engine:VisualElement name="StatusBar" style="margin-bottom: 10px; padding: 8px; background-color: rgb(35, 35, 35); border-radius: 5px;">
<engine:Label name="StatusLabel" text="⚪ 待保存状态:无修改" style="font-size: 13px; color: rgb(100, 150, 255); -unity-text-align: middle-left;" />
</engine:VisualElement>
<!-- 顶部配置区域 -->
<engine:VisualElement name="ConfigArea" style="margin-bottom: 10px; padding: 10px; border-width: 2px; border-color: rgb(50, 50, 50); border-radius: 5px;">
<!-- 标题行:配置管理 + 核心操作按钮 + 常用工具 + 折叠按钮 -->
<engine:VisualElement style="flex-direction: row; align-items: center; margin-bottom: 10px;">
<engine:Label text="配置管理" style="font-size: 16px; -unity-font-style: bold; width: 100px;" />
<engine:Button name="LoadConfigButton" text="读取配置" style="width: 90px; height: 28px; margin-right: 5px; background-color: rgb(60, 120, 180);" />
<engine:Button name="SaveConfigButton" text="保存配置" style="width: 90px; height: 28px; margin-right: 5px; background-color: rgb(80, 140, 80);" />
<engine:Button name="RecordInitButton" text="记录初始状态" style="width: 110px; height: 28px; margin-right: 15px; background-color: rgb(180, 100, 60);" />
<!-- 常用工具(高频按钮) -->
<engine:Label text="常用工具:" style="font-size: 12px; margin-right: 5px; color: rgb(150, 200, 255);" />
<engine:Button name="SortNamesButtonTop" text="名称排序" style="width: 85px; height: 28px; margin-right: 5px; background-color: rgb(100, 80, 140);" />
<engine:Button name="ScaleImageButtonTop" text="图片X1.5" style="width: 80px; height: 28px; margin-right: 10px; background-color: rgb(100, 80, 140);" />
<engine:VisualElement style="flex-grow: 1;" />
<engine:Button name="ToggleConfigButton" text="▲ 折叠" style="width: 80px; height: 25px;" />
</engine:VisualElement>
<!-- 可折叠内容区域 -->
<engine:VisualElement name="ConfigContent">
<!-- 基础配置 -->
<engine:VisualElement style="margin-bottom: 10px; padding: 8px; background-color: rgba(0, 0, 0, 0.2); border-radius: 3px;">
<engine:VisualElement style="flex-direction: row; margin-bottom: 5px;">
<engine:Label text="配置文件路径:" style="width: 120px; -unity-text-align: middle-left;" />
<engine:TextField name="ConfigPathField" style="flex-grow: 1; margin-right: 5px;" />
<engine:Button name="BrowseConfigButton" text="浏览..." style="width: 80px;" />
</engine:VisualElement>
<engine:VisualElement style="flex-direction: row; margin-bottom: 5px;">
<engine:Label text="场景AreaId:" style="width: 120px; -unity-text-align: middle-left;" />
<engine:IntegerField name="AreaIdField" value="1" style="width: 100px;" />
<engine:Label text="步骤数量(固定):" style="width: 130px; margin-left: 20px; -unity-text-align: middle-left;" />
<engine:IntegerField name="StepCountField" value="25" style="width: 100px;" />
<engine:Label text="不可修改,如需调整请联系编辑器作者" style="margin-left: 10px; -unity-text-align: middle-left; font-size: 10px; opacity: 0.7;" />
</engine:VisualElement>
<engine:VisualElement style="flex-direction: row;">
<engine:Label text="场景bg节点:" style="width: 120px; -unity-text-align: middle-left;" />
<editor:ObjectField name="BgObjectField" type="UnityEngine.Transform, UnityEngine.CoreModule" style="flex-grow: 1; margin-right: 5px;" />
<engine:Button name="CheckBgButton" text="检测bg格式" style="width: 100px;" />
</engine:VisualElement>
</engine:VisualElement>
<!-- 批量设置 -->
<engine:Label text="批量设置" style="font-size: 13px; -unity-font-style: bold; margin-bottom: 5px; color: rgb(150, 200, 255);" />
<engine:VisualElement style="flex-direction: row; margin-bottom: 5px;">
<engine:Button name="AutoSetTitleButton" text="自动设置标题" style="flex-grow: 1; margin-right: 5px; height: 28px;" />
<engine:Button name="AutoSetIconButton" text="自动设置Icon" style="flex-grow: 1; margin-right: 5px; height: 28px;" />
<engine:Button name="BatchSetIconPosButton" text="批量Icon位置" style="flex-grow: 1; margin-right: 5px; height: 28px;" />
<engine:Button name="AutoSetBuildButton" text="自动建造效果" style="flex-grow: 1; margin-right: 5px; height: 28px;" />
<engine:Button name="AutoSetShineButton" text="全部闪光" style="flex-grow: 1; height: 28px;" />
</engine:VisualElement>
<engine:VisualElement style="flex-direction: row;">
<engine:Button name="BatchSetCostButton" text="批量资源消耗" style="flex-grow: 1; margin-right: 5px; height: 28px;" />
<engine:Button name="BatchSetSkipButton" text="批量设置批次" style="flex-grow: 1; margin-right: 5px; height: 28px;" />
<engine:Button name="ClearSceneButton" text="清空场景" style="flex-grow: 1; margin-right: 5px; height: 28px; background-color: rgb(140, 60, 60);" />
<engine:VisualElement style="flex-grow: 2;" />
</engine:VisualElement>
</engine:VisualElement>
</engine:VisualElement>
<!-- 初始状态编辑区域 -->
<engine:VisualElement style="margin-bottom: 10px; padding: 10px; border-width: 2px; border-color: rgb(50, 50, 50); border-radius: 5px;">
<!-- 标题行带折叠按钮 -->
<engine:VisualElement style="flex-direction: row; align-items: center; margin-bottom: 10px;">
<engine:Label text="初始状态编辑" style="font-size: 14px; -unity-font-style: bold; flex-grow: 1;" />
<engine:Button name="ToggleInitEditButton" text="▲ 折叠" style="width: 80px; height: 25px;" />
</engine:VisualElement>
<!-- 可折叠内容 -->
<engine:VisualElement name="InitEditContent">
<engine:Label text="点击【记录初始状态】后,可在此处编辑各对象的初始属性" style="font-size: 11px; color: rgb(150, 150, 150); margin-bottom: 8px;" />
<engine:ScrollView style="max-height: 300px; border-width: 1px; border-color: rgb(60, 60, 60); border-radius: 3px; background-color: rgba(0, 0, 0, 0.2);">
<engine:VisualElement name="InitEditContainer" style="padding: 5px;">
<engine:Label name="InitEmptyLabel" text="暂无初始状态数据" style="margin: 20px; -unity-text-align: middle-center; color: rgb(128, 128, 128);" />
</engine:VisualElement>
</engine:ScrollView>
</engine:VisualElement>
</engine:VisualElement>
<!-- 步骤操作和编辑区域(双栏布局)-->
<engine:VisualElement style="flex-direction: row; flex-grow: 1; gap: 10px;">
<!-- 左侧:步骤快速选择面板 -->
<engine:VisualElement style="width: 220px; border-width: 2px; border-color: rgb(50, 50, 50); border-radius: 5px; padding: 10px;">
<engine:Label text="步骤导航" style="font-size: 14px; -unity-font-style: bold; margin-bottom: 8px;" />
<!-- 快速跳转 -->
<engine:VisualElement style="flex-direction: row; margin-bottom: 8px; align-items: center;">
<engine:Label text="当前:" style="width: 35px; font-size: 11px;" />
<engine:IntegerField name="CurrentStepField" value="0" style="width: 50px; margin-right: 3px;" />
<engine:Button name="GotoStepButton" text="GO" style="width: 35px; height: 22px; font-size: 11px;" />
</engine:VisualElement>
<!-- 导航按钮组 -->
<engine:VisualElement style="flex-direction: row; gap: 3px; margin-bottom: 10px;">
<engine:Button name="PrevStepButton" text="◀上步" style="flex-grow: 1; height: 24px; font-size: 11px;" />
<engine:Button name="NextStepButton" text="下步▶" style="flex-grow: 1; height: 24px; font-size: 11px;" />
</engine:VisualElement>
<!-- 步骤网格4列布局-->
<engine:ScrollView style="flex-grow: 1; border-width: 1px; border-color: rgb(60, 60, 60); border-radius: 3px; background-color: rgba(0, 0, 0, 0.3);">
<engine:VisualElement name="StepGridContainer" style="padding: 5px; flex-wrap: wrap; flex-direction: row;">
<!-- 这里会动态添加25个步骤按钮每个按钮40x38像素4列布局 -->
</engine:VisualElement>
</engine:ScrollView>
<!-- 预览控制 -->
<engine:VisualElement style="margin-top: 8px; padding-top: 8px; border-top-width: 1px; border-color: rgb(60, 60, 60);">
<engine:Label name="StepInfoLabel" text="初始状态" style="margin-bottom: 5px; -unity-text-align: middle-center; font-size: 11px; color: rgb(180, 180, 180);" />
<engine:Button name="ShowDecorateButton" text="显示装饰按钮" style="width: 100%; height: 24px; margin-bottom: 3px; font-size: 11px;" />
<engine:Button name="HideDecorateButton" text="隐藏装饰按钮" style="width: 100%; height: 24px; font-size: 11px;" />
</engine:VisualElement>
</engine:VisualElement>
<!-- 右侧:步骤编辑区域 -->
<engine:ScrollView name="StepEditScrollView" style="flex-grow: 1; border-width: 2px; border-color: rgb(50, 50, 50); border-radius: 5px;">
<engine:VisualElement name="StepEditContainer" style="padding: 10px;">
<engine:Label text="步骤编辑" style="font-size: 14px; -unity-font-style: bold; margin-bottom: 10px;" />
<!-- 这里会动态添加步骤编辑项 -->
<engine:Label name="EmptyLabel" text="请先读取或创建配置" style="margin: 20px; -unity-text-align: middle-center; color: rgb(128, 128, 128);" />
</engine:VisualElement>
</engine:ScrollView>
</engine:VisualElement>
</engine:VisualElement>
</engine:UXML>

View File

@ -1,10 +0,0 @@
fileFormatVersion: 2
guid: 18e43c7ee1d33164697d9fbb1076fe3d
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@ -1,234 +0,0 @@
using UnityEditor;
using UnityEngine;
using System.IO;
using OfficeOpenXml;
/// <summary>
/// DecorateCost表格转换工具
/// 用于将Icon列的完整路径转换为只保留文件名
/// 例如: Icon/first/s1_icon_renwu_5 -> s1_icon_renwu_5
/// </summary>
public class DecorateCostTableConverter : EditorWindow
{
private string excelPath = "E:\\WorkSpace\\Docs\\config\\DecorateCost.xlsx";
private const string SHEET_NAME = "DecorateCost";
private Vector2 scrollPosition;
private string logMessage = "";
[MenuItem("蹊径/转换DecorateCost表格")]
public static void ShowWindow()
{
var window = GetWindow<DecorateCostTableConverter>("DecorateCost表格转换");
window.minSize = new Vector2(500, 400);
}
private void OnGUI()
{
GUILayout.Label("DecorateCost表格Icon路径转换工具", EditorStyles.boldLabel);
GUILayout.Space(10);
EditorGUILayout.HelpBox(
"此工具会将Icon列的完整路径转换为只保留文件名。\n" +
"例如: Icon/first/s1_icon_renwu_5 → s1_icon_renwu_5\n\n" +
"转换规则:\n" +
"• 如果Icon路径包含'/',则只保留最后一部分\n" +
"• 如果Icon路径为空或不包含'/',则保持不变\n" +
"• 如果Action列包含'init_spine',则跳过该行(不转换)\n" +
"• 会自动备份原文件(添加.backup扩展名",
MessageType.Info);
GUILayout.Space(10);
// Excel文件路径
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Excel路径:", GUILayout.Width(80));
excelPath = EditorGUILayout.TextField(excelPath);
if (GUILayout.Button("浏览", GUILayout.Width(60)))
{
string path = EditorUtility.OpenFilePanel("选择DecorateCost.xlsx", "", "xlsx");
if (!string.IsNullOrEmpty(path))
{
excelPath = path;
}
}
EditorGUILayout.EndHorizontal();
GUILayout.Space(10);
// 转换按钮
if (GUILayout.Button("开始转换", GUILayout.Height(30)))
{
ConvertTable();
}
GUILayout.Space(10);
// 日志显示区域
if (!string.IsNullOrEmpty(logMessage))
{
EditorGUILayout.LabelField("转换日志:", EditorStyles.boldLabel);
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.Height(200));
EditorGUILayout.TextArea(logMessage, GUILayout.ExpandHeight(true));
EditorGUILayout.EndScrollView();
}
}
private void ConvertTable()
{
logMessage = "";
// 检查文件是否存在
if (!File.Exists(excelPath))
{
logMessage = $"❌ 错误: 文件不存在\n路径: {excelPath}";
Debug.LogError(logMessage);
return;
}
try
{
// 设置EPPlus许可证
// 备份原文件
string backupPath = excelPath + ".backup";
File.Copy(excelPath, backupPath, true);
logMessage += $"✓ 已备份原文件到: {backupPath}\n\n";
// 读取Excel
using (var package = new ExcelPackage(new FileInfo(excelPath)))
{
var worksheet = package.Workbook.Worksheets[SHEET_NAME];
if (worksheet == null)
{
logMessage += $"❌ 错误: 找不到工作表 '{SHEET_NAME}'\n";
Debug.LogError(logMessage);
return;
}
// 查找Icon列的索引假设第一行是标题行
int iconColIndex = -1;
int sortIdColIndex = -1;
int actionColIndex = -1;
int totalCols = worksheet.Dimension.End.Column;
for (int col = 1; col <= totalCols; col++)
{
var cellValue = worksheet.Cells[1, col].Text.Trim();
if (cellValue == "Icon" || cellValue == "图标")
{
iconColIndex = col;
}
else if (cellValue == "SortId" || cellValue == "排序Id")
{
sortIdColIndex = col;
}
else if (cellValue == "Action" || cellValue == "行为")
{
actionColIndex = col;
}
}
if (iconColIndex == -1)
{
logMessage += "❌ 错误: 找不到Icon列\n";
Debug.LogError(logMessage);
return;
}
logMessage += $"✓ 找到Icon列: 第{iconColIndex}列\n";
if (sortIdColIndex != -1)
{
logMessage += $"✓ 找到SortId列: 第{sortIdColIndex}列\n";
}
if (actionColIndex != -1)
{
logMessage += $"✓ 找到Action列: 第{actionColIndex}列\n";
}
logMessage += "\n开始转换...\n\n";
int totalRows = worksheet.Dimension.End.Row;
int convertedCount = 0;
int skippedCount = 0;
int emptyCount = 0;
int spineSkippedCount = 0;
// 从第3行开始处理第1行是英文标题第2行是中文标题
for (int row = 3; row <= totalRows; row++)
{
var iconCell = worksheet.Cells[row, iconColIndex];
string originalIcon = iconCell.Text.Trim();
if (string.IsNullOrEmpty(originalIcon))
{
emptyCount++;
continue;
}
// 检查Action列是否包含init_spine
bool hasInitSpine = false;
if (actionColIndex != -1)
{
var actionCell = worksheet.Cells[row, actionColIndex];
string actionValue = actionCell.Text.Trim();
if (!string.IsNullOrEmpty(actionValue) && actionValue.Contains("init_spine"))
{
hasInitSpine = true;
}
}
// 如果包含init_spine跳过这一行
if (hasInitSpine)
{
spineSkippedCount++;
logMessage += $"第{row}行: 跳过(包含init_spine): {originalIcon}\n";
continue;
}
// 如果包含'/',则只保留最后一部分
if (originalIcon.Contains("/"))
{
string newIcon = originalIcon.Substring(originalIcon.LastIndexOf('/') + 1);
iconCell.Value = newIcon;
string sortInfo = "";
if (sortIdColIndex != -1)
{
var sortIdCell = worksheet.Cells[row, sortIdColIndex];
sortInfo = $" [SortId={sortIdCell.Text}]";
}
logMessage += $"第{row}行{sortInfo}: {originalIcon} → {newIcon}\n";
convertedCount++;
}
else
{
skippedCount++;
}
}
// 保存修改
package.Save();
logMessage += $"\n转换完成\n";
logMessage += $"━━━━━━━━━━━━━━━━━━━━━━\n";
logMessage += $"✓ 已转换: {convertedCount} 条\n";
logMessage += $"• 跳过(无需转换): {skippedCount} 条\n";
logMessage += $"• 跳过(init_spine): {spineSkippedCount} 条\n";
logMessage += $"• 空值: {emptyCount} 条\n";
logMessage += $"━━━━━━━━━━━━━━━━━━━━━━\n";
logMessage += $"✓ 文件已保存: {excelPath}\n";
Debug.Log("DecorateCost表格转换完成");
EditorUtility.DisplayDialog("转换完成",
$"Icon路径转换完成\n\n已转换: {convertedCount} 条\n跳过(无需转换): {skippedCount} 条\n跳过(init_spine): {spineSkippedCount} 条\n空值: {emptyCount} 条\n\n原文件已备份",
"确定");
}
}
catch (System.Exception ex)
{
logMessage += $"\n❌ 转换失败:\n{ex.Message}\n\n{ex.StackTrace}";
Debug.LogError($"转换失败: {ex.Message}");
EditorUtility.DisplayDialog("转换失败", $"转换过程中出现错误:\n\n{ex.Message}", "确定");
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 26d566349ea253e4ca0d63d21018f9e5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,96 +0,0 @@
# DecorateConfigEditor 快速参考
## 🚫 操作限制
### AreaId限制
- ❌ **不能编辑** AreaId = 1 或 2
- ✅ **可以编辑** AreaId ≥ 3 的所有场景
- 原因1和2有特殊处理逻辑
### 步骤数量
- ⚠️ **固定25步**,无法修改
- 如需调整,请联系编辑器作者
---
## 📋 操作类型速查
| 操作 | 用途 | UI字段数 | 字段类型 |
|-----|------|---------|---------|
| **clear** | 隐藏对象 | 动态(+/-) | ObjectField |
| **add** | 显示对象 | 动态(+/-) | ObjectField |
| **replace** | 替换对象 | 固定2个 | ObjectField × 2 |
| **replace_image** | 换图片 | 固定2个 | ObjectField + TextField |
| **reset_pos** | 重置位置 | 固定2个 | ObjectField + TextField(x=y) |
| **spine** | Spine相关 | 不显示 | 自动保留 |
---
## 🔄 常用工作流程
### 创建新场景
1. 点击"创建新场景"
2. 输入AreaId≥3且不存在
3. 编辑步骤配置
4. 点击"保存配置"
### 编辑现有场景
1. 设置AreaId≥3
2. 点击"读取配置"
3. 在下方编辑步骤
4. 点击"保存配置"
### 预览效果
1. 读取配置后
2. 使用"上一步/下一步"按钮
3. 或输入步骤号点击"跳转"
---
## 💡 快速提示
- **只读显示**每个步骤顶部的灰色框显示完整Action字符串
- **Spine操作**:完全透明,你看不到但它们会被保留
- **拖放编辑**从Hierarchy拖GameObject到ObjectField
- **位置格式**reset_pos用 `x=y` 格式,如 `-156=-838`
- **配置保护**:新建场景不会立即保存,可以先预览
---
## ⚡ 键盘快捷操作
- 使用 **Tab** 在字段间切换
- **+/-** 按钮添加/删除ObjectField
- **X** 按钮删除整个操作组
---
## 🎯 示例配置
```
clear#bg/clearObj/3,bg/clearObj/4@add#bg/addObj/5@replace#bg/clearObj/1,bg/addObj/1@replace_image#bg,BG/s3_bg_2@reset_pos#bg/clearObj/2,-156=-838
```
解读:
1. 隐藏 clearObj/3 和 clearObj/4
2. 显示 addObj/5
3. 用 addObj/1 替换 clearObj/1
4. bg换图片为 s3_bg_2
5. clearObj/2 移动到 (-156, -838)
---
## ❗ 常见错误
| 错误 | 原因 | 解决方法 |
|-----|------|---------|
| "AreaId 1和2不支持" | 选择了受限AreaId | 使用AreaId ≥ 3 |
| "配置不存在" | AreaId未创建 | 点击"创建新场景" |
| "找不到路径" | GameObject路径错误 | 检查Hierarchy结构 |
| reset_pos不生效 | 格式错误 | 使用 x=y 格式(=号) |
---
## 📞 需要帮助?
如需修改编辑器功能或遇到特殊需求,请联系编辑器作者。

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: bd5a55ccdd0a4fc4d854b7e0ec9bbbe7
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,236 +0,0 @@
# DecorateConfigEditor SO集成升级说明
## 📋 概述
已将 `DecorateConfigEditor` 从直接加载图片路径升级为使用 ScriptableObject (SO) 资源系统。
## 🎯 主要改动
### 1. **资源加载方式变更**
#### 旧方式(已废弃)
```
Icon列Icon/first/s1_icon_renwu_5
加载:从 Assets/GameMain/UI/UISprites/Decorate/ 路径直接加载
```
#### 新方式(当前)
```
Icon列s1_icon_renwu_5
加载:从 ArtTableSO 中根据 Name 查找对应的 Sprite
```
### 2. **SO资源分类**
#### DecorateScene场景资源
- **路径**: `Assets/Art_SubModule/Art_SO/DecorateScene/Scene{AreaId}Resource.asset`
- **用途**: 存储 `SortId=0` 的初始化场景物体图片
- **命名规则**: Scene1Resource, Scene2Resource, ..., Scene50Resource
- **示例**: AreaId=1 → 从 Scene1Resource.asset 中查找
#### DecorateIcon图标资源
- **路径**: `Assets/Art_SubModule/Art_SO/DecorateIcon/DecorateIconResource.asset`
- **用途**: 存储 `SortId>0` 的步骤装饰图标
- **所有步骤图标**: 统一存放在 DecorateIconResource.asset
### 3. **核心功能修改**
#### ✅ 已修改的功能
1. **表格路径转换工具**
- 新增:`蹊径 > 转换DecorateCost表格`
- 功能将Icon列完整路径转换为文件名
- 自动备份原文件
2. **配置保存验证**
- 保存前验证Icon名称是否存在于对应SO中
- 显示详细的验证错误信息
- 支持强制保存(不推荐)
3. **图片预览加载**
- 步骤编辑UI的Icon预览
- 初始状态编辑UI的Icon预览
- 装饰按钮预览
4. **自动设置Icon**
- 从SO中查找匹配的Icon名称
- 支持批量设置
5. **场景物体Action执行**
- `init_img`: 从SO加载Sprite
- `replace_image`: 从SO加载替换图片
6. **拖入Sprite处理**
- 自动从SO中查找对应Name
- 兼容旧路径格式(自动转换)
## 🚀 使用流程
### 步骤1: 转换现有表格
1. 打开Unity编辑器
2. 菜单栏选择:`蹊径 > 转换DecorateCost表格`
3. 确认Excel路径`E:\WorkSpace\Docs\config\DecorateCost.xlsx`
4. 点击"开始转换"
5. 检查转换日志,确认成功
**转换示例**
```
Icon/first/s1_icon_renwu_5 → s1_icon_renwu_5
Icon/first/s2_icon_renwu_3 → s2_icon_renwu_3
cat/old/old_curtain → old_curtain
```
### 步骤2: 准备SO资源
#### 确保SO中包含所需资源
**DecorateScene SO** (初始化场景)
```
Assets/Art_SubModule/Art_SO/DecorateScene/Scene1Resource.asset
└── Items
├── Name: "old_curtain" → Sprite: cat/old/old_curtain.png
├── Name: "back_box_2" → Sprite: cat/old/back_box_2.png
└── Name: "old_gabage" → Sprite: cat/old/old_gabage.png
```
**DecorateIcon SO** (步骤图标)
```
Assets/Art_SubModule/Art_SO/DecorateIcon/DecorateIconResource.asset
└── Items
├── Name: "s1_icon_renwu_1" → Sprite: Icon/first/s1_icon_renwu_1.png
├── Name: "s1_icon_renwu_2" → Sprite: Icon/first/s1_icon_renwu_2.png
└── Name: "s2_icon_renwu_1" → Sprite: Icon/first/s2_icon_renwu_1.png
```
### 步骤3: 使用编辑器
1. 打开:`蹊径 > 场景建造工具`
2. 加载配置会自动从SO中加载图片
3. 编辑步骤配置
4. 保存时自动验证Icon名称是否存在
## 📝 配置示例
### DecorateCost.xlsx 表格格式
| Id | AreaId | SortId | Title | Icon | Pos | Action |
|----|--------|--------|-------|------|-----|--------|
| 1 | 1 | 0 | show | old_curtain | 150#625 | init_img#bg/clearObj/1 |
| 2 | 1 | 1 | 炉火 | s1_icon_renwu_1 | first | clear#bg/clearObj/10 |
| 3 | 1 | 2 | 窗户 | s1_icon_renwu_2 | second | add#bg/clearObj/18 |
### SO配置规则
```csharp
// SortId = 0 → 从 DecorateScene 加载
if (row.SortId == 0) {
// 查找 Scene{AreaId}Resource.asset
// 例如AreaId=1 → Scene1Resource.asset
}
// SortId > 0 → 从 DecorateIcon 加载
if (row.SortId > 0) {
// 查找 DecorateIconResource.asset
}
```
## ⚠️ 注意事项
### 1. Icon名称规范
- **必须与SO中Item的Name完全匹配**
- 区分大小写
- 不包含路径前缀(如 `Icon/first/`
### 2. SO资源管理
- 定期检查SO中是否包含所有需要的资源
- 保持Name的唯一性
- Sprite引用不能为空
### 3. 保存验证
- 保存前会检查Icon名称有效性
- 无效的Icon会显示详细错误信息
- 建议修复后再保存(强制保存可能导致运行时错误)
### 4. 兼容性
- 支持拖入Sprite自动提取Name
- 兼容旧路径格式自动转换为Name
- 旧表格需要先运行转换工具
## 🔧 故障排查
### 问题1: 保存时提示"Icon名称在SO中不存在"
**原因**: Icon名称与SO中的Item Name不匹配
**解决方案**:
1. 检查Icon列的值是否正确
2. 打开对应的SO资源Scene{AreaId}Resource 或 DecorateIconResource
3. 确认Items列表中是否有匹配Name的项
4. 修正Icon名称或在SO中添加缺失的Item
### 问题2: Icon预览显示为空
**原因**: 无法从SO中加载Sprite
**解决方案**:
1. 检查SO资源是否存在
2. 确认Item.Name与Icon名称匹配
3. 确认Item.Sprite引用不为空
4. 查看Console日志获取详细错误信息
### 问题3: 自动设置Icon失败
**原因**: SO中找不到对应的Icon资源
**解决方案**:
1. 确认图标资源已添加到DecorateIconResource.asset
2. 确认Name格式`s{AreaId}_icon_renwu_{StepId}`
3. 例如AreaId=1, Step=5 → Name应为 `s1_icon_renwu_5`
### 问题4: 拖入Sprite后路径错误
**原因**: Sprite不在SO中使用了旧路径提取逻辑
**解决方案**:
1. 确保拖入的Sprite已添加到对应SO的Items中
2. 如果是新Sprite先在SO中添加设置Name再拖入编辑器
## 📚 API参考
### 新增方法
```csharp
// 加载装饰图标SO
ArtTableSO LoadDecorateIconSO()
// 加载场景资源SO
ArtTableSO LoadDecorateSceneSO(int areaId)
// 从SO中查找Sprite
Sprite FindSpriteInSO(string iconName, int areaId, bool isInitScene)
// 验证Icon名称
bool ValidateIconNameInSO(string iconName, int areaId, bool isInitScene, out string errorMsg)
// 从Icon名称加载Sprite兼容旧格式
Sprite LoadSpriteFromIconName(string iconNameOrPath, int areaId, bool isInitScene)
```
## 🎉 优势
1. **统一资源管理**: 所有图片资源集中在SO中管理
2. **编辑器友好**: 可视化管理,支持拖拽
3. **运行时高效**: 通过Name快速查找无需路径解析
4. **类型安全**: 在保存时验证资源存在性
5. **易于维护**: SO结构清晰便于扩展
## 📌 版本信息
- **升级日期**: 2026年1月31日
- **影响范围**: DecorateConfigEditor, DecorateCost.xlsx
- **兼容性**: 完全向后兼容(需先运行转换工具)
---
如有问题,请联系技术支持或查看详细日志。

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 71927440b54c68d46a8c2410690241cc
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: