策划工具更新

This commit is contained in:
zhang hongbo 2026-04-14 17:30:13 +08:00
parent f3be6dbec3
commit cc1400d2dd
12 changed files with 979 additions and 362 deletions

View File

@ -498,21 +498,14 @@ namespace DesignTools.Collections
int.TryParse(initText, out init);
}
int iconId = -1;
if (!string.IsNullOrEmpty(iconText))
{
if (!int.TryParse(iconText, out iconId))
{
iconId = -1;
}
}
string iconName = iconText ?? "";
var emojiData = new EmojiData
{
Id = id,
NameKey = nameKey,
Init = init,
IconId = iconId
IconName = iconName
};
emojiDataList.Add(emojiData);
@ -669,13 +662,16 @@ namespace DesignTools.Collections
var iconNames = iconNamesList.ToArray();
int currentIndex = 0;
var currentItem = iconItems.Find(x => x.Id == data.IconId);
if (currentItem != null)
if (!string.IsNullOrEmpty(data.IconName))
{
int itemIndex = iconItems.IndexOf(currentItem);
if (itemIndex >= 0)
var currentItem = iconItems.Find(x => x.Name == data.IconName);
if (currentItem != null)
{
currentIndex = itemIndex + 1;
int itemIndex = iconItems.IndexOf(currentItem);
if (itemIndex >= 0)
{
currentIndex = itemIndex + 1;
}
}
}
@ -685,18 +681,18 @@ namespace DesignTools.Collections
{
if (newIndex == 0)
{
data.IconId = -1;
data.IconName = "";
}
else if (newIndex > 0 && newIndex <= iconItems.Count)
{
data.IconId = iconItems[newIndex - 1].Id;
data.IconName = iconItems[newIndex - 1].Name;
}
}
// 预览
if (data.IconId >= 0)
if (!string.IsNullOrEmpty(data.IconName))
{
var item = iconItems.Find(x => x.Id == data.IconId);
var item = iconItems.Find(x => x.Name == data.IconName);
if (item != null)
{
Sprite sprite = item.Sprite;
@ -792,7 +788,7 @@ namespace DesignTools.Collections
Id = newId,
NameKey = "",
Init = 0,
IconId = -1
IconName = ""
};
emojiDataList.Add(newEmoji);
@ -865,14 +861,7 @@ namespace DesignTools.Collections
worksheet.Cells[row, nameKeyCol].Value = emojiData.NameKey;
worksheet.Cells[row, initCol].Value = emojiData.Init;
if (emojiData.IconId < 0)
{
worksheet.Cells[row, iconCol].Value = "";
}
else
{
worksheet.Cells[row, iconCol].Value = emojiData.IconId;
}
worksheet.Cells[row, iconCol].Value = emojiData.IconName ?? "";
processedIds.Add(id);
}
@ -893,14 +882,7 @@ namespace DesignTools.Collections
worksheet.Cells[currentRow, nameKeyCol].Value = emojiData.NameKey;
worksheet.Cells[currentRow, initCol].Value = emojiData.Init;
if (emojiData.IconId < 0)
{
worksheet.Cells[currentRow, iconCol].Value = "";
}
else
{
worksheet.Cells[currentRow, iconCol].Value = emojiData.IconId;
}
worksheet.Cells[currentRow, iconCol].Value = emojiData.IconName ?? "";
}
}
@ -931,7 +913,7 @@ namespace DesignTools.Collections
public int Id;
public string NameKey;
public int Init;
public int IconId;
public string IconName = "";
}
}
}

View File

@ -525,21 +525,14 @@ namespace DesignTools.Collections
int.TryParse(initText, out init);
}
int iconId = -1; // 默认为未选择
if (!string.IsNullOrEmpty(iconText))
{
if (!int.TryParse(iconText, out iconId))
{
iconId = -1; // 解析失败设为未选择
}
}
string iconName = iconText ?? "";
var faceData = new FaceData
{
Id = id,
NameKey = nameKey,
Init = init,
IconId = iconId
IconName = iconName
};
faceDataList.Add(faceData);
@ -700,14 +693,17 @@ namespace DesignTools.Collections
// 查找当前选中的索引
int currentIndex = 0; // 默认为"未选择"
var currentItem = iconItems.Find(x => x.Id == data.IconId);
if (currentItem != null)
if (!string.IsNullOrEmpty(data.IconName))
{
// 找到了对应的Item索引需要+1因为第0项是"未选择"
int itemIndex = iconItems.IndexOf(currentItem);
if (itemIndex >= 0)
var currentItem = iconItems.Find(x => x.Name == data.IconName);
if (currentItem != null)
{
currentIndex = itemIndex + 1;
// 找到了对应的Item索引需要+1因为第0项是"未选择"
int itemIndex = iconItems.IndexOf(currentItem);
if (itemIndex >= 0)
{
currentIndex = itemIndex + 1;
}
}
}
@ -718,19 +714,19 @@ namespace DesignTools.Collections
if (newIndex == 0)
{
// 选择了"未选择"
data.IconId = -1;
data.IconName = "";
}
else if (newIndex > 0 && newIndex <= iconItems.Count)
{
// 选择了具体的Icon索引需要-1
data.IconId = iconItems[newIndex - 1].Id;
data.IconName = iconItems[newIndex - 1].Name;
}
}
// 预览和Desc提示
if (data.IconId >= 0)
if (!string.IsNullOrEmpty(data.IconName))
{
var item = iconItems.Find(x => x.Id == data.IconId);
var item = iconItems.Find(x => x.Name == data.IconName);
if (item != null)
{
// 直接使用Sprite引用
@ -796,7 +792,7 @@ namespace DesignTools.Collections
}
else
{
// IconId < 0,未选择状态
// IconName为空,未选择状态
GUILayoutUtility.GetRect(50, 50, GUILayout.Width(50), GUILayout.Height(50));
GUILayout.Space(-50);
EditorGUILayout.LabelField("未选择", GUILayout.Width(50));
@ -835,7 +831,7 @@ namespace DesignTools.Collections
Id = newId,
NameKey = "",
Init = 0,
IconId = -1
IconName = ""
};
faceDataList.Add(newFace);
@ -911,15 +907,7 @@ namespace DesignTools.Collections
worksheet.Cells[row, nameKeyCol].Value = faceData.NameKey;
worksheet.Cells[row, initCol].Value = faceData.Init;
// IconId为-1时写入空字符串否则写入实际值
if (faceData.IconId < 0)
{
worksheet.Cells[row, iconCol].Value = "";
}
else
{
worksheet.Cells[row, iconCol].Value = faceData.IconId;
}
worksheet.Cells[row, iconCol].Value = faceData.IconName ?? "";
processedIds.Add(id);
}
@ -942,14 +930,7 @@ namespace DesignTools.Collections
worksheet.Cells[currentRow, nameKeyCol].Value = faceData.NameKey;
worksheet.Cells[currentRow, initCol].Value = faceData.Init;
if (faceData.IconId < 0)
{
worksheet.Cells[currentRow, iconCol].Value = "";
}
else
{
worksheet.Cells[currentRow, iconCol].Value = faceData.IconId;
}
worksheet.Cells[currentRow, iconCol].Value = faceData.IconName ?? "";
}
}
@ -981,7 +962,7 @@ namespace DesignTools.Collections
public int Id;
public string NameKey;
public int Init;
public int IconId;
public string IconName = "";
}
}
}

View File

@ -502,14 +502,7 @@ namespace DesignTools.Collections
int.TryParse(initText, out init);
}
int iconId = -1;
if (!string.IsNullOrEmpty(iconText))
{
if (!int.TryParse(iconText, out iconId))
{
iconId = -1;
}
}
string iconName = iconText ?? "";
float frameImageScale = 1.0f;
if (!string.IsNullOrEmpty(frameImageScaleText))
@ -525,7 +518,7 @@ namespace DesignTools.Collections
Id = id,
NameKey = nameKey,
Init = init,
IconId = iconId,
IconName = iconName,
FrameImageScale = frameImageScale
};
@ -685,13 +678,16 @@ namespace DesignTools.Collections
var iconNames = iconNamesList.ToArray();
int currentIndex = 0;
var currentItem = iconItems.Find(x => x.Id == data.IconId);
if (currentItem != null)
if (!string.IsNullOrEmpty(data.IconName))
{
int itemIndex = iconItems.IndexOf(currentItem);
if (itemIndex >= 0)
var currentItem = iconItems.Find(x => x.Name == data.IconName);
if (currentItem != null)
{
currentIndex = itemIndex + 1;
int itemIndex = iconItems.IndexOf(currentItem);
if (itemIndex >= 0)
{
currentIndex = itemIndex + 1;
}
}
}
@ -701,18 +697,18 @@ namespace DesignTools.Collections
{
if (newIndex == 0)
{
data.IconId = -1;
data.IconName = "";
}
else if (newIndex > 0 && newIndex <= iconItems.Count)
{
data.IconId = iconItems[newIndex - 1].Id;
data.IconName = iconItems[newIndex - 1].Name;
}
}
// 预览
if (data.IconId >= 0)
if (!string.IsNullOrEmpty(data.IconName))
{
var item = iconItems.Find(x => x.Id == data.IconId);
var item = iconItems.Find(x => x.Name == data.IconName);
if (item != null)
{
Sprite sprite = item.Sprite;
@ -811,7 +807,7 @@ namespace DesignTools.Collections
Id = newId,
NameKey = "",
Init = 0,
IconId = -1,
IconName = "",
FrameImageScale = 1.0f
};
@ -888,14 +884,7 @@ namespace DesignTools.Collections
worksheet.Cells[row, nameKeyCol].Value = frameData.NameKey;
worksheet.Cells[row, initCol].Value = frameData.Init;
if (frameData.IconId < 0)
{
worksheet.Cells[row, iconCol].Value = "";
}
else
{
worksheet.Cells[row, iconCol].Value = frameData.IconId;
}
worksheet.Cells[row, iconCol].Value = frameData.IconName ?? "";
worksheet.Cells[row, frameImageScaleCol].Value = frameData.FrameImageScale;
@ -918,14 +907,7 @@ namespace DesignTools.Collections
worksheet.Cells[currentRow, nameKeyCol].Value = frameData.NameKey;
worksheet.Cells[currentRow, initCol].Value = frameData.Init;
if (frameData.IconId < 0)
{
worksheet.Cells[currentRow, iconCol].Value = "";
}
else
{
worksheet.Cells[currentRow, iconCol].Value = frameData.IconId;
}
worksheet.Cells[currentRow, iconCol].Value = frameData.IconName ?? "";
worksheet.Cells[currentRow, frameImageScaleCol].Value = frameData.FrameImageScale;
}
@ -958,7 +940,7 @@ namespace DesignTools.Collections
public int Id;
public string NameKey;
public int Init;
public int IconId;
public string IconName = "";
public float FrameImageScale;
}
}

View File

@ -0,0 +1,326 @@
# 策划工具系统 (Design Tools) - AI Skill 文档
## 概述
策划工具系统是基于Unity Editor的配置数据管理工具集主要用于编辑xlsx配置表数据并与美术资源ArtTableSO联动显示预览。所有工具通过Unity菜单栏「策划工具」菜单访问。
---
## 项目结构
### 代码目录
```
Assets/Design_SubModule/Scripts/Editor/Design_Tools/
├── BaseDesignToolEditor.cs # 编辑器基类Git管理、路径管理、UI框架
├── ItemConfigEditor.cs # 道具配置编辑器 (Item.xlsx)
├── Common/
│ └── DesignToolItemPickerWindow.cs # 通用道具选择弹窗(搜索+分页+筛选)
├── Collections/
│ ├── HeadFrameConfigEditor.cs # 头像框配置 (Avatar.xlsx)
│ ├── HeadConfigEditor.cs # 头像配置 (Face.xlsx)
│ └── EmojiConfigEditor.cs # 表情配置 (Emoji.xlsx)
├── DesignSoundEditorWindow/
│ ├── AmbientSoundConfigEditor.cs # 环境音配置 (Sound.xlsx - AmbientSound)
│ └── SoundItemConfigEditor.cs # 音效配置 (Sound.xlsx - Sound)
├── Friends/
│ ├── FriendTreasureChestEditor.cs # 宝箱奖励及概率 (FriendTreasure.xlsx - Chest)
│ └── InviteRewardEditor.cs # 邀请奖励 (Invite.xlsx - Reward)
├── LimitedTimeEvent/
│ └── CatTrickConstEditor.cs # 小猫戏法常量 (LimitedTimeEvent.xlsx - Const)
└── Scene/
├── DecorateCostPlannerEditor.cs # 装饰消耗规划 (DecorateCost.xlsx)
├── IndoorProgressEditor.cs # 场景进度奖励 (IndoorProgress.xlsx + Item.xlsx)
└── MaxAreaIdEditor.cs # 最大场景ID (Constant.xlsx - ConstantInt)
```
### 菜单路径
| 菜单路径 | 编辑器类 | 操作的xlsx文件 |
|---------|---------|--------------|
| 策划工具/Item配置 | ItemConfigEditor | Item.xlsx (Sheet: Item) |
| 策划工具/收藏品/头像框 | HeadFrameConfigEditor | Avatar.xlsx (Sheet: Avatar) |
| 策划工具/收藏品/头像 | HeadConfigEditor | Face.xlsx (Sheet: Face) |
| 策划工具/收藏品/表情 | EmojiConfigEditor | Emoji.xlsx (Sheet: Emoji) |
| 策划工具/好友/好友馈赠/宝箱奖励及概率 | FriendTreasureChestEditor | FriendTreasure.xlsx (Sheet: Chest) |
| 策划工具/好友/邀请奖励 | InviteRewardEditor | Invite.xlsx (Sheet: Reward) |
| 策划工具/限时活动/小猫戏法常量 | CatTrickConstEditor | LimitedTimeEvent.xlsx (Sheet: Const) |
| 策划工具/场景/装饰消耗规划 | DecorateCostPlannerEditor | DecorateCost.xlsx (Sheet: DecorateCost) |
| 策划工具/场景/场景进度奖励 | IndoorProgressEditor | IndoorProgress.xlsx + Item.xlsx |
| 策划工具/场景/最大场景ID | MaxAreaIdEditor | Constant.xlsx (Sheet: ConstantInt) |
| 策划工具/音效配置/环境音 | AmbientSoundConfigEditor | Sound.xlsx (Sheet: AmbientSound + Sound) |
| 策划工具/音效配置/音效 | SoundItemConfigEditor | Sound.xlsx (Sheet: Sound) |
---
## Excel 表格约定
### 通用格式规则
- **第1行**Key行列名/字段名,如 Id, Name, IType, Res 等)
- **第2行**:备注行(中文说明,程序不读取)
- **第3行起**:实际数据行
### 数据起始行常量
所有编辑器中 `DATA_START_ROW = 3`即从第3行开始读写数据
### xlsx文件位置
所有xlsx文件存放在Docs项目外部Git仓库`config/` 目录下。
### 主要xlsx结构
#### Item.xlsx (Sheet: Item)
| 列名 | 类型 | 说明 |
|------|------|------|
| Id | int | 道具唯一ID |
| Name | string | 道具名称 |
| IType | int | 道具类型见IType类型表 |
| Effect | string | 效果数据格式因IType而异 |
| Res | string | 美术资源引用,格式: `"TableName,ItemName"` → 对应ArtTableSO的TableName和ArtItemData的Name |
#### IndoorProgress.xlsx (Sheet: IndoorProgress)
| 列名 | 类型 | 说明 |
|------|------|------|
| Id | int | 唯一ID |
| Scene | int | 场景编号 |
| Lv | int | 等级/步骤 |
| Item | string | 道具奖励JSON: `[{"Id":83,"Num":1}]` |
| Emit | string | 发射的道具ID: `83` |
| Reward | string | 奖励: `83=1` |
| BigReward | string | 大奖励: `Energy=25``Energy=50,PurplePig=1` |
| AreaReward | string | 区域奖励JSON: `[{"Id":100001,"Num":25}]` |
| Part | int | 零件数量 |
#### FriendTreasure.xlsx (Sheet: Chest)
| 列名 | 类型 | 说明 |
|------|------|------|
| 第1列 | int | Id |
| 第2列 | string | Items JSON: `[{"Id":83,"Num":1}]` |
| 第3列 | int | Prob 概率(总和=1000即千分之 |
#### Avatar.xlsx (Sheet: Avatar)
| 列名 | 类型 | 说明 |
|------|------|------|
| Id | int | 头像框ID |
| NameKey | string | 多语言key |
| Init | int | 是否初始拥有 (0/1) |
| Icon | int | 图标资源ID对应HeadFrameResource SO中的ArtItemData.Id |
| FrameImageScale | float | 缩放比例 |
#### Face.xlsx (Sheet: Face)
| 列名 | 类型 | 说明 |
|------|------|------|
| Id | int | 头像ID |
| NameKey | string | 多语言key |
| Init | int | 是否初始拥有 |
| Icon | int | 图标资源ID对应HeadResource SO中的ArtItemData.Id |
#### Emoji.xlsx (Sheet: Emoji)
类似Face.xlsx结构Icon对应EmojiResource SO。
---
## IType 道具类型表
| IType | 名称 | 说明 |
|-------|------|------|
| 1 | 能量 | Energy |
| 2 | 星星 | Star |
| 3 | 钻石 | Diamond |
| 97 | Playroom宠物道具 | |
| 98 | 卡牌 | |
| 99 | 背包道具 | |
| 100 | 棋子 | 数量最多的类型 |
| 101 | 卡包 | |
| 102 | 限时事件 | Effect格式: `eventId,seconds` |
| 103 | 小猪存钱罐 | |
| 104 | 万能卡 | |
| 105 | 头像框 | Effect关联Avatar.xlsx |
| 106 | 活动代币 | |
| 107 | 竞赛游戏代币 | |
| 108 | Pet Playroom拜访道具 | |
| 109 | 表情 | Effect关联Emoji.xlsx |
| 110 | 头像 | Effect关联Face.xlsx |
| 111 | Playroom装饰 | |
| 112 | Playroom服装 | |
| 113 | Playroom装饰套装 | |
| 114 | Playroom服装套装 | |
| 115 | Playroom道具宝箱 | |
| 116 | 活动通行证代币道具 | |
---
## 美术资源系统 (Art Resource)
### 核心类
#### ArtTableSO (ScriptableObject)
```
路径: Assets/Art_SubModule/Art_Scripts/ArtTableSO.cs
```
| 字段 | 类型 | 说明 |
|------|------|------|
| TableId | int | 表ID全局唯一系统自动分配 |
| TableName | string | 表名称(全局唯一标识符,如 "HeadFrameResource" |
| Items | List\<ArtItemData\> | 资源项列表 |
#### ArtItemData (Serializable)
```
路径: Assets/Art_SubModule/Art_Scripts/ArtItemData.cs
```
| 字段 | 类型 | 说明 |
|------|------|------|
| Id | int | 资源ID表内唯一 |
| Name | string | 资源名称(唯一,用于查询) |
| Desc | string | 资源描述 |
| Sprite | Sprite | 图片资源引用Editor模式 |
| SpritePath | string | 图片资源路径Runtime模式 |
| SpineAsset | SkeletonDataAsset | Spine骨骼资源Editor模式 |
| SpineAssetPath | string | Spine资源路径Runtime模式 |
| SpineAnimName | string | Spine动画名称 |
### Art_SO 目录结构
```
Assets/Art_SubModule/Art_SO/
├── art_table_manifest.json # 资源表全局配置清单
├── Collections/ # 收藏品资源
│ ├── EmojiResource.asset # 表情TableName="EmojiResource"
│ ├── HeadFrameResource.asset # 头像框TableName="HeadFrameResource"
│ └── HeadResource.asset # 头像TableName="HeadResource"
├── DecorateIcon/ # 装饰图标资源(按场景区间分组)
│ ├── DecorateIconResource_1_8.asset
│ ├── DecorateIconResource_9_17.asset
│ └── ...共9个按区间的资源
├── DecorateScene/ # 装饰场景资源
│ └── Scene1Resource.asset ~ Scene55Resource.asset
├── Scene/
│ └── SceneExpression.asset
└── Shop/
├── ShopBig.asset
└── ShopOther.asset
```
### Res字段格式
Item.xlsx中的Res字段格式为 `"TableName,ItemName"`
- `TableName` → 对应 ArtTableSO.TableName全局唯一标识符
- `ItemName` → 对应 ArtItemData.Name表内唯一名称
- 例如: `"HeadFrameResource,frame_001"` 表示 TableName="HeadFrameResource" 的 ArtTableSO 中 Name="frame_001" 的 ArtItemData
- 仅选了表但未选Item时: `"HeadFrameResource,"`(逗号后为空)
> **历史格式**: 旧版本使用 `"TableId,ItemId"`(数字格式如 `"3,15"`。ItemConfigEditor中提供了「迁移Res格式(ID→Name)」按钮,可一键将旧格式转换为新格式。
### 资源查找流程
```
Res = "TableName,ItemName"
→ 找到 ArtTableSO (where TableName == TableName)
→ 在 Items 列表中找到 ArtItemData (where Name == ItemName)
→ 获取 Sprite / SpinePath 等资源
```
ItemConfigEditor中的DrawResField使用三级选择UI
1. **组(Folder)** → Art_SO下的子文件夹Collections/DecorateIcon等
2. **表(Table)** → 该文件夹下的ArtTableSO
3. **Item** → 该表中的ArtItemData
---
## 基类架构 (BaseDesignToolEditor)
### 继承关系
```
EditorWindow
├── BaseDesignToolEditor (抽象基类)
│ ├── FriendTreasureChestEditor
│ ├── InviteRewardEditor
│ ├── CatTrickConstEditor
│ ├── AmbientSoundConfigEditor
│ ├── SoundItemConfigEditor
│ ├── DecorateCostPlannerEditor
│ └── MaxAreaIdEditor
├── ItemConfigEditor (独立,未继承基类)
├── HeadFrameConfigEditor (独立)
├── HeadConfigEditor (独立)
├── EmojiConfigEditor (独立)
└── IndoorProgressEditor (独立)
```
### 基类提供的能力
- `docsRootPath` 管理和EditorPrefs持久化
- `LoadData()` 统一加载流程:路径校验 → Git检查/更新 → 分支检查 → 子类LoadConfigData()
- `ExecuteGitCommand()` Git命令执行
- `CheckAndUpdateDocsRepository()` Docs仓库fetch/pull/冲突检测
- `CheckAndSwitchDesignSubModuleBranch()` Design_SubModule分支切换
- 统一UI框架工具栏、路径选择、加载按钮、底部保存/重载按钮
- `GetDocsConfigFilePath(fileName)` 拼接Docs配置文件路径
### 子类必须实现的抽象方法
- `GetDocsPathPrefKey()` - 返回唯一的EditorPrefs key
- `GetWindowTitle()` - 窗口标题
- `LoadConfigData()` - 具体的数据加载逻辑
- `DrawDataEditor()` - 具体的UI绘制逻辑
### 子类可选覆盖的虚方法
- `GetMinWindowSize()` - 窗口最小尺寸默认1000x600
- `NeedCheckDesignSubModuleBranch()` - 是否检查分支默认true
- `SaveDataToExcel()` - 保存到Excel
- `ShowBottomButtons()` - 是否显示底部按钮默认true
---
## 通用组件
### DesignToolItemPickerWindow道具选择弹窗
```
路径: Common/DesignToolItemPickerWindow.cs
```
**功能**:弹出模态窗口,供用户在大量道具中快速查找并选择
**使用方式**
```csharp
DesignToolItemPickerWindow.ShowWindow(
items, // List<DesignToolItemPickerItem> 所有道具
itemTypes, // List<int> 所有IType列表
typeNames, // Dictionary<int,string> IType名称映射
currentType, // 当前筛选的IType
selectedItemId, // 当前选中的ItemId
ownerRect, // 父窗口位置(弹窗居中用)
onSelected // Action<int> 选中回调返回ItemId
);
```
**UI特性**搜索框按ID/Name匹配、IType下拉筛选、分页每页20条、当前选中高亮
**使用者**FriendTreasureChestEditor、IndoorProgressEditor道具奖励和区域奖励均使用
---
## 多语言支持
加载 `AllLanguage.xlsx` (Sheet: client)
- 第1行: key行key, zh_CN, en_US, pt_BR...
- 第2行: 备注行
- 第3行起: 数据行
- 用于Collections编辑器中显示名称tooltip
---
## Git工作流
所有工具在加载数据前会:
1. 检查Docs目录是否为Git仓库.git目录存在
2. `git fetch` 检查远程更新
3. `git status -uno` 检查本地状态
4. 如有远程更新则 `git pull`
5. 检查Design_SubModule当前分支必要时 `checkout main`
保存后会提醒用户手动commit和push。
---
## 关键数据格式
| 格式名 | 示例 | 使用场景 |
|--------|------|---------|
| Item JSON | `[{"Id":83,"Num":1}]` | IndoorProgress.Item / FriendTreasure.Items |
| 多Item JSON | `[{"Id":100001,"Num":50},{"Id":100021,"Num":1}]` | IndoorProgress.AreaReward |
| Reward字符串 | `83=1` | IndoorProgress.Reward |
| BigReward字符串 | `Energy=25,PurplePig=1` | IndoorProgress.BigReward使用MY_ITEM_DICT映射名称 |
| Res引用 | `"TableName,ItemName"``"HeadFrameResource,frame_001"` | Item.xlsx.Res字段 |
| Effect引用 | `"id,param"``"5,0"` | Item.xlsx.Effect字段收藏品/限时事件) |
| 限时事件 | `"eventId,seconds"` | IType=102的Effect |

View File

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

View File

@ -645,20 +645,23 @@ namespace DesignTools.Friends
return;
}
if (!int.TryParse(parts[0], out int tableId) || !int.TryParse(parts[1], out int artItemId))
string tableName = parts[0];
string artItemName = parts[1];
if (string.IsNullOrEmpty(tableName) || string.IsNullOrEmpty(artItemName))
{
EditorGUILayout.LabelField("无图", GUILayout.Width(70));
return;
}
ArtTableSO artTable = allArtTables.FirstOrDefault(x => x.TableId == tableId);
ArtTableSO artTable = allArtTables.FirstOrDefault(x => x.TableName == tableName);
if (artTable == null)
{
EditorGUILayout.LabelField("无图", GUILayout.Width(70));
return;
}
var artItem = artTable.Items.FirstOrDefault(x => x.Id == artItemId);
var artItem = artTable.Items.FirstOrDefault(x => x.Name == artItemName);
if (artItem == null || artItem.Sprite == null)
{
EditorGUILayout.LabelField("无图", GUILayout.Width(70));

View File

@ -616,13 +616,12 @@ namespace DesignTools
{
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
IconName = iconText ?? ""
};
}
}
@ -681,13 +680,12 @@ namespace DesignTools
{
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
IconName = iconText ?? ""
};
}
}
@ -746,13 +744,12 @@ namespace DesignTools
{
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
IconName = iconText ?? ""
};
}
}
@ -1306,13 +1303,10 @@ namespace DesignTools
{
EditorGUILayout.BeginVertical(GUILayout.Width(200));
// Res格式: "TableId,ItemId"
// Res格式: "TableName,ItemName"
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);
string tableName = parts.Length > 0 ? parts[0] : "";
string itemName = parts.Length > 1 ? parts[1] : "";
// 查找当前表格和所属文件夹
ArtTableSO currentTable = null;
@ -1325,9 +1319,9 @@ namespace DesignTools
}
// 如果有Res数据从Res推导组和表
if (tableId >= 0)
if (!string.IsNullOrEmpty(tableName))
{
currentTable = allArtTables.FirstOrDefault(x => x.TableId == tableId);
currentTable = allArtTables.FirstOrDefault(x => x.TableName == tableName);
if (currentTable != null)
{
string tablePath = AssetDatabase.GetAssetPath(currentTable);
@ -1365,8 +1359,8 @@ namespace DesignTools
resSelectedFolderCache[data.Id] = currentFolder;
// 组改变时清空表和Item选择并清空Res数据
currentTable = null;
tableId = -1;
itemId = -1;
tableName = "";
itemName = "";
data.Res = ""; // 清空Res避免旧数据干扰
}
else if (newFolderIndex == 0)
@ -1405,7 +1399,7 @@ namespace DesignTools
int currentTableIndex = 0;
if (currentTable != null && !folderChanged)
{
int foundIndex = tablesInFolder.FindIndex(t => t.TableId == currentTable.TableId);
int foundIndex = tablesInFolder.FindIndex(t => t.TableName == currentTable.TableName);
if (foundIndex >= 0)
{
currentTableIndex = foundIndex + 1; // +1 因为有"未选择"选项
@ -1422,8 +1416,8 @@ namespace DesignTools
{
currentTable = tablesInFolder[newTableIndex - 1]; // -1 因为有"未选择"选项
// 表格改变时清空Item选择
data.Res = $"{currentTable.TableId},-1";
itemId = -1;
data.Res = $"{currentTable.TableName},";
itemName = "";
}
else if (newTableIndex == 0)
{
@ -1452,9 +1446,9 @@ namespace DesignTools
itemOptions.AddRange(currentTable.Items.Select(item => $"{item.Name}(ID:{item.Id})"));
int currentItemIndex = 0;
if (itemId >= 0 && !tableSelectionChanged)
if (!string.IsNullOrEmpty(itemName) && !tableSelectionChanged)
{
int foundIndex = currentTable.Items.FindIndex(item => item.Id == itemId);
int foundIndex = currentTable.Items.FindIndex(item => item.Name == itemName);
if (foundIndex >= 0)
{
currentItemIndex = foundIndex + 1; // +1 因为有"未选择"选项
@ -1467,12 +1461,12 @@ namespace DesignTools
{
if (newItemIndex > 0 && newItemIndex <= currentTable.Items.Count)
{
data.Res = $"{currentTable.TableId},{currentTable.Items[newItemIndex - 1].Id}"; // -1 因为有"未选择"选项
data.Res = $"{currentTable.TableName},{currentTable.Items[newItemIndex - 1].Name}";
}
else if (newItemIndex == 0)
{
// 选择"未选择"
data.Res = $"{currentTable.TableId},-1";
data.Res = $"{currentTable.TableName},";
}
}
}
@ -1523,8 +1517,9 @@ namespace DesignTools
if (!avatarDict.ContainsKey(avatarId)) return null;
int iconId = avatarDict[avatarId].IconId;
var item = headFrameResourceSO.Items.FirstOrDefault(x => x.Id == iconId);
string iconName = avatarDict[avatarId].IconName;
if (string.IsNullOrEmpty(iconName)) return null;
var item = headFrameResourceSO.Items.FirstOrDefault(x => x.Name == iconName);
return item?.Sprite;
}
@ -1543,8 +1538,9 @@ namespace DesignTools
if (!faceDict.ContainsKey(faceId)) return null;
int iconId = faceDict[faceId].IconId;
var item = headResourceSO.Items.FirstOrDefault(x => x.Id == iconId);
string iconName = faceDict[faceId].IconName;
if (string.IsNullOrEmpty(iconName)) return null;
var item = headResourceSO.Items.FirstOrDefault(x => x.Name == iconName);
return item?.Sprite;
}
@ -1563,8 +1559,9 @@ namespace DesignTools
if (!emojiDict.ContainsKey(emojiId)) return null;
int iconId = emojiDict[emojiId].IconId;
var item = emojiResourceSO.Items.FirstOrDefault(x => x.Id == iconId);
string iconName = emojiDict[emojiId].IconName;
if (string.IsNullOrEmpty(iconName)) return null;
var item = emojiResourceSO.Items.FirstOrDefault(x => x.Name == iconName);
return item?.Sprite;
}
@ -1579,13 +1576,15 @@ namespace DesignTools
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;
string tableName = parts[0];
string artItemName = parts[1];
var table = allArtTables.FirstOrDefault(x => x.TableId == tableId);
if (string.IsNullOrEmpty(tableName) || string.IsNullOrEmpty(artItemName)) return null;
var table = allArtTables.FirstOrDefault(x => x.TableName == tableName);
if (table == null) return null;
var item = table.Items.FirstOrDefault(x => x.Id == itemId);
var item = table.Items.FirstOrDefault(x => x.Name == artItemName);
return item?.Sprite;
}
@ -1634,15 +1633,15 @@ namespace DesignTools
string[] parts = res.Split(',');
if (parts.Length < 2) return "";
if (!int.TryParse(parts[0], out int tableId)) return "";
if (!int.TryParse(parts[1], out int itemId)) return "";
string tableName = parts[0];
string artItemName = parts[1];
if (itemId < 0) return ""; // 未选择Item
if (string.IsNullOrEmpty(tableName) || string.IsNullOrEmpty(artItemName)) return "";
var table = allArtTables.FirstOrDefault(x => x.TableId == tableId);
var table = allArtTables.FirstOrDefault(x => x.TableName == tableName);
if (table == null) return "";
var item = table.Items.FirstOrDefault(x => x.Id == itemId);
var item = table.Items.FirstOrDefault(x => x.Name == artItemName);
if (item == null) return "";
if (item.Sprite == null) return "";
@ -1814,7 +1813,7 @@ namespace DesignTools
{
public int Id;
public string NameKey;
public int IconId;
public string IconName = "";
}
/// <summary>
@ -1824,7 +1823,7 @@ namespace DesignTools
{
public int Id;
public string NameKey;
public int IconId;
public string IconName = "";
}
/// <summary>
@ -1834,7 +1833,7 @@ namespace DesignTools
{
public int Id;
public string NameKey;
public int IconId;
public string IconName = "";
}
}
}

View File

@ -0,0 +1,170 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using OfficeOpenXml;
using ArtResource;
namespace DesignTools
{
/// <summary>
/// 一次性工具:将 Emoji.xlsx / Face.xlsx / Avatar.xlsx 的 Icon 字段从 ArtItemData.Id 迁移为 ArtItemData.Name
/// 使用完毕后可删除此文件
/// </summary>
public static class MigrateIconFormatTool
{
private static readonly string[] SO_SEARCH_PATHS = new[]
{
"Assets/Art_SubModule/Art_SO",
"Assets/Art_SubModule/Art_SO/Collections"
};
private struct MigrationTarget
{
public string ExcelName;
public string SheetName;
public string SOTableName;
}
private static readonly MigrationTarget[] Targets = new[]
{
new MigrationTarget { ExcelName = "Emoji.xlsx", SheetName = "Emoji", SOTableName = "EmojiResource" },
new MigrationTarget { ExcelName = "Face.xlsx", SheetName = "Face", SOTableName = "HeadResource" },
new MigrationTarget { ExcelName = "Avatar.xlsx", SheetName = "Avatar", SOTableName = "HeadFrameResource" },
};
[MenuItem("策划工具/一次性迁移/Icon格式 ID→Name (Emoji+Face+Avatar)")]
public static void Execute()
{
// 1. 选择 Docs/config 目录
string configDir = EditorUtility.OpenFolderPanel("选择 Docs/config 目录(包含 Emoji.xlsx、Face.xlsx、Avatar.xlsx", "", "");
if (string.IsNullOrEmpty(configDir)) return;
// 2. 加载所有 ArtTableSO
var allArtTables = new List<ArtTableSO>();
foreach (string searchPath in SO_SEARCH_PATHS)
{
string[] guids = AssetDatabase.FindAssets("t:ArtTableSO", new[] { searchPath });
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
var so = AssetDatabase.LoadAssetAtPath<ArtTableSO>(path);
if (so != null && !allArtTables.Any(x => x.TableName == so.TableName))
allArtTables.Add(so);
}
}
if (allArtTables.Count == 0)
{
EditorUtility.DisplayDialog("错误", "未找到任何 ArtTableSO 资源", "确定");
return;
}
int totalMigrated = 0;
int totalSkipped = 0;
int totalErrors = 0;
var allMessages = new List<string>();
foreach (var target in Targets)
{
string excelPath = Path.Combine(configDir, target.ExcelName);
if (!File.Exists(excelPath))
{
allMessages.Add($"[{target.ExcelName}] 文件不存在,跳过");
continue;
}
var tableSO = allArtTables.FirstOrDefault(x => x.TableName == target.SOTableName);
if (tableSO == null)
{
allMessages.Add($"[{target.ExcelName}] 未找到 ArtTableSO: {target.SOTableName},跳过");
totalErrors++;
continue;
}
// 备份
string backupPath = excelPath + ".bak";
File.Copy(excelPath, backupPath, true);
int migrated = 0, skipped = 0, errors = 0;
using (var package = new ExcelPackage(new FileInfo(excelPath)))
{
var ws = package.Workbook.Worksheets[target.SheetName];
if (ws == null)
{
allMessages.Add($"[{target.ExcelName}] 未找到 Sheet: {target.SheetName},跳过");
totalErrors++;
continue;
}
// 找 Icon 列和 Id 列
int iconCol = -1, idCol = -1;
int colCount = ws.Dimension?.Columns ?? 0;
for (int c = 1; c <= colCount; c++)
{
string h = ws.Cells[1, c].Text;
if (h == "Icon") iconCol = c;
else if (h == "Id") idCol = c;
}
if (iconCol < 0)
{
allMessages.Add($"[{target.ExcelName}] 未找到 Icon 列,跳过");
totalErrors++;
continue;
}
int rowCount = ws.Dimension?.Rows ?? 0;
for (int row = 3; row <= rowCount; row++)
{
string iconText = ws.Cells[row, iconCol].Text.Trim();
if (string.IsNullOrEmpty(iconText))
{
continue;
}
// 判断是否是旧格式(纯数字)
if (!int.TryParse(iconText, out int artItemId))
{
// 已经是Name格式
skipped++;
continue;
}
string rowId = idCol > 0 ? ws.Cells[row, idCol].Text : row.ToString();
var artItem = tableSO.Items.FirstOrDefault(x => x.Id == artItemId);
if (artItem == null)
{
errors++;
allMessages.Add($"[{target.ExcelName}] Row {row} Id={rowId}: {target.SOTableName} 中无 ArtItemId={artItemId}");
continue;
}
ws.Cells[row, iconCol].Value = artItem.Name;
migrated++;
}
package.Save();
}
totalMigrated += migrated;
totalSkipped += skipped;
totalErrors += errors;
allMessages.Add($"[{target.ExcelName}] 成功: {migrated}, 跳过: {skipped}, 错误: {errors} (备份: {backupPath})");
}
// 报告
string msg = $"Icon 迁移完成!\n\n" +
$"总计成功: {totalMigrated} 条\n" +
$"已是新格式(跳过): {totalSkipped} 条\n" +
$"错误: {totalErrors} 条\n\n" +
string.Join("\n", allMessages);
EditorUtility.DisplayDialog("迁移结果", msg, "确定");
Debug.Log(msg);
}
}
}

View File

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

View File

@ -0,0 +1,155 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using OfficeOpenXml;
using ArtResource;
namespace DesignTools
{
/// <summary>
/// 一次性工具:将 Item.xlsx 的 Res 字段从 "TableId,ItemId" 迁移为 "TableName,ItemName"
/// 使用完毕后可删除此文件
/// </summary>
public static class MigrateResFormatTool
{
private const string ART_SO_PATH = "Assets/Art_SubModule/Art_SO";
private const string ITEM_SHEET_NAME = "Item";
[MenuItem("策划工具/一次性迁移/Res格式 ID→Name")]
public static void Execute()
{
// 1. 选择 Item.xlsx
string excelPath = EditorUtility.OpenFilePanel("选择 Item.xlsx", "", "xlsx");
if (string.IsNullOrEmpty(excelPath)) return;
if (!File.Exists(excelPath))
{
EditorUtility.DisplayDialog("错误", "文件不存在", "确定");
return;
}
// 2. 备份
string backupPath = excelPath + ".bak";
File.Copy(excelPath, backupPath, true);
Debug.Log($"已备份到: {backupPath}");
// 3. 加载所有 ArtTableSO
var allArtTables = new List<ArtTableSO>();
string[] guids = AssetDatabase.FindAssets("t:ArtTableSO", new[] { ART_SO_PATH });
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
var so = AssetDatabase.LoadAssetAtPath<ArtTableSO>(path);
if (so != null) allArtTables.Add(so);
}
if (allArtTables.Count == 0)
{
EditorUtility.DisplayDialog("错误", "未找到任何 ArtTableSO 资源", "确定");
return;
}
// 4. 读取并修改 Excel
int migratedCount = 0;
int skippedCount = 0;
int errorCount = 0;
var errors = new List<string>();
using (var package = new ExcelPackage(new FileInfo(excelPath)))
{
var ws = package.Workbook.Worksheets[ITEM_SHEET_NAME];
if (ws == null)
{
EditorUtility.DisplayDialog("错误", $"未找到 Sheet: {ITEM_SHEET_NAME}", "确定");
return;
}
// 找 Res 列
int resCol = -1;
int idCol = -1;
int nameCol = -1;
int colCount = ws.Dimension?.Columns ?? 0;
for (int c = 1; c <= colCount; c++)
{
string h = ws.Cells[1, c].Text;
if (h == "Res") resCol = c;
else if (h == "Id") idCol = c;
else if (h == "Name") nameCol = c;
}
if (resCol < 0)
{
EditorUtility.DisplayDialog("错误", "未找到 Res 列", "确定");
return;
}
int rowCount = ws.Dimension?.Rows ?? 0;
for (int row = 3; row <= rowCount; row++)
{
string res = ws.Cells[row, resCol].Text.Trim();
if (string.IsNullOrEmpty(res)) continue;
string[] parts = res.Split(',');
if (parts.Length < 2) continue;
// 判断是否是旧格式(首段为数字)
if (!int.TryParse(parts[0], out int tableId))
{
skippedCount++;
continue;
}
int.TryParse(parts[1], out int artItemId);
string rowId = idCol > 0 ? ws.Cells[row, idCol].Text : row.ToString();
string rowName = nameCol > 0 ? ws.Cells[row, nameCol].Text : "";
var table = allArtTables.FirstOrDefault(x => x.TableId == tableId);
if (table == null)
{
errorCount++;
errors.Add($"Row {row} Id={rowId}({rowName}): TableId={tableId} 不存在");
continue;
}
if (artItemId < 0)
{
ws.Cells[row, resCol].Value = $"{table.TableName},";
migratedCount++;
continue;
}
var artItem = table.Items.FirstOrDefault(x => x.Id == artItemId);
if (artItem == null)
{
errorCount++;
errors.Add($"Row {row} Id={rowId}({rowName}): Table={table.TableName} 中无 ItemId={artItemId}");
continue;
}
ws.Cells[row, resCol].Value = $"{table.TableName},{artItem.Name}";
migratedCount++;
}
package.Save();
}
// 5. 报告结果
string msg = $"迁移完成!\n\n" +
$"成功: {migratedCount} 条\n" +
$"已是新格式(跳过): {skippedCount} 条\n" +
$"错误: {errorCount} 条\n\n" +
$"备份文件: {backupPath}";
if (errorCount > 0)
{
msg += $"\n\n错误详情:\n{string.Join("\n", errors)}";
}
EditorUtility.DisplayDialog("Res迁移结果", msg, "确定");
Debug.Log(msg);
}
}
}

View File

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

View File

@ -8,6 +8,7 @@ using UnityEditor;
using UnityEngine;
using OfficeOpenXml;
using ArtResource;
using DesignTools.Common;
using Debug = UnityEngine.Debug;
namespace DesignTools.Scene
@ -101,11 +102,15 @@ namespace DesignTools.Scene
// 道具奖励临时编辑状态按数据ID存储
private Dictionary<int, int> tempItemRewardTypeIndex = new Dictionary<int, int>();
private Dictionary<int, int> tempItemRewardInputId = 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>();
// 所有IType列表动态生成
private List<int> allItemTypes = new List<int>();
[MenuItem("策划工具/场景/场景进度奖励")]
public static void ShowWindow()
@ -484,6 +489,9 @@ namespace DesignTools.Scene
}
Debug.Log($"加载了 {itemDict.Count} 个Item数据");
// 生成所有IType列表
allItemTypes = itemDict.Values.Select(x => x.IType).Distinct().OrderBy(x => x).ToList();
}
/// <summary>
@ -752,12 +760,14 @@ namespace DesignTools.Scene
/// <summary>
/// 绘制道具奖励编辑器Item/Emit/Reward
/// 使用输入框+查找按钮+弹窗选择的方式,支持所有类型
/// </summary>
private void DrawItemRewardEditor(IndoorProgressData data)
{
// 解析当前数据
var itemReward = ParseItemReward(data.Item);
EditorGUILayout.BeginVertical();
EditorGUILayout.BeginHorizontal();
// 选择IType
@ -770,7 +780,7 @@ namespace DesignTools.Scene
typeValues.Add(kvp.Key);
}
// 初始化临时状态:如果有数据就用数据的类型,如果没数据就用临时保存的选择
// 初始化临时状态
if (!tempItemRewardTypeIndex.ContainsKey(data.Id))
{
if (itemReward.HasValue)
@ -780,12 +790,11 @@ namespace DesignTools.Scene
}
else
{
tempItemRewardTypeIndex[data.Id] = 0; // 默认"无"
tempItemRewardTypeIndex[data.Id] = 0;
}
}
else if (itemReward.HasValue)
{
// 如果有数据,同步临时状态
int itemType = GetItemType(itemReward.Value.Id);
int typeIndex = typeValues.IndexOf(itemType);
if (typeIndex >= 0)
@ -794,6 +803,12 @@ namespace DesignTools.Scene
}
}
// 初始化输入ID缓存
if (!tempItemRewardInputId.ContainsKey(data.Id))
{
tempItemRewardInputId[data.Id] = itemReward.HasValue ? itemReward.Value.Id : 0;
}
int currentTypeIndex = tempItemRewardTypeIndex[data.Id];
if (currentTypeIndex < 0) currentTypeIndex = 0;
@ -814,6 +829,7 @@ namespace DesignTools.Scene
data.Item = "";
data.Emit = "";
data.Reward = "";
tempItemRewardInputId[data.Id] = 0;
}
}
else
@ -827,110 +843,115 @@ namespace DesignTools.Scene
data.Emit = "";
data.Reward = "";
itemReward = null;
tempItemRewardInputId[data.Id] = 0;
}
// 根据类型筛选Item
var itemsOfType = itemDict.Values.Where(x => x.IType == selectedType).ToList();
// 统一使用输入框+查找按钮方式
int inputId = tempItemRewardInputId[data.Id];
if (itemsOfType.Count > 0)
EditorGUILayout.LabelField("ID:", GUILayout.Width(25));
int newInputId = EditorGUILayout.IntField(inputId, GUILayout.Width(70));
if (newInputId != inputId)
{
// IType=100(棋子)使用输入框+刷新按钮,其他使用下拉菜单
if (selectedType == 100)
tempItemRewardInputId[data.Id] = newInputId;
if (itemDict.ContainsKey(newInputId))
{
// 输入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));
}
UpdateItemReward(data, newInputId, num);
// 同步类型选择
int newItemType = GetItemType(newInputId);
int newTypeIdx = typeValues.IndexOf(newItemType);
if (newTypeIdx >= 0) tempItemRewardTypeIndex[data.Id] = newTypeIdx;
}
else
}
// 查找按钮
GUI.backgroundColor = new Color(0.75f, 0.9f, 1f);
if (GUILayout.Button("查找", GUILayout.Width(45), GUILayout.Height(18)))
{
int capturedDataId = data.Id;
int currentType = selectedType;
EditorApplication.delayCall += () => OpenItemPickerForReward(capturedDataId, currentType);
GUI.backgroundColor = Color.white;
GUIUtility.ExitGUI();
}
GUI.backgroundColor = Color.white;
// 数量
EditorGUILayout.LabelField("x", GUILayout.Width(10));
int currentNum = itemReward.HasValue ? itemReward.Value.Num : 1;
EditorGUI.BeginChangeCheck();
currentNum = EditorGUILayout.IntField(currentNum, GUILayout.Width(40));
if (EditorGUI.EndChangeCheck())
{
int currentId = tempItemRewardInputId[data.Id];
if (currentId > 0 && itemDict.ContainsKey(currentId))
{
// 其他类型使用下拉菜单
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);
}
UpdateItemReward(data, currentId, currentNum);
}
}
// 显示预览图
int previewId = tempItemRewardInputId[data.Id];
if (previewId > 0 && itemDict.TryGetValue(previewId, out var previewItem))
{
DrawItemPreview(previewItem);
}
else
{
EditorGUILayout.LabelField("该类型无可用Item", GUILayout.Width(150));
EditorGUILayout.LabelField("无", GUILayout.Width(50));
}
}
EditorGUILayout.EndHorizontal();
// 显示当前道具名称
if (itemReward.HasValue && itemDict.TryGetValue(itemReward.Value.Id, out var currentItem))
{
EditorGUILayout.LabelField($" 当前: {currentItem.Name} ({currentItem.Id})", EditorStyles.miniLabel);
}
EditorGUILayout.EndVertical();
}
/// <summary>
/// 打开Item选择弹窗道具奖励用
/// </summary>
private void OpenItemPickerForReward(int dataId, int currentType)
{
var data = allProgressDataList.FirstOrDefault(x => x.Id == dataId);
if (data == null) data = filteredProgressDataList.FirstOrDefault(x => x.Id == dataId);
if (data == null) return;
var itemReward = ParseItemReward(data.Item);
int selectedItemId = itemReward.HasValue ? itemReward.Value.Id : 0;
DesignToolItemPickerWindow.ShowWindow(
itemDict.Values
.Select(x => new DesignToolItemPickerItem { Id = x.Id, Name = x.Name, IType = x.IType })
.ToList(),
allItemTypes,
ITEM_TYPES,
currentType,
selectedItemId,
position,
pickedId =>
{
if (itemDict.ContainsKey(pickedId))
{
int num = itemReward.HasValue ? itemReward.Value.Num : 1;
UpdateItemReward(data, pickedId, num);
tempItemRewardInputId[dataId] = pickedId;
// 同步类型
int pickedType = GetItemType(pickedId);
var typeValues2 = new List<int> { -1 };
foreach (var kvp in ITEM_TYPES.OrderBy(x => x.Key))
typeValues2.Add(kvp.Key);
int typeIdx = typeValues2.IndexOf(pickedType);
if (typeIdx >= 0) tempItemRewardTypeIndex[dataId] = typeIdx;
Repaint();
}
});
}
/// <summary>
@ -1024,26 +1045,13 @@ namespace DesignTools.Scene
/// <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;
@ -1053,116 +1061,92 @@ namespace DesignTools.Scene
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)
// 输入ID
EditorGUILayout.LabelField("ID:", GUILayout.Width(25));
int inputId = tempAreaRewardItemId[data.Id];
int newInputId = EditorGUILayout.IntField(inputId, GUILayout.Width(70));
if (newInputId != inputId)
{
tempAreaRewardTypeIndex[data.Id] = newTypeIndex;
tempAreaRewardItemId[data.Id] = 0; // 重置ItemId
tempAreaRewardItemId[data.Id] = newInputId;
}
if (newTypeIndex > 0)
// 查找按钮
GUI.backgroundColor = new Color(0.75f, 0.9f, 1f);
if (GUILayout.Button("查找", GUILayout.Width(45), GUILayout.Height(18)))
{
int selectedType = typeValues[newTypeIndex];
// 根据类型筛选Item
var itemsOfType = itemDict.Values.Where(x => x.IType == selectedType).ToList();
if (itemsOfType.Count > 0)
int capturedDataId = data.Id;
EditorApplication.delayCall += () => OpenItemPickerForAreaReward(capturedDataId);
GUI.backgroundColor = Color.white;
GUIUtility.ExitGUI();
}
GUI.backgroundColor = Color.white;
// 显示道具名称
int currentInputId = tempAreaRewardItemId[data.Id];
if (currentInputId > 0 && itemDict.TryGetValue(currentInputId, out var inputItem))
{
EditorGUILayout.LabelField($"{inputItem.Name}", EditorStyles.miniLabel, GUILayout.Width(100));
}
else if (currentInputId > 0)
{
Color oldColor = GUI.color;
GUI.color = new Color(1f, 0.35f, 0.35f);
EditorGUILayout.LabelField("未找到", EditorStyles.miniLabel, GUILayout.Width(100));
GUI.color = oldColor;
}
// 数量
EditorGUILayout.LabelField("x", GUILayout.Width(10));
int num = tempAreaRewardNum[data.Id];
num = EditorGUILayout.IntField(num, GUILayout.Width(40));
tempAreaRewardNum[data.Id] = num;
// 添加按钮
GUI.backgroundColor = Color.green;
if (GUILayout.Button("+添加", GUILayout.Width(50)))
{
int addId = tempAreaRewardItemId[data.Id];
if (addId > 0 && itemDict.ContainsKey(addId))
{
// 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);
}
}
areaRewards.Add(new ItemReward { Id = addId, Num = tempAreaRewardNum[data.Id] });
UpdateAreaReward(data, areaRewards);
// 重置
tempAreaRewardItemId[data.Id] = 0;
tempAreaRewardNum[data.Id] = 1;
}
else
{
EditorGUILayout.LabelField("该类型无可用Item", GUILayout.Width(120));
EditorUtility.DisplayDialog("错误", $"ID {addId} 不存在于Item表中", "确定");
}
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
}
/// <summary>
/// 打开Item选择弹窗区域奖励用
/// </summary>
private void OpenItemPickerForAreaReward(int dataId)
{
int currentItemId = tempAreaRewardItemId.ContainsKey(dataId) ? tempAreaRewardItemId[dataId] : 0;
int currentType = currentItemId > 0 && itemDict.ContainsKey(currentItemId) ? itemDict[currentItemId].IType : -1;
DesignToolItemPickerWindow.ShowWindow(
itemDict.Values
.Select(x => new DesignToolItemPickerItem { Id = x.Id, Name = x.Name, IType = x.IType })
.ToList(),
allItemTypes,
ITEM_TYPES,
currentType,
currentItemId,
position,
pickedId =>
{
tempAreaRewardItemId[dataId] = pickedId;
Repaint();
});
}
/// <summary>
/// 解析Item奖励
@ -1349,7 +1333,7 @@ namespace DesignTools.Scene
return;
}
// 解析Res格式: "tableId,itemId"
// 解析Res格式: "TableName,ItemName"
var parts = item.Res.Split(',');
if (parts.Length != 2)
{
@ -1357,18 +1341,24 @@ namespace DesignTools.Scene
return;
}
if (!int.TryParse(parts[0], out int tableId)) return;
if (!int.TryParse(parts[1], out int itemId)) return;
string tableName = parts[0];
string artItemName = parts[1];
if (string.IsNullOrEmpty(tableName) || string.IsNullOrEmpty(artItemName))
{
EditorGUILayout.LabelField("无图", GUILayout.Width(50));
return;
}
// 查找对应的ArtTableSO
var artTable = allArtTables.FirstOrDefault(x => x.TableId == tableId);
var artTable = allArtTables.FirstOrDefault(x => x.TableName == tableName);
if (artTable == null)
{
EditorGUILayout.LabelField("无图", GUILayout.Width(50));
return;
}
var artItem = artTable.Items.FirstOrDefault(x => x.Id == itemId);
var artItem = artTable.Items.FirstOrDefault(x => x.Name == artItemName);
if (artItem == null || artItem.Sprite == null)
{
EditorGUILayout.LabelField("无图", GUILayout.Width(50));