策划项目提交

This commit is contained in:
zhang hongbo 2026-02-27 19:20:28 +08:00
parent 02a81ee658
commit 45eee6799d
19 changed files with 2956 additions and 1 deletions

Binary file not shown.

View File

@ -0,0 +1,118 @@
// 此文件由 ThriftIntegratedPipeline 自动生成,请勿手动修改
// 配置类: AmbientData
// 数据类: AmbientDataItem
using UnityEngine;
using Byway.Config;
using Byway.Thrift.Data;
using UnityGameFramework.Runtime;
namespace CrazyMaple
{
/// <summary>
/// AmbientData 数据行
/// </summary>
public class DRAmbientData : DataRowBase
{
private AmbientDataItem _configData;
/// <summary>
/// 唯一标识
/// </summary>
public override int Id
{
get
{
return _configData?.Id ?? 0;
}
}
/// <summary>
/// AreaId
/// </summary>
public int AreaId
{
get
{
return _configData?.AreaId ?? 0;
}
}
/// <summary>
/// SoundId
/// </summary>
public int SoundId
{
get
{
return _configData?.SoundId ?? 0;
}
}
/// <summary>
/// 从配置加载数据(优先使用传入的配置实例)
/// </summary>
public void LoadFromConfig(int id, AmbientData config = null)
{
if (config == null)
{
config = ConfigManager.Instance.GetConfig<AmbientData>();
}
if (config?.Ambientdatas != null)
{
config.Ambientdatas.TryGetValue(id, out _configData);
}
}
/// <summary>
/// 直接设置配置数据(性能优化:跳过字典查询)
/// </summary>
public void SetConfigData(AmbientDataItem configData)
{
_configData = configData;
}
/// <summary>
/// 解析数据行(优化:使用 userData 传入的配置实例,避免重复调用 GetConfig
/// </summary>
public override bool ParseDataRow(string dataRowString, object userData)
{
int id = 0;
if (!int.TryParse(dataRowString, out id))
{
return false;
}
// 性能优化:尝试从 userData 获取配置字典,直接获取 Item
if (userData is System.Collections.Generic.Dictionary<string, object> userDataDict)
{
// 优先尝试从缓存的字典直接获取 Item最快
if (userDataDict.TryGetValue("ConfigDict", out object dictObj))
{
var dict = dictObj as System.Collections.Generic.Dictionary<int, AmbientDataItem>;
if (dict != null && dict.TryGetValue(id, out var item))
{
_configData = item;
return true;
}
}
// 备选方案:从配置实例获取
if (userDataDict.TryGetValue("ConfigInstance", out object configObj))
{
var config = configObj as AmbientData;
if (config != null)
{
LoadFromConfig(id, config);
return _configData != null;
}
}
}
// 兜底方案:直接查询(最慢)
LoadFromConfig(id);
return _configData != null;
}
}
}

View File

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

View File

@ -0,0 +1,515 @@
using System;
using System.Diagnostics;
using System.IO;
using UnityEditor;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace DesignTools
{
/// <summary>
/// 策划工具编辑器基类
/// 提供通用的Docs路径管理、Git操作、UI框架等功能
/// </summary>
public abstract class BaseDesignToolEditor : EditorWindow
{
#region
/// <summary>
/// Design_SubModule相对路径
/// </summary>
protected const string DESIGN_SUBMODULE_PATH = "Assets/Design_SubModule";
#endregion
#region
/// <summary>
/// Docs项目根目录路径
/// </summary>
protected string docsRootPath = "";
/// <summary>
/// 数据是否已加载
/// </summary>
protected bool isDataLoaded = false;
/// <summary>
/// 滚动位置
/// </summary>
protected Vector2 scrollPosition;
#endregion
#region
/// <summary>
/// 获取EditorPrefs保存路径的Key
/// 子类需要返回唯一的Key例如: "ItemConfigEditor_DocsPath"
/// </summary>
protected abstract string GetDocsPathPrefKey();
/// <summary>
/// 获取工具窗口标题
/// </summary>
protected abstract string GetWindowTitle();
/// <summary>
/// 加载具体的配置数据
/// 在Git检查和更新完成后调用
/// </summary>
protected abstract void LoadConfigData();
/// <summary>
/// 绘制数据编辑器UI
/// 在数据加载完成后调用
/// </summary>
protected abstract void DrawDataEditor();
/// <summary>
/// 获取工具窗口的最小尺寸
/// 默认为 (1000, 600)
/// </summary>
protected virtual Vector2 GetMinWindowSize()
{
return new Vector2(1000, 600);
}
/// <summary>
/// 是否需要检查Design_SubModule分支
/// 默认为true子类可以覆盖
/// </summary>
protected virtual bool NeedCheckDesignSubModuleBranch()
{
return true;
}
/// <summary>
/// 获取Docs配置路径相对于Docs根目录的路径
/// 默认为 "config",子类可以覆盖
/// </summary>
protected virtual string GetDocsConfigPath()
{
return "config";
}
/// <summary>
/// 是否显示底部操作按钮(保存、重新加载等)
/// 默认为true
/// </summary>
protected virtual bool ShowBottomButtons()
{
return true;
}
/// <summary>
/// 保存数据到Excel
/// 子类可以覆盖此方法实现具体的保存逻辑
/// </summary>
protected virtual void SaveDataToExcel()
{
EditorUtility.DisplayDialog("提示", "此工具未实现保存功能", "确定");
}
#endregion
#region Unity生命周期
protected virtual void OnEnable()
{
// 读取上次保存的路径
string prefKey = GetDocsPathPrefKey();
if (EditorPrefs.HasKey(prefKey))
{
docsRootPath = EditorPrefs.GetString(prefKey);
}
}
protected virtual void OnGUI()
{
// 绘制工具栏
DrawToolbar();
EditorGUILayout.Space(5);
// 绘制Docs路径选择区域
DrawDocsPathSelector();
EditorGUILayout.Space(5);
// 绘制数据编辑区域或提示信息
if (isDataLoaded)
{
DrawDataEditor();
// 绘制底部操作按钮
if (ShowBottomButtons())
{
DrawBottomButtons();
}
}
else
{
EditorGUILayout.HelpBox("请先选择Docs根目录并加载配置数据", MessageType.Info);
}
}
#endregion
#region UI绘制方法
/// <summary>
/// 绘制工具栏
/// </summary>
protected virtual void DrawToolbar()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
EditorGUILayout.LabelField(GetWindowTitle(), EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
/// <summary>
/// 绘制Docs路径选择区域
/// </summary>
protected virtual void DrawDocsPathSelector()
{
EditorGUILayout.BeginVertical("box");
EditorGUILayout.LabelField("Docs项目根目录", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
docsRootPath = EditorGUILayout.TextField("路径", docsRootPath);
if (GUILayout.Button("选择文件夹", GUILayout.Width(100)))
{
SelectDocsPath();
}
EditorGUILayout.EndHorizontal();
if (GUILayout.Button("加载配置数据", GUILayout.Height(30)))
{
LoadData();
}
EditorGUILayout.EndVertical();
}
/// <summary>
/// 绘制底部操作按钮
/// </summary>
protected virtual void DrawBottomButtons()
{
EditorGUILayout.Space(5);
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("保存到Excel", GUILayout.Height(30), GUILayout.Width(150)))
{
SaveDataToExcel();
}
if (GUILayout.Button("重新加载", GUILayout.Height(30), GUILayout.Width(150)))
{
LoadData();
}
EditorGUILayout.EndHorizontal();
}
#endregion
#region
/// <summary>
/// 选择Docs路径
/// </summary>
protected virtual void SelectDocsPath()
{
string selectedPath = EditorUtility.OpenFolderPanel("选择Docs项目根目录", "", "");
if (!string.IsNullOrEmpty(selectedPath))
{
docsRootPath = selectedPath;
EditorPrefs.SetString(GetDocsPathPrefKey(), docsRootPath);
}
}
/// <summary>
/// 加载数据(最终方法,不可覆盖)
/// </summary>
protected void LoadData()
{
try
{
// 1. 校验路径
if (!ValidateDocsPath())
{
return;
}
// 2. 检查Docs是否为Git仓库并更新
if (!CheckAndUpdateDocsRepository())
{
return;
}
// 3. 检查Design_SubModule分支如果需要
if (NeedCheckDesignSubModuleBranch())
{
if (!CheckAndSwitchDesignSubModuleBranch())
{
return;
}
}
// 4. 调用子类的具体加载逻辑
LoadConfigData();
// 5. 设置数据加载完成标志
isDataLoaded = true;
// 6. 显示成功提示
EditorUtility.DisplayDialog("成功", "配置数据加载成功!", "确定");
}
catch (Exception e)
{
EditorUtility.DisplayDialog("错误", $"加载数据失败: {e.Message}\n{e.StackTrace}", "确定");
isDataLoaded = false;
}
}
/// <summary>
/// 校验Docs路径
/// </summary>
protected virtual bool ValidateDocsPath()
{
if (string.IsNullOrEmpty(docsRootPath) || !Directory.Exists(docsRootPath))
{
EditorUtility.DisplayDialog("错误", "请选择有效的Docs根目录", "确定");
return false;
}
return true;
}
#endregion
#region Git操作方法
/// <summary>
/// 执行Git命令
/// </summary>
/// <param name="workingDirectory">工作目录</param>
/// <param name="arguments">Git命令参数</param>
/// <param name="success">是否执行成功</param>
/// <returns>命令输出或错误信息</returns>
protected string ExecuteGitCommand(string workingDirectory, string arguments, out bool success)
{
try
{
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "git",
Arguments = arguments,
WorkingDirectory = workingDirectory,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using (Process process = Process.Start(startInfo))
{
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();
process.WaitForExit();
success = process.ExitCode == 0;
return success ? output : error;
}
}
catch (Exception e)
{
success = false;
return $"执行Git命令失败: {e.Message}";
}
}
/// <summary>
/// 检查并更新Docs仓库
/// </summary>
protected virtual bool CheckAndUpdateDocsRepository()
{
// 1. 检查是否为Git仓库
string gitPath = Path.Combine(docsRootPath, ".git");
if (!Directory.Exists(gitPath))
{
EditorUtility.DisplayDialog("错误",
$"Docs目录不是Git仓库\n路径: {docsRootPath}\n\n请确保Docs项目已正确克隆",
"确定");
return false;
}
// 2. 执行Git Fetch
EditorUtility.DisplayProgressBar("检查更新", "正在检查Docs仓库远程更新...", 0.3f);
string fetchResult = ExecuteGitCommand(docsRootPath, "fetch", out bool fetchSuccess);
if (!fetchSuccess)
{
EditorUtility.ClearProgressBar();
EditorUtility.DisplayDialog("Git Fetch失败",
$"无法检查远程更新:\n{fetchResult}\n\n请在SourceTree或GitHubDesktop中检查网络连接和仓库状态",
"确定");
return false;
}
// 3. 检查仓库状态
EditorUtility.DisplayProgressBar("检查更新", "正在检查是否有新提交...", 0.6f);
string statusResult = ExecuteGitCommand(docsRootPath, "status -uno", out bool statusSuccess);
if (!statusSuccess)
{
EditorUtility.ClearProgressBar();
EditorUtility.DisplayDialog("Git Status失败",
$"无法获取仓库状态:\n{statusResult}\n\n请在SourceTree或GitHubDesktop中检查仓库状态",
"确定");
return false;
}
// 4. 检查是否有未提交的更改
if (statusResult.Contains("Changes not staged") || statusResult.Contains("Changes to be committed"))
{
EditorUtility.ClearProgressBar();
bool proceed = EditorUtility.DisplayDialog("警告:有未提交的更改",
"Docs仓库中有未提交的更改这可能导致拉取时产生冲突。\n\n建议先提交或暂存这些更改。\n\n是否继续加载配置不推荐",
"继续(不推荐)", "取消,前往处理");
if (!proceed)
{
Debug.Log("请在SourceTree或GitHubDesktop中处理未提交的更改");
return false;
}
}
// 5. 如果远程有更新则执行Pull
if (statusResult.Contains("Your branch is behind"))
{
EditorUtility.DisplayProgressBar("更新中", "正在从远程拉取最新代码...", 0.8f);
string pullResult = ExecuteGitCommand(docsRootPath, "pull", out bool pullSuccess);
EditorUtility.ClearProgressBar();
if (!pullSuccess)
{
if (pullResult.Contains("CONFLICT") || pullResult.Contains("conflict"))
{
EditorUtility.DisplayDialog("拉取失败:存在冲突",
$"拉取远程更新时发生冲突:\n{pullResult}\n\n请在SourceTree或GitHubDesktop中解决冲突后再操作",
"确定");
}
else
{
EditorUtility.DisplayDialog("拉取失败",
$"无法拉取远程更新:\n{pullResult}\n\n请在SourceTree或GitHubDesktop中检查并解决问题",
"确定");
}
return false;
}
Debug.Log($"Docs仓库已更新到最新版本:\n{pullResult}");
EditorUtility.DisplayDialog("更新成功", "Docs仓库已更新到最新版本", "确定");
}
else
{
EditorUtility.ClearProgressBar();
Debug.Log("Docs仓库已是最新版本");
}
return true;
}
/// <summary>
/// 检查并切换Design_SubModule到main分支
/// </summary>
protected virtual bool CheckAndSwitchDesignSubModuleBranch()
{
string designSubModulePath = Path.Combine(Application.dataPath, "..", DESIGN_SUBMODULE_PATH);
designSubModulePath = Path.GetFullPath(designSubModulePath);
if (!Directory.Exists(designSubModulePath))
{
EditorUtility.DisplayDialog("错误",
$"Design_SubModule目录不存在:\n{designSubModulePath}\n\n请确保子模块已正确初始化",
"确定");
return false;
}
EditorUtility.DisplayProgressBar("检查分支", "正在检查Design_SubModule分支...", 0.5f);
string branchResult = ExecuteGitCommand(designSubModulePath, "branch --show-current", out bool branchSuccess);
if (!branchSuccess)
{
EditorUtility.ClearProgressBar();
EditorUtility.DisplayDialog("Git Branch失败",
$"无法获取当前分支:\n{branchResult}\n\n请在SourceTree或GitHubDesktop中检查Design_SubModule状态",
"确定");
return false;
}
string currentBranch = branchResult.Trim();
if (currentBranch != "main")
{
EditorUtility.DisplayProgressBar("切换分支", "正在切换到main分支...", 0.8f);
string checkoutResult = ExecuteGitCommand(designSubModulePath, "checkout main", out bool checkoutSuccess);
EditorUtility.ClearProgressBar();
if (!checkoutSuccess)
{
EditorUtility.DisplayDialog("切换分支失败",
$"无法切换到main分支:\n{checkoutResult}\n\n当前分支: {currentBranch}\n\n请在SourceTree或GitHubDesktop中手动切换到main分支",
"确定");
return false;
}
Debug.Log($"Design_SubModule已从 {currentBranch} 切换到 main 分支");
}
else
{
EditorUtility.ClearProgressBar();
Debug.Log("Design_SubModule已在main分支");
}
return true;
}
#endregion
#region
/// <summary>
/// 获取Docs配置文件的完整路径
/// </summary>
/// <param name="fileName">文件名(包含扩展名)</param>
/// <returns>完整路径</returns>
protected string GetDocsConfigFilePath(string fileName)
{
return Path.Combine(docsRootPath, GetDocsConfigPath(), fileName);
}
/// <summary>
/// 获取Docs根目录下的完整路径
/// </summary>
/// <param name="relativePath">相对路径</param>
/// <returns>完整路径</returns>
protected string GetDocsFullPath(string relativePath)
{
return Path.Combine(docsRootPath, relativePath);
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,322 @@
# BaseDesignToolEditor 使用说明
## 概述
`BaseDesignToolEditor` 是为策划工具统一设计的编辑器基类,封装了常见的功能,包括:
- ✅ Docs路径的选择和保存
- ✅ 自动检查并拉取Docs仓库的最新更新
- ✅ 检查是否有未提交的更改(提示但不强制)
- ✅ 自动切换Design_SubModule到main分支
- ✅ 统一的UI框架工具栏、路径选择、数据编辑区
- ✅ Git命令执行的封装
## 功能特点
### 1. 自动Git管理
- **自动Fetch**: 检查远程仓库更新
- **智能提醒**: 如果有未提交的更改,会提示用户但允许继续(不强制)
- **自动Pull**: 如果远程有更新,自动拉取
- **冲突检测**: 如果拉取时有冲突,会友好提示
### 2. 分支管理
- 自动检查Design_SubModule是否在main分支
- 如果不在main分支自动切换
### 3. 路径管理
- EditorPrefs自动保存和恢复用户上次选择的路径
- 提供便捷的路径选择对话框
### 4. 统一UI
- 工具栏显示工具名称
- 路径选择区域
- 加载按钮
- 数据编辑区域
## 使用方法
### 基本用法
创建一个新的Editor工具时继承 `BaseDesignToolEditor` 并实现必需的抽象方法:
```csharp
using UnityEditor;
using UnityEngine;
using DesignTools;
namespace DesignTools.YourCategory
{
public class YourConfigEditor : BaseDesignToolEditor
{
// 1. 定义MenuItem来显示窗口
[MenuItem("策划工具/你的分类/你的工具")]
public static void ShowWindow()
{
var window = GetWindow<YourConfigEditor>("你的工具");
window.minSize = window.GetMinWindowSize(); // 可选:使用默认大小或自定义
window.Show();
}
// 2. 实现必需的抽象方法
/// <summary>
/// 返回保存路径的EditorPrefs键需要唯一
/// </summary>
protected override string GetDocsPathPrefKey()
{
return "YourConfigEditor_DocsPath";
}
/// <summary>
/// 返回窗口标题
/// </summary>
protected override string GetWindowTitle()
{
return "你的工具配置工具";
}
/// <summary>
/// 加载具体的配置数据
/// 在Git检查和更新完成后调用
/// </summary>
protected override void LoadConfigData()
{
// 使用 GetDocsConfigFilePath() 获取Excel文件路径
string excelPath = GetDocsConfigFilePath("YourExcel.xlsx");
// 加载你的Excel数据
// LoadYourExcelData(excelPath);
// 加载其他必要数据
// ...
}
/// <summary>
/// 绘制数据编辑器UI
/// </summary>
protected override void DrawDataEditor()
{
// 使用 scrollPosition 管理滚动视图
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
// 绘制你的数据编辑UI
// ...
EditorGUILayout.EndScrollView();
}
}
}
```
### 可选的虚方法覆盖
#### 1. 自定义窗口尺寸
```csharp
protected override Vector2 GetMinWindowSize()
{
return new Vector2(1200, 700); // 自定义最小窗口大小
}
```
#### 2. 禁用Design_SubModule分支检查
```csharp
protected override bool NeedCheckDesignSubModuleBranch()
{
return false; // 如果不需要检查分支返回false
}
```
#### 3. 自定义Docs配置路径
```csharp
protected override string GetDocsConfigPath()
{
return "custom/config/path"; // 默认是 "config"
}
```
#### 4. 自定义路径验证
```csharp
protected override bool ValidateDocsPath()
{
// 先调用基类的验证
if (!base.ValidateDocsPath())
{
return false;
}
// 添加你自己的验证逻辑
// ...
return true;
}
```
## 完整示例
以下是一个完整的示例,展示如何使用基类创建一个简单的配置工具:
```csharp
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using OfficeOpenXml;
using DesignTools;
namespace DesignTools.Examples
{
public class SimpleConfigEditor : BaseDesignToolEditor
{
private List<string> dataList = new List<string>();
[MenuItem("策划工具/示例/简单配置工具")]
public static void ShowWindow()
{
var window = GetWindow<SimpleConfigEditor>("简单配置工具");
window.minSize = window.GetMinWindowSize();
window.Show();
}
protected override string GetDocsPathPrefKey()
{
return "SimpleConfigEditor_DocsPath";
}
protected override string GetWindowTitle()
{
return "简单配置工具";
}
protected override void LoadConfigData()
{
// 清空旧数据
dataList.Clear();
// 获取Excel文件路径
string excelPath = GetDocsConfigFilePath("SimpleConfig.xlsx");
// 使用EPPlus加载Excel
using (var package = new ExcelPackage(new System.IO.FileInfo(excelPath)))
{
var worksheet = package.Workbook.Worksheets[0];
int rowCount = worksheet.Dimension.Rows;
for (int row = 2; row <= rowCount; row++)
{
string data = worksheet.Cells[row, 1].Text;
if (!string.IsNullOrEmpty(data))
{
dataList.Add(data);
}
}
}
Debug.Log($"加载了 {dataList.Count} 条数据");
}
protected override void DrawDataEditor()
{
EditorGUILayout.BeginVertical("box");
EditorGUILayout.LabelField($"共有 {dataList.Count} 条数据", EditorStyles.boldLabel);
EditorGUILayout.EndVertical();
EditorGUILayout.Space(5);
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
foreach (var data in dataList)
{
EditorGUILayout.LabelField(data);
}
EditorGUILayout.EndScrollView();
}
}
}
```
## 基类提供的受保护字段
可以在子类中直接使用以下字段:
```csharp
protected string docsRootPath; // Docs根目录路径
protected bool isDataLoaded; // 数据是否已加载
protected Vector2 scrollPosition; // 滚动位置
```
## 基类提供的受保护常量
```csharp
protected const string DESIGN_SUBMODULE_PATH = "Assets/Design_SubModule";
```
## 基类提供的工具方法
### 1. 获取配置文件路径
```csharp
// 获取 Docs/config/YourFile.xlsx 的完整路径
string path = GetDocsConfigFilePath("YourFile.xlsx");
```
### 2. 获取Docs下的任意路径
```csharp
// 获取 Docs/custom/path/file.txt 的完整路径
string path = GetDocsFullPath("custom/path/file.txt");
```
### 3. 执行Git命令
```csharp
string result = ExecuteGitCommand(
workingDirectory: docsRootPath,
arguments: "log -1 --oneline",
out bool success
);
if (success)
{
Debug.Log($"Git命令执行成功: {result}");
}
```
## 注意事项
1. **唯一的PrefsKey**: 确保每个编辑器工具使用唯一的 `GetDocsPathPrefKey()` 返回值
2. **异常处理**: 基类已经处理了 `LoadData()` 的异常,子类在 `LoadConfigData()` 中只需关注具体逻辑
3. **进度条**: Git操作时会自动显示进度条无需在子类中处理
4. **EPPlus许可证**: 如果使用EPPlus记得在子类的 `OnEnable()` 中设置许可证上下文
```csharp
protected override void OnEnable()
{
base.OnEnable(); // 调用基类的OnEnable
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
}
```
## 迁移现有工具
如果要将现有的Editor工具迁移到使用基类步骤如下
1. 将类声明改为继承 `BaseDesignToolEditor`
2. 删除重复的字段定义(如 `docsRootPath`、`isDataLoaded` 等)
3. 删除 `ExecuteGitCommand`、`CheckAndUpdateDocsRepository` 等方法
4. 实现必需的抽象方法
5. 修改 `LoadData()` 逻辑,将具体加载逻辑移到 `LoadConfigData()`
6. 修改 `OnGUI()`,删除路径选择和工具栏部分,只保留 `DrawDataEditor()` 的实现
## 未来扩展
基类还可以继续扩展以下功能:
- 语言表加载的通用封装
- Excel读取的辅助方法
- ArtTableSO加载的通用方法
- 数据验证的通用框架
- 数据保存和导出的通用方法
如有需要,可以继续向基类添加通用功能。

View File

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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 948d9a14622d03a4884178645ac059ab
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,756 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using OfficeOpenXml;
using Debug = UnityEngine.Debug;
namespace DesignTools
{
/// <summary>
/// 环境音配置Editor工具
/// 读取Docs/config/Sound.xlsx的Ambient Sheet和Sound Sheet编辑环境音配置和环境音属性
/// </summary>
public class AmbientSoundConfigEditor : BaseDesignToolEditor
{
private const string SOUND_EXCEL_NAME = "Sound.xlsx";
private const string AMBIENT_SHEET_NAME = "Ambient";
private const string SOUND_SHEET_NAME = "Sound";
private const string LANGUAGE_EXCEL_NAME = "AllLanguage.xlsx";
private const string LANGUAGE_SHEET_NAME = "client";
// Ambient表格列索引
private const int COL_ID = 1;
private const int COL_AREA_ID = 2;
private const int COL_SOUND_ID = 3;
// Sound表格列索引
private const int COL_SOUND_ID_IN_SOUND = 1;
private const int COL_COMMENT = 2;
private const int COL_ASSET_NAME = 3;
private const int COL_PRIORITY = 4;
private const int COL_LOOP = 5;
private const int COL_VOLUME = 6;
private const int COL_SPATIAL_BLEND = 7;
private const int COL_MAX_DISTANCE = 8;
// 固定的环境音ID列表
private static readonly int[] AMBIENT_SOUND_IDS = { 10172, 10173, 10174, 10175, 10176, 10177, 10178, 10179 };
private List<AmbientSoundData> ambientDataList = new List<AmbientSoundData>();
private Dictionary<int, SoundData> soundDataDict = new Dictionary<int, SoundData>(); // 环境音的Sound数据
private Dictionary<string, Dictionary<string, string>> languageDict = new Dictionary<string, Dictionary<string, string>>();
// UI状态
private Vector2 ambientScrollPosition;
private Vector2 soundScrollPosition;
private int selectedTab = 0; // 0=场景关联, 1=环境音编辑
[MenuItem("策划工具/声音/环境音配置")]
public static void ShowWindow()
{
var window = GetWindow<AmbientSoundConfigEditor>("环境音配置");
window.minSize = window.GetMinWindowSize();
window.Show();
}
#region
protected override string GetDocsPathPrefKey()
{
return "AmbientSoundConfigEditor_DocsPath";
}
protected override string GetWindowTitle()
{
return "环境音配置工具";
}
protected override Vector2 GetMinWindowSize()
{
return new Vector2(1200, 700);
}
protected override void LoadConfigData()
{
// 1. 加载语言表
LoadLanguageData();
// 2. 加载Sound表中的环境音数据
LoadSoundData();
// 3. 加载Ambient表
LoadAmbientExcel();
}
protected override void DrawDataEditor()
{
// 标签页切换
EditorGUILayout.BeginHorizontal();
string[] tabNames = { "场景环境音关联", "环境音属性编辑" };
selectedTab = GUILayout.Toolbar(selectedTab, tabNames, GUILayout.Height(30));
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(5);
// 根据选中的标签页显示不同内容
if (selectedTab == 0)
{
DrawAmbientDataTable();
}
else
{
DrawSoundDataTable();
}
}
protected override bool ShowBottomButtons()
{
return false; // 使用顶部保存按钮
}
protected override void SaveDataToExcel()
{
SaveToExcel();
}
#endregion
#region
/// <summary>
/// 加载语言表
/// </summary>
private void LoadLanguageData()
{
languageDict.Clear();
string languageExcelPath = GetDocsConfigFilePath(LANGUAGE_EXCEL_NAME);
if (!File.Exists(languageExcelPath))
{
Debug.LogWarning($"未找到语言表: {languageExcelPath}");
return;
}
// ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
using (var package = new ExcelPackage(new FileInfo(languageExcelPath)))
{
var worksheet = package.Workbook.Worksheets[LANGUAGE_SHEET_NAME];
if (worksheet == null)
{
Debug.LogWarning($"未找到Sheet: {LANGUAGE_SHEET_NAME}");
return;
}
int rowCount = worksheet.Dimension?.Rows ?? 0;
if (rowCount < 2)
{
return;
}
// 第一行是表头,找到各列的索引
int keyColIndex = -1;
int zhCNColIndex = -1;
for (int col = 1; col <= worksheet.Dimension.Columns; col++)
{
string header = worksheet.Cells[1, col].Text.Trim();
if (header.Equals("key", StringComparison.OrdinalIgnoreCase) ||
header.Equals("Key", StringComparison.OrdinalIgnoreCase))
{
keyColIndex = col;
}
else if (header == "zh_CN")
{
zhCNColIndex = col;
}
}
if (keyColIndex == -1 || zhCNColIndex == -1)
{
Debug.LogWarning($"语言表格式错误未找到key或zh_CN列已扫描 {worksheet.Dimension.Columns} 列)");
return;
}
// 从第3行开始读取数据第1行是字段名第2行是中文说明
for (int row = 3; row <= rowCount; row++)
{
string key = worksheet.Cells[row, keyColIndex].Text.Trim();
string zhCN = worksheet.Cells[row, zhCNColIndex].Text.Trim();
if (!string.IsNullOrEmpty(key))
{
if (!languageDict.ContainsKey(key))
{
languageDict[key] = new Dictionary<string, string>();
}
languageDict[key]["zh_CN"] = zhCN;
}
}
}
Debug.Log($"加载了 {languageDict.Count} 条语言数据");
}
/// <summary>
/// 加载Sound表中的环境音数据
/// </summary>
private void LoadSoundData()
{
soundDataDict.Clear();
string excelPath = GetDocsConfigFilePath(SOUND_EXCEL_NAME);
if (!File.Exists(excelPath))
{
EditorUtility.DisplayDialog("错误",
$"未找到Sound.xlsx\n路径: {excelPath}",
"确定");
return;
}
// ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
using (var package = new ExcelPackage(new FileInfo(excelPath)))
{
var worksheet = package.Workbook.Worksheets[SOUND_SHEET_NAME];
if (worksheet == null)
{
EditorUtility.DisplayDialog("错误",
$"未找到Sheet: {SOUND_SHEET_NAME}",
"确定");
return;
}
int rowCount = worksheet.Dimension?.Rows ?? 0;
// 从第3行开始读取第1行是字段名第2行是中文说明
for (int row = 3; row <= rowCount; row++)
{
string idText = worksheet.Cells[row, COL_SOUND_ID_IN_SOUND].Text;
if (string.IsNullOrEmpty(idText))
{
continue;
}
if (!int.TryParse(idText, out int soundId))
{
continue;
}
// 只加载固定的环境音ID
if (!AMBIENT_SOUND_IDS.Contains(soundId))
{
continue;
}
var soundData = new SoundData
{
Id = soundId,
Comment = worksheet.Cells[row, COL_COMMENT].Text,
AssetName = worksheet.Cells[row, COL_ASSET_NAME].Text,
Priority = ParseInt(worksheet.Cells[row, COL_PRIORITY].Text, 0),
Loop = ParseBool(worksheet.Cells[row, COL_LOOP].Text),
Volume = ParseFloat(worksheet.Cells[row, COL_VOLUME].Text, 1f),
SpatialBlend = ParseFloat(worksheet.Cells[row, COL_SPATIAL_BLEND].Text, 0f),
MaxDistance = ParseFloat(worksheet.Cells[row, COL_MAX_DISTANCE].Text, 100f),
RowIndex = row
};
soundDataDict[soundId] = soundData;
}
}
Debug.Log($"加载了 {soundDataDict.Count} 个环境音数据");
}
/// <summary>
/// 加载Ambient.xlsx
/// </summary>
private void LoadAmbientExcel()
{
ambientDataList.Clear();
string excelPath = GetDocsConfigFilePath(SOUND_EXCEL_NAME);
if (!File.Exists(excelPath))
{
EditorUtility.DisplayDialog("错误",
$"未找到Sound.xlsx\n路径: {excelPath}",
"确定");
return;
}
// ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
using (var package = new ExcelPackage(new FileInfo(excelPath)))
{
var worksheet = package.Workbook.Worksheets[AMBIENT_SHEET_NAME];
if (worksheet == null)
{
EditorUtility.DisplayDialog("错误",
$"未找到Sheet: {AMBIENT_SHEET_NAME}",
"确定");
return;
}
int rowCount = worksheet.Dimension?.Rows ?? 0;
// 从第3行开始读取第1行是字段名第2行是中文说明
for (int row = 3; row <= rowCount; row++)
{
string idText = worksheet.Cells[row, COL_ID].Text;
if (string.IsNullOrEmpty(idText))
{
continue;
}
if (!int.TryParse(idText, out int id))
{
continue;
}
int areaId = ParseInt(worksheet.Cells[row, COL_AREA_ID].Text, 0);
int soundId = ParseInt(worksheet.Cells[row, COL_SOUND_ID].Text, 0);
var ambientData = new AmbientSoundData
{
Id = id,
AreaId = areaId,
SoundId = soundId,
RowIndex = row
};
ambientDataList.Add(ambientData);
}
}
Debug.Log($"加载了 {ambientDataList.Count} 条环境音配置");
}
#endregion
#region UI绘制
/// <summary>
/// 绘制环境音数据表格
/// </summary>
private void DrawAmbientDataTable()
{
EditorGUILayout.BeginVertical("box");
// 顶部信息和保存按钮
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($"共 {ambientDataList.Count} 条环境音配置", EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
// 添加新行按钮
if (GUILayout.Button(" 添加新行", GUILayout.Height(25), GUILayout.Width(100)))
{
AddNewAmbientData();
}
// 保存按钮
GUI.backgroundColor = Color.green;
if (GUILayout.Button("💾 保存到Excel", GUILayout.Height(25), GUILayout.Width(120)))
{
SaveToExcel();
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(5);
// 表头
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
EditorGUILayout.LabelField("ID", EditorStyles.boldLabel, GUILayout.Width(50));
EditorGUILayout.LabelField("场景ID", EditorStyles.boldLabel, GUILayout.Width(60));
EditorGUILayout.LabelField("场景名称", EditorStyles.boldLabel, GUILayout.Width(200));
EditorGUILayout.LabelField("环境音", EditorStyles.boldLabel, GUILayout.Width(300));
EditorGUILayout.LabelField("操作", EditorStyles.boldLabel, GUILayout.Width(60));
EditorGUILayout.EndHorizontal();
// 数据行
ambientScrollPosition = EditorGUILayout.BeginScrollView(ambientScrollPosition, GUILayout.ExpandHeight(true));
for (int i = 0; i < ambientDataList.Count; i++)
{
var data = ambientDataList[i];
DrawAmbientDataRow(data, i);
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
/// <summary>
/// 绘制单行环境音数据
/// </summary>
private void DrawAmbientDataRow(AmbientSoundData data, int index)
{
EditorGUILayout.BeginHorizontal("box");
// ID只读自增
EditorGUILayout.LabelField(data.Id.ToString(), GUILayout.Width(50));
// 场景ID可编辑
int newAreaId = EditorGUILayout.IntField(data.AreaId, GUILayout.Width(60));
if (newAreaId != data.AreaId)
{
data.AreaId = newAreaId;
}
// 场景名称(显示 AreaId + 中文名)
string sceneName = GetSceneName(data.AreaId);
EditorGUILayout.LabelField(sceneName, GUILayout.Width(200));
// 环境音下拉选择
int currentIndex = GetSoundIndex(data.SoundId);
string[] soundOptions = GetSoundOptions();
int[] soundIds = AMBIENT_SOUND_IDS;
int newIndex = EditorGUILayout.Popup(currentIndex, soundOptions, GUILayout.Width(300));
if (newIndex != currentIndex && newIndex >= 0 && newIndex < soundIds.Length)
{
data.SoundId = soundIds[newIndex];
}
// 删除按钮
GUI.backgroundColor = new Color(1f, 0.3f, 0.3f);
if (GUILayout.Button("删除", GUILayout.Width(60), GUILayout.Height(20)))
{
if (EditorUtility.DisplayDialog("确认删除",
$"确定要删除ID为 {data.Id} 的环境音配置吗?",
"删除", "取消"))
{
ambientDataList.RemoveAt(index);
}
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
}
/// <summary>
/// 绘制环境音属性编辑表格
/// </summary>
private void DrawSoundDataTable()
{
EditorGUILayout.BeginVertical("box");
// 顶部信息和保存按钮
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($"共 {soundDataDict.Count} 个环境音", EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
// 保存按钮
GUI.backgroundColor = Color.green;
if (GUILayout.Button("💾 保存到Excel", GUILayout.Height(25), GUILayout.Width(120)))
{
SaveToExcel();
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(5);
// 表头
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
EditorGUILayout.LabelField("ID", EditorStyles.boldLabel, GUILayout.Width(60));
EditorGUILayout.LabelField("策划备注", EditorStyles.boldLabel, GUILayout.Width(120));
EditorGUILayout.LabelField("资源名称", EditorStyles.boldLabel, GUILayout.Width(180));
EditorGUILayout.LabelField("优先级", EditorStyles.boldLabel, GUILayout.Width(60));
EditorGUILayout.LabelField("循环", EditorStyles.boldLabel, GUILayout.Width(50));
EditorGUILayout.LabelField("音量", EditorStyles.boldLabel, GUILayout.Width(120));
EditorGUILayout.LabelField("空间混合", EditorStyles.boldLabel, GUILayout.Width(120));
EditorGUILayout.LabelField("最大距离", EditorStyles.boldLabel, GUILayout.Width(80));
EditorGUILayout.EndHorizontal();
// 数据行
soundScrollPosition = EditorGUILayout.BeginScrollView(soundScrollPosition, GUILayout.ExpandHeight(true));
// 按固定顺序显示环境音
foreach (int soundId in AMBIENT_SOUND_IDS)
{
if (soundDataDict.ContainsKey(soundId))
{
DrawSoundDataRow(soundDataDict[soundId]);
}
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
/// <summary>
/// 绘制单行环境音Sound数据
/// </summary>
private void DrawSoundDataRow(SoundData data)
{
EditorGUILayout.BeginHorizontal("box");
// ID只读
EditorGUILayout.LabelField(data.Id.ToString(), GUILayout.Width(60));
// 策划备注
string newComment = EditorGUILayout.TextField(data.Comment, GUILayout.Width(120));
if (newComment != data.Comment)
{
data.Comment = newComment;
}
// 资源名称
string newAssetName = EditorGUILayout.TextField(data.AssetName, GUILayout.Width(180));
if (newAssetName != data.AssetName)
{
data.AssetName = newAssetName;
}
// 优先级
int newPriority = EditorGUILayout.IntField(data.Priority, GUILayout.Width(60));
if (newPriority != data.Priority)
{
data.Priority = Mathf.Clamp(newPriority, -128, 128);
}
// 循环
bool newLoop = EditorGUILayout.Toggle(data.Loop, GUILayout.Width(50));
if (newLoop != data.Loop)
{
data.Loop = newLoop;
}
// 音量(带滑动条)
float newVolume = EditorGUILayout.Slider(data.Volume, 0f, 1f, GUILayout.Width(120));
if (!Mathf.Approximately(newVolume, data.Volume))
{
data.Volume = newVolume;
}
// 空间混合(带滑动条)
float newSpatialBlend = EditorGUILayout.Slider(data.SpatialBlend, 0f, 1f, GUILayout.Width(120));
if (!Mathf.Approximately(newSpatialBlend, data.SpatialBlend))
{
data.SpatialBlend = newSpatialBlend;
}
// 最大距离
float newMaxDistance = EditorGUILayout.FloatField(data.MaxDistance, GUILayout.Width(80));
if (!Mathf.Approximately(newMaxDistance, data.MaxDistance))
{
data.MaxDistance = Mathf.Max(0f, newMaxDistance);
}
EditorGUILayout.EndHorizontal();
}
#endregion
#region
/// <summary>
/// 添加新环境音数据
/// </summary>
private void AddNewAmbientData()
{
int newId = ambientDataList.Count > 0 ? ambientDataList.Max(d => d.Id) + 1 : 1;
var newData = new AmbientSoundData
{
Id = newId,
AreaId = 0,
SoundId = 10172, // 默认第一个环境音
RowIndex = -1 // 新行还没有Excel行索引
};
ambientDataList.Add(newData);
}
/// <summary>
/// 获取场景名称
/// </summary>
private string GetSceneName(int areaId)
{
if (areaId == 0)
{
return "未设置";
}
string key = $"CS_ScenePanel_Scene{areaId}";
if (languageDict.ContainsKey(key) && languageDict[key].ContainsKey("zh_CN"))
{
string zhName = languageDict[key]["zh_CN"];
return $"{areaId} - {zhName}";
}
return $"{areaId} - (未找到名称)";
}
/// <summary>
/// 获取环境音选项(用于下拉框)
/// </summary>
private string[] GetSoundOptions()
{
List<string> options = new List<string>();
foreach (int soundId in AMBIENT_SOUND_IDS)
{
if (soundDataDict.ContainsKey(soundId))
{
var soundData = soundDataDict[soundId];
options.Add($"{soundId} - {soundData.Comment} - {soundData.AssetName}");
}
else
{
options.Add($"{soundId} - (未加载)");
}
}
return options.ToArray();
}
/// <summary>
/// 获取SoundId在下拉列表中的索引
/// </summary>
private int GetSoundIndex(int soundId)
{
for (int i = 0; i < AMBIENT_SOUND_IDS.Length; i++)
{
if (AMBIENT_SOUND_IDS[i] == soundId)
{
return i;
}
}
return 0; // 默认返回第一个
}
/// <summary>
/// 保存到Excel
/// </summary>
private void SaveToExcel()
{
try
{
string excelPath = GetDocsConfigFilePath(SOUND_EXCEL_NAME);
// ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
using (var package = new ExcelPackage(new FileInfo(excelPath)))
{
// 1. 保存Ambient Sheet
var ambientWorksheet = package.Workbook.Worksheets[AMBIENT_SHEET_NAME];
if (ambientWorksheet != null)
{
// 先清除旧数据从第3行开始
int existingRowCount = ambientWorksheet.Dimension?.Rows ?? 2;
for (int row = 3; row <= existingRowCount; row++)
{
ambientWorksheet.DeleteRow(3);
}
// 写入新数据
int currentRow = 3;
foreach (var data in ambientDataList)
{
ambientWorksheet.Cells[currentRow, COL_ID].Value = data.Id;
ambientWorksheet.Cells[currentRow, COL_AREA_ID].Value = data.AreaId;
ambientWorksheet.Cells[currentRow, COL_SOUND_ID].Value = data.SoundId;
data.RowIndex = currentRow;
currentRow++;
}
}
// 2. 保存Sound Sheet中的环境音数据
var soundWorksheet = package.Workbook.Worksheets[SOUND_SHEET_NAME];
if (soundWorksheet != null)
{
foreach (var kvp in soundDataDict)
{
var data = kvp.Value;
int row = data.RowIndex;
soundWorksheet.Cells[row, COL_SOUND_ID_IN_SOUND].Value = data.Id;
soundWorksheet.Cells[row, COL_COMMENT].Value = data.Comment;
soundWorksheet.Cells[row, COL_ASSET_NAME].Value = data.AssetName;
soundWorksheet.Cells[row, COL_PRIORITY].Value = data.Priority;
soundWorksheet.Cells[row, COL_LOOP].Value = data.Loop ? "TRUE" : "FALSE";
soundWorksheet.Cells[row, COL_VOLUME].Value = data.Volume;
soundWorksheet.Cells[row, COL_SPATIAL_BLEND].Value = data.SpatialBlend;
soundWorksheet.Cells[row, COL_MAX_DISTANCE].Value = data.MaxDistance;
}
}
package.Save();
}
EditorUtility.DisplayDialog("成功", "环境音配置已保存到Excel", "确定");
Debug.Log("环境音配置已保存");
}
catch (Exception e)
{
EditorUtility.DisplayDialog("错误", $"保存失败: {e.Message}", "确定");
Debug.LogError($"保存环境音配置失败: {e}");
}
}
#endregion
#region
private int ParseInt(string text, int defaultValue)
{
if (int.TryParse(text, out int result))
{
return result;
}
return defaultValue;
}
private float ParseFloat(string text, float defaultValue)
{
if (float.TryParse(text, out float result))
{
return result;
}
return defaultValue;
}
private bool ParseBool(string text)
{
return text.Equals("TRUE", StringComparison.OrdinalIgnoreCase) ||
text.Equals("true", StringComparison.OrdinalIgnoreCase) ||
text == "1";
}
#endregion
}
/// <summary>
/// 环境音数据类
/// </summary>
[Serializable]
public class AmbientSoundData
{
public int Id; // 自增ID
public int AreaId; // 场景ID
public int SoundId; // 环境音SoundId
public int RowIndex; // Excel中的行索引用于保存时定位
}
}

View File

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

View File

@ -0,0 +1,506 @@
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using OfficeOpenXml;
using Debug = UnityEngine.Debug;
namespace DesignTools
{
/// <summary>
/// 音效配置Editor工具
/// 读取Docs/config/Sound.xlsx的Sound Sheet编辑音效配置
/// </summary>
public class SoundItemConfigEditor : BaseDesignToolEditor
{
private const string SOUND_EXCEL_NAME = "Sound.xlsx";
private const string SOUND_SHEET_NAME = "Sound";
// 表格列索引
private const int COL_ID = 1;
private const int COL_COMMENT = 2;
private const int COL_ASSET_NAME = 3;
private const int COL_PRIORITY = 4;
private const int COL_LOOP = 5;
private const int COL_VOLUME = 6;
private const int COL_SPATIAL_BLEND = 7;
private const int COL_MAX_DISTANCE = 8;
private List<SoundData> soundDataList = new List<SoundData>();
private List<SoundData> filteredSoundDataList = new List<SoundData>();
// 搜索和筛选
private string searchText = "";
private bool showOnlyLoopSounds = false;
// 分页
private int currentPage = 0;
private int itemsPerPage = 20;
private int totalPages = 0;
// 编辑状态
private Dictionary<int, bool> foldoutStates = new Dictionary<int, bool>();
[MenuItem("策划工具/声音/音效配置")]
public static void ShowWindow()
{
var window = GetWindow<SoundItemConfigEditor>("音效配置");
window.minSize = window.GetMinWindowSize();
window.Show();
}
#region
protected override string GetDocsPathPrefKey()
{
return "SoundItemConfigEditor_DocsPath";
}
protected override string GetWindowTitle()
{
return "音效配置工具";
}
protected override Vector2 GetMinWindowSize()
{
return new Vector2(1200, 700);
}
protected override void LoadConfigData()
{
// 加载Sound表
LoadSoundExcel();
}
protected override void DrawDataEditor()
{
DrawFilterAndPagination();
EditorGUILayout.Space(5);
DrawSoundDataTable();
}
/// <summary>
/// 不显示基类的底部按钮(已在顶部显示)
/// </summary>
protected override bool ShowBottomButtons()
{
return false;
}
/// <summary>
/// 保存数据到Excel覆盖基类方法
/// </summary>
protected override void SaveDataToExcel()
{
SaveToExcel();
}
#endregion
#region
/// <summary>
/// 加载Sound.xlsx
/// </summary>
private void LoadSoundExcel()
{
soundDataList.Clear();
string excelPath = GetDocsConfigFilePath(SOUND_EXCEL_NAME);
if (!File.Exists(excelPath))
{
EditorUtility.DisplayDialog("错误",
$"未找到Sound.xlsx\n路径: {excelPath}",
"确定");
return;
}
// ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
using (var package = new ExcelPackage(new FileInfo(excelPath)))
{
var worksheet = package.Workbook.Worksheets[SOUND_SHEET_NAME];
if (worksheet == null)
{
EditorUtility.DisplayDialog("错误",
$"未找到Sheet: {SOUND_SHEET_NAME}",
"确定");
return;
}
int rowCount = worksheet.Dimension?.Rows ?? 0;
// 从第3行开始读取第1行是字段名第2行是中文说明
for (int row = 3; row <= rowCount; row++)
{
string idText = worksheet.Cells[row, COL_ID].Text;
if (string.IsNullOrEmpty(idText))
{
continue;
}
if (!int.TryParse(idText, out int id))
{
continue;
}
var soundData = new SoundData
{
Id = id,
Comment = worksheet.Cells[row, COL_COMMENT].Text,
AssetName = worksheet.Cells[row, COL_ASSET_NAME].Text,
Priority = ParseInt(worksheet.Cells[row, COL_PRIORITY].Text, 0),
Loop = ParseBool(worksheet.Cells[row, COL_LOOP].Text),
Volume = ParseFloat(worksheet.Cells[row, COL_VOLUME].Text, 1f),
SpatialBlend = ParseFloat(worksheet.Cells[row, COL_SPATIAL_BLEND].Text, 0f),
MaxDistance = ParseFloat(worksheet.Cells[row, COL_MAX_DISTANCE].Text, 100f),
RowIndex = row
};
soundDataList.Add(soundData);
}
}
Debug.Log($"加载了 {soundDataList.Count} 条音效配置");
// 初始化筛选列表
ApplyFilter();
}
#endregion
#region UI绘制
/// <summary>
/// 绘制筛选和分页
/// </summary>
private void DrawFilterAndPagination()
{
EditorGUILayout.BeginVertical("box");
// 搜索和筛选
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("搜索", GUILayout.Width(40));
string newSearchText = EditorGUILayout.TextField(searchText);
if (newSearchText != searchText)
{
searchText = newSearchText;
ApplyFilter();
}
bool newShowOnlyLoop = EditorGUILayout.Toggle("仅显示循环音效", showOnlyLoopSounds, GUILayout.Width(150));
if (newShowOnlyLoop != showOnlyLoopSounds)
{
showOnlyLoopSounds = newShowOnlyLoop;
ApplyFilter();
}
if (GUILayout.Button("清除筛选", GUILayout.Width(80)))
{
searchText = "";
showOnlyLoopSounds = false;
ApplyFilter();
}
GUILayout.FlexibleSpace();
// 保存按钮(放在筛选区域右侧,更显眼)
GUI.backgroundColor = Color.green;
if (GUILayout.Button("💾 保存到Excel", GUILayout.Height(25), GUILayout.Width(120)))
{
SaveToExcel();
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
// 分页控制
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($"共 {filteredSoundDataList.Count} 条音效", GUILayout.Width(120));
GUILayout.FlexibleSpace();
if (GUILayout.Button("<<", GUILayout.Width(40)))
{
currentPage = 0;
}
GUI.enabled = currentPage > 0;
if (GUILayout.Button("<", GUILayout.Width(40)))
{
currentPage--;
}
GUI.enabled = true;
EditorGUILayout.LabelField($"第 {currentPage + 1}/{totalPages} 页", GUILayout.Width(100));
GUI.enabled = currentPage < totalPages - 1;
if (GUILayout.Button(">", GUILayout.Width(40)))
{
currentPage++;
}
GUI.enabled = true;
if (GUILayout.Button(">>", GUILayout.Width(40)))
{
currentPage = totalPages - 1;
}
EditorGUILayout.LabelField("每页", GUILayout.Width(30));
int newItemsPerPage = EditorGUILayout.IntField(itemsPerPage, GUILayout.Width(50));
if (newItemsPerPage != itemsPerPage && newItemsPerPage > 0)
{
itemsPerPage = newItemsPerPage;
ApplyFilter();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
/// <summary>
/// 绘制音效数据表格
/// </summary>
private void DrawSoundDataTable()
{
EditorGUILayout.BeginVertical("box");
// 表头
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
EditorGUILayout.LabelField("ID", EditorStyles.boldLabel, GUILayout.Width(60));
EditorGUILayout.LabelField("策划备注", EditorStyles.boldLabel, GUILayout.Width(120));
EditorGUILayout.LabelField("资源名称", EditorStyles.boldLabel, GUILayout.Width(180));
EditorGUILayout.LabelField("优先级", EditorStyles.boldLabel, GUILayout.Width(60));
EditorGUILayout.LabelField("循环", EditorStyles.boldLabel, GUILayout.Width(50));
EditorGUILayout.LabelField("音量", EditorStyles.boldLabel, GUILayout.Width(120));
EditorGUILayout.LabelField("空间混合", EditorStyles.boldLabel, GUILayout.Width(120));
EditorGUILayout.LabelField("最大距离", EditorStyles.boldLabel, GUILayout.Width(80));
EditorGUILayout.EndHorizontal();
// 数据行(使用 ExpandHeight 让 ScrollView 自适应剩余空间)
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.ExpandHeight(true));
int startIndex = currentPage * itemsPerPage;
int endIndex = Mathf.Min(startIndex + itemsPerPage, filteredSoundDataList.Count);
for (int i = startIndex; i < endIndex; i++)
{
var soundData = filteredSoundDataList[i];
DrawSoundDataRow(soundData);
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
/// <summary>
/// 绘制单行音效数据
/// </summary>
private void DrawSoundDataRow(SoundData data)
{
bool isExpanded = foldoutStates.ContainsKey(data.Id) && foldoutStates[data.Id];
EditorGUILayout.BeginVertical("box");
// 基础信息行
EditorGUILayout.BeginHorizontal();
// ID可编辑
int newId = EditorGUILayout.IntField(data.Id, GUILayout.Width(60));
if (newId != data.Id && newId > 0)
{
data.Id = newId;
}
// 策划备注
string newComment = EditorGUILayout.TextField(data.Comment, GUILayout.Width(120));
if (newComment != data.Comment)
{
data.Comment = newComment;
}
// 资源名称
string newAssetName = EditorGUILayout.TextField(data.AssetName, GUILayout.Width(180));
if (newAssetName != data.AssetName)
{
data.AssetName = newAssetName;
}
// 优先级
int newPriority = EditorGUILayout.IntField(data.Priority, GUILayout.Width(60));
if (newPriority != data.Priority)
{
data.Priority = Mathf.Clamp(newPriority, -128, 128);
}
// 循环
bool newLoop = EditorGUILayout.Toggle(data.Loop, GUILayout.Width(50));
if (newLoop != data.Loop)
{
data.Loop = newLoop;
}
// 音量(带滑动条)
float newVolume = EditorGUILayout.Slider(data.Volume, 0f, 1f, GUILayout.Width(120));
if (!Mathf.Approximately(newVolume, data.Volume))
{
data.Volume = newVolume;
}
// 空间混合(带滑动条)
float newSpatialBlend = EditorGUILayout.Slider(data.SpatialBlend, 0f, 1f, GUILayout.Width(120));
if (!Mathf.Approximately(newSpatialBlend, data.SpatialBlend))
{
data.SpatialBlend = newSpatialBlend;
}
// 最大距离
float newMaxDistance = EditorGUILayout.FloatField(data.MaxDistance, GUILayout.Width(80));
if (!Mathf.Approximately(newMaxDistance, data.MaxDistance))
{
data.MaxDistance = Mathf.Max(0f, newMaxDistance);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
#endregion
#region
/// <summary>
/// 应用筛选
/// </summary>
private void ApplyFilter()
{
filteredSoundDataList.Clear();
foreach (var data in soundDataList)
{
bool matchSearch = string.IsNullOrEmpty(searchText) ||
data.Id.ToString().Contains(searchText) ||
data.Comment.Contains(searchText) ||
data.AssetName.Contains(searchText);
bool matchLoop = !showOnlyLoopSounds || data.Loop;
if (matchSearch && matchLoop)
{
filteredSoundDataList.Add(data);
}
}
// 更新分页
totalPages = Mathf.Max(1, Mathf.CeilToInt((float)filteredSoundDataList.Count / itemsPerPage));
currentPage = Mathf.Clamp(currentPage, 0, totalPages - 1);
}
/// <summary>
/// 保存到Excel
/// </summary>
private void SaveToExcel()
{
try
{
string excelPath = GetDocsConfigFilePath(SOUND_EXCEL_NAME);
// ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
using (var package = new ExcelPackage(new FileInfo(excelPath)))
{
var worksheet = package.Workbook.Worksheets[SOUND_SHEET_NAME];
if (worksheet == null)
{
EditorUtility.DisplayDialog("错误",
$"未找到Sheet: {SOUND_SHEET_NAME}",
"确定");
return;
}
// 更新每一行数据
foreach (var data in soundDataList)
{
int row = data.RowIndex;
worksheet.Cells[row, COL_ID].Value = data.Id;
worksheet.Cells[row, COL_COMMENT].Value = data.Comment;
worksheet.Cells[row, COL_ASSET_NAME].Value = data.AssetName;
worksheet.Cells[row, COL_PRIORITY].Value = data.Priority;
worksheet.Cells[row, COL_LOOP].Value = data.Loop ? "TRUE" : "FALSE";
worksheet.Cells[row, COL_VOLUME].Value = data.Volume;
worksheet.Cells[row, COL_SPATIAL_BLEND].Value = data.SpatialBlend;
worksheet.Cells[row, COL_MAX_DISTANCE].Value = data.MaxDistance;
}
package.Save();
}
EditorUtility.DisplayDialog("成功", "音效配置已保存到Excel", "确定");
Debug.Log("音效配置已保存");
}
catch (Exception e)
{
EditorUtility.DisplayDialog("错误", $"保存失败: {e.Message}", "确定");
Debug.LogError($"保存音效配置失败: {e}");
}
}
#endregion
#region
private int ParseInt(string text, int defaultValue)
{
if (int.TryParse(text, out int result))
{
return result;
}
return defaultValue;
}
private float ParseFloat(string text, float defaultValue)
{
if (float.TryParse(text, out float result))
{
return result;
}
return defaultValue;
}
private bool ParseBool(string text)
{
return text.Equals("TRUE", StringComparison.OrdinalIgnoreCase) ||
text.Equals("true", StringComparison.OrdinalIgnoreCase) ||
text == "1";
}
#endregion
}
/// <summary>
/// 音效数据类
/// </summary>
[Serializable]
public class SoundData
{
public int Id; // 声音编号
public string Comment; // 策划备注
public string AssetName; // 资源名称
public int Priority; // 优先级0默认128最高-128最低
public bool Loop; // 是否循环
public float Volume; // 音量0~1
public float SpatialBlend; // 空间混合量0为2D1为3D
public float MaxDistance; // 最大距离
public int RowIndex; // Excel中的行索引用于保存时定位
}
}

View File

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

View File

@ -128,6 +128,7 @@ namespace Byway.Thrift.Data
private global::Byway.Thrift.Data.GuideReward _GuideReward;
private global::Byway.Thrift.Data.FriendConst _FriendConst;
private global::Byway.Thrift.Data.ChargeConst _ChargeConst;
private global::Byway.Thrift.Data.AmbientData _AmbientData;
[DataMember(Order = 0)]
public global::Byway.Thrift.Data.AdGiftData AdGiftData
@ -1361,6 +1362,20 @@ namespace Byway.Thrift.Data
}
}
[DataMember(Order = 0)]
public global::Byway.Thrift.Data.AmbientData AmbientData
{
get
{
return _AmbientData;
}
set
{
__isset.AmbientData = true;
this._AmbientData = value;
}
}
[DataMember(Order = 1)]
public Isset __isset;
@ -1543,6 +1558,8 @@ namespace Byway.Thrift.Data
public bool FriendConst;
[DataMember]
public bool ChargeConst;
[DataMember]
public bool AmbientData;
}
#region XmlSerializer support
@ -1987,6 +2004,11 @@ namespace Byway.Thrift.Data
return __isset.ChargeConst;
}
public bool ShouldSerializeAmbientData()
{
return __isset.AmbientData;
}
#endregion XmlSerializer support
public AllConfigs()
@ -2436,6 +2458,11 @@ namespace Byway.Thrift.Data
tmp0.ChargeConst = (global::Byway.Thrift.Data.ChargeConst)this.ChargeConst.DeepCopy();
}
tmp0.__isset.ChargeConst = this.__isset.ChargeConst;
if((AmbientData != null) && __isset.AmbientData)
{
tmp0.AmbientData = (global::Byway.Thrift.Data.AmbientData)this.AmbientData.DeepCopy();
}
tmp0.__isset.AmbientData = this.__isset.AmbientData;
return tmp0;
}
@ -3424,6 +3451,17 @@ namespace Byway.Thrift.Data
await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);
}
break;
case 89:
if (field.Type == TType.Struct)
{
AmbientData = new global::Byway.Thrift.Data.AmbientData();
await AmbientData.ReadAsync(iprot, cancellationToken);
}
else
{
await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);
}
break;
default:
await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);
break;
@ -4240,6 +4278,15 @@ namespace Byway.Thrift.Data
await ChargeConst.WriteAsync(oprot, cancellationToken);
await oprot.WriteFieldEndAsync(cancellationToken);
}
if((AmbientData != null) && __isset.AmbientData)
{
tmp2.Name = "AmbientData";
tmp2.Type = TType.Struct;
tmp2.ID = 89;
await oprot.WriteFieldBeginAsync(tmp2, cancellationToken);
await AmbientData.WriteAsync(oprot, cancellationToken);
await oprot.WriteFieldEndAsync(cancellationToken);
}
await oprot.WriteFieldStopAsync(cancellationToken);
await oprot.WriteStructEndAsync(cancellationToken);
}
@ -4340,7 +4387,8 @@ namespace Byway.Thrift.Data
&& ((__isset.ConstantString == other.__isset.ConstantString) && ((!__isset.ConstantString) || (global::System.Object.Equals(ConstantString, other.ConstantString))))
&& ((__isset.GuideReward == other.__isset.GuideReward) && ((!__isset.GuideReward) || (global::System.Object.Equals(GuideReward, other.GuideReward))))
&& ((__isset.FriendConst == other.__isset.FriendConst) && ((!__isset.FriendConst) || (global::System.Object.Equals(FriendConst, other.FriendConst))))
&& ((__isset.ChargeConst == other.__isset.ChargeConst) && ((!__isset.ChargeConst) || (global::System.Object.Equals(ChargeConst, other.ChargeConst))));
&& ((__isset.ChargeConst == other.__isset.ChargeConst) && ((!__isset.ChargeConst) || (global::System.Object.Equals(ChargeConst, other.ChargeConst))))
&& ((__isset.AmbientData == other.__isset.AmbientData) && ((!__isset.AmbientData) || (global::System.Object.Equals(AmbientData, other.AmbientData))));
}
public override int GetHashCode() {
@ -4698,6 +4746,10 @@ namespace Byway.Thrift.Data
{
hashcode = (hashcode * 397) + ChargeConst.GetHashCode();
}
if((AmbientData != null) && __isset.AmbientData)
{
hashcode = (hashcode * 397) + AmbientData.GetHashCode();
}
}
return hashcode;
}
@ -5234,6 +5286,12 @@ namespace Byway.Thrift.Data
tmp3.Append("ChargeConst: ");
ChargeConst.ToString(tmp3);
}
if((AmbientData != null) && __isset.AmbientData)
{
if(0 < tmp4++) { tmp3.Append(", "); }
tmp3.Append("AmbientData: ");
AmbientData.ToString(tmp3);
}
tmp3.Append(')');
return tmp3.ToString();
}

View File

@ -0,0 +1,65 @@
/**
* <auto-generated>
* Autogenerated by Thrift Compiler (0.22.0)
* DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
* </auto-generated>
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Thrift;
using Thrift.Collections;
using System.Runtime.Serialization;
using Thrift.Protocol;
#pragma warning disable IDE0079 // remove unnecessary pragmas
#pragma warning disable IDE0017 // object init can be simplified
#pragma warning disable IDE0028 // collection init can be simplified
#pragma warning disable IDE0305 // collection init can be simplified
#pragma warning disable IDE0034 // simplify default expression
#pragma warning disable IDE0066 // use switch expression
#pragma warning disable IDE0090 // simplify new expression
#pragma warning disable IDE0290 // use primary CTOR
#pragma warning disable IDE1006 // parts of the code use IDL spelling
#pragma warning disable CA1822 // empty DeepCopy() methods still non-static
#pragma warning disable IDE0083 // pattern matching "that is not SomeType" requires net5.0 but we still support earlier versions
namespace Byway.Thrift.Data
{
public static class AmbientDataExtensions
{
public static bool Equals(this Dictionary<int, global::Byway.Thrift.Data.AmbientDataItem> instance, object that)
{
if (!(that is Dictionary<int, global::Byway.Thrift.Data.AmbientDataItem> other)) return false;
if (ReferenceEquals(instance, other)) return true;
return TCollections.Equals(instance, other);
}
public static int GetHashCode(this Dictionary<int, global::Byway.Thrift.Data.AmbientDataItem> instance)
{
return TCollections.GetHashCode(instance);
}
public static Dictionary<int, global::Byway.Thrift.Data.AmbientDataItem> DeepCopy(this Dictionary<int, global::Byway.Thrift.Data.AmbientDataItem> source)
{
if (source == null)
return null;
var tmp15 = new Dictionary<int, global::Byway.Thrift.Data.AmbientDataItem>(source.Count);
foreach (var pair in source)
tmp15.Add(pair.Key, (pair.Value != null) ? pair.Value.DeepCopy() : null);
return tmp15;
}
}
}

View File

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

View File

@ -0,0 +1,214 @@
/**
* <auto-generated>
* Autogenerated by Thrift Compiler (0.22.0)
* DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
* </auto-generated>
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Thrift;
using Thrift.Collections;
using System.Runtime.Serialization;
using Thrift.Protocol;
using Thrift.Protocol.Entities;
using Thrift.Protocol.Utilities;
using Thrift.Transport;
using Thrift.Transport.Client;
#pragma warning disable IDE0079 // remove unnecessary pragmas
#pragma warning disable IDE0017 // object init can be simplified
#pragma warning disable IDE0028 // collection init can be simplified
#pragma warning disable IDE0305 // collection init can be simplified
#pragma warning disable IDE0034 // simplify default expression
#pragma warning disable IDE0066 // use switch expression
#pragma warning disable IDE0090 // simplify new expression
#pragma warning disable IDE0290 // use primary CTOR
#pragma warning disable IDE1006 // parts of the code use IDL spelling
#pragma warning disable CA1822 // empty DeepCopy() methods still non-static
#pragma warning disable IDE0083 // pattern matching "that is not SomeType" requires net5.0 but we still support earlier versions
namespace Byway.Thrift.Data
{
[DataContract(Namespace="")]
public partial class AmbientData : TBase
{
private Dictionary<int, global::Byway.Thrift.Data.AmbientDataItem> _ambientdatas;
[DataMember(Order = 0)]
public Dictionary<int, global::Byway.Thrift.Data.AmbientDataItem> Ambientdatas
{
get
{
return _ambientdatas;
}
set
{
__isset.@ambientdatas = true;
this._ambientdatas = value;
}
}
[DataMember(Order = 1)]
public Isset __isset;
[DataContract]
public struct Isset
{
[DataMember]
public bool @ambientdatas;
}
#region XmlSerializer support
public bool ShouldSerializeAmbientdatas()
{
return __isset.@ambientdatas;
}
#endregion XmlSerializer support
public AmbientData()
{
}
public AmbientData DeepCopy()
{
var tmp5 = new AmbientData();
if((Ambientdatas != null) && __isset.@ambientdatas)
{
tmp5.Ambientdatas = this.Ambientdatas.DeepCopy();
}
tmp5.__isset.@ambientdatas = this.__isset.@ambientdatas;
return tmp5;
}
public async global::System.Threading.Tasks.Task ReadAsync(TProtocol iprot, CancellationToken cancellationToken)
{
iprot.IncrementRecursionDepth();
try
{
TField field;
await iprot.ReadStructBeginAsync(cancellationToken);
while (true)
{
field = await iprot.ReadFieldBeginAsync(cancellationToken);
if (field.Type == TType.Stop)
{
break;
}
switch (field.ID)
{
case 1:
if (field.Type == TType.Map)
{
{
var _map6 = await iprot.ReadMapBeginAsync(cancellationToken);
Ambientdatas = new Dictionary<int, global::Byway.Thrift.Data.AmbientDataItem>(_map6.Count);
for(int _i7 = 0; _i7 < _map6.Count; ++_i7)
{
int _key8;
global::Byway.Thrift.Data.AmbientDataItem _val9;
_key8 = await iprot.ReadI32Async(cancellationToken);
_val9 = new global::Byway.Thrift.Data.AmbientDataItem();
await _val9.ReadAsync(iprot, cancellationToken);
Ambientdatas[_key8] = _val9;
}
await iprot.ReadMapEndAsync(cancellationToken);
}
}
else
{
await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);
}
break;
default:
await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);
break;
}
await iprot.ReadFieldEndAsync(cancellationToken);
}
await iprot.ReadStructEndAsync(cancellationToken);
}
finally
{
iprot.DecrementRecursionDepth();
}
}
public async global::System.Threading.Tasks.Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)
{
oprot.IncrementRecursionDepth();
try
{
var tmp10 = new TStruct("AmbientData");
await oprot.WriteStructBeginAsync(tmp10, cancellationToken);
var tmp11 = new TField();
if((Ambientdatas != null) && __isset.@ambientdatas)
{
tmp11.Name = "ambientdatas";
tmp11.Type = TType.Map;
tmp11.ID = 1;
await oprot.WriteFieldBeginAsync(tmp11, cancellationToken);
await oprot.WriteMapBeginAsync(new TMap(TType.I32, TType.Struct, Ambientdatas.Count), cancellationToken);
foreach (int _iter12 in Ambientdatas.Keys)
{
await oprot.WriteI32Async(_iter12, cancellationToken);
await Ambientdatas[_iter12].WriteAsync(oprot, cancellationToken);
}
await oprot.WriteMapEndAsync(cancellationToken);
await oprot.WriteFieldEndAsync(cancellationToken);
}
await oprot.WriteFieldStopAsync(cancellationToken);
await oprot.WriteStructEndAsync(cancellationToken);
}
finally
{
oprot.DecrementRecursionDepth();
}
}
public override bool Equals(object that)
{
if (!(that is AmbientData other)) return false;
if (ReferenceEquals(this, other)) return true;
return ((__isset.@ambientdatas == other.__isset.@ambientdatas) && ((!__isset.@ambientdatas) || (TCollections.Equals(Ambientdatas, other.Ambientdatas))));
}
public override int GetHashCode() {
int hashcode = 157;
unchecked {
if((Ambientdatas != null) && __isset.@ambientdatas)
{
hashcode = (hashcode * 397) + TCollections.GetHashCode(Ambientdatas);
}
}
return hashcode;
}
public override string ToString()
{
var tmp13 = new StringBuilder("AmbientData(");
int tmp14 = 0;
if((Ambientdatas != null) && __isset.@ambientdatas)
{
if(0 < tmp14++) { tmp13.Append(", "); }
tmp13.Append("Ambientdatas: ");
Ambientdatas.ToString(tmp13);
}
tmp13.Append(')');
return tmp13.ToString();
}
}
}

View File

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

View File

@ -0,0 +1,309 @@
/**
* <auto-generated>
* Autogenerated by Thrift Compiler (0.22.0)
* DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
* </auto-generated>
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Thrift;
using Thrift.Collections;
using System.Runtime.Serialization;
using Thrift.Protocol;
using Thrift.Protocol.Entities;
using Thrift.Protocol.Utilities;
using Thrift.Transport;
using Thrift.Transport.Client;
#pragma warning disable IDE0079 // remove unnecessary pragmas
#pragma warning disable IDE0017 // object init can be simplified
#pragma warning disable IDE0028 // collection init can be simplified
#pragma warning disable IDE0305 // collection init can be simplified
#pragma warning disable IDE0034 // simplify default expression
#pragma warning disable IDE0066 // use switch expression
#pragma warning disable IDE0090 // simplify new expression
#pragma warning disable IDE0290 // use primary CTOR
#pragma warning disable IDE1006 // parts of the code use IDL spelling
#pragma warning disable CA1822 // empty DeepCopy() methods still non-static
#pragma warning disable IDE0083 // pattern matching "that is not SomeType" requires net5.0 but we still support earlier versions
namespace Byway.Thrift.Data
{
[DataContract(Namespace="")]
public partial class AmbientDataItem : TBase
{
private int _Id;
private int _AreaId;
private int _SoundId;
[DataMember(Order = 0)]
public int Id
{
get
{
return _Id;
}
set
{
__isset.Id = true;
this._Id = value;
}
}
[DataMember(Order = 0)]
public int AreaId
{
get
{
return _AreaId;
}
set
{
__isset.AreaId = true;
this._AreaId = value;
}
}
[DataMember(Order = 0)]
public int SoundId
{
get
{
return _SoundId;
}
set
{
__isset.SoundId = true;
this._SoundId = value;
}
}
[DataMember(Order = 1)]
public Isset __isset;
[DataContract]
public struct Isset
{
[DataMember]
public bool Id;
[DataMember]
public bool AreaId;
[DataMember]
public bool SoundId;
}
#region XmlSerializer support
public bool ShouldSerializeId()
{
return __isset.Id;
}
public bool ShouldSerializeAreaId()
{
return __isset.AreaId;
}
public bool ShouldSerializeSoundId()
{
return __isset.SoundId;
}
#endregion XmlSerializer support
public AmbientDataItem()
{
}
public AmbientDataItem DeepCopy()
{
var tmp0 = new AmbientDataItem();
if(__isset.Id)
{
tmp0.Id = this.Id;
}
tmp0.__isset.Id = this.__isset.Id;
if(__isset.AreaId)
{
tmp0.AreaId = this.AreaId;
}
tmp0.__isset.AreaId = this.__isset.AreaId;
if(__isset.SoundId)
{
tmp0.SoundId = this.SoundId;
}
tmp0.__isset.SoundId = this.__isset.SoundId;
return tmp0;
}
public async global::System.Threading.Tasks.Task ReadAsync(TProtocol iprot, CancellationToken cancellationToken)
{
iprot.IncrementRecursionDepth();
try
{
TField field;
await iprot.ReadStructBeginAsync(cancellationToken);
while (true)
{
field = await iprot.ReadFieldBeginAsync(cancellationToken);
if (field.Type == TType.Stop)
{
break;
}
switch (field.ID)
{
case 1:
if (field.Type == TType.I32)
{
Id = await iprot.ReadI32Async(cancellationToken);
}
else
{
await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);
}
break;
case 2:
if (field.Type == TType.I32)
{
AreaId = await iprot.ReadI32Async(cancellationToken);
}
else
{
await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);
}
break;
case 3:
if (field.Type == TType.I32)
{
SoundId = await iprot.ReadI32Async(cancellationToken);
}
else
{
await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);
}
break;
default:
await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);
break;
}
await iprot.ReadFieldEndAsync(cancellationToken);
}
await iprot.ReadStructEndAsync(cancellationToken);
}
finally
{
iprot.DecrementRecursionDepth();
}
}
public async global::System.Threading.Tasks.Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)
{
oprot.IncrementRecursionDepth();
try
{
var tmp1 = new TStruct("AmbientDataItem");
await oprot.WriteStructBeginAsync(tmp1, cancellationToken);
var tmp2 = new TField();
if(__isset.Id)
{
tmp2.Name = "Id";
tmp2.Type = TType.I32;
tmp2.ID = 1;
await oprot.WriteFieldBeginAsync(tmp2, cancellationToken);
await oprot.WriteI32Async(Id, cancellationToken);
await oprot.WriteFieldEndAsync(cancellationToken);
}
if(__isset.AreaId)
{
tmp2.Name = "AreaId";
tmp2.Type = TType.I32;
tmp2.ID = 2;
await oprot.WriteFieldBeginAsync(tmp2, cancellationToken);
await oprot.WriteI32Async(AreaId, cancellationToken);
await oprot.WriteFieldEndAsync(cancellationToken);
}
if(__isset.SoundId)
{
tmp2.Name = "SoundId";
tmp2.Type = TType.I32;
tmp2.ID = 3;
await oprot.WriteFieldBeginAsync(tmp2, cancellationToken);
await oprot.WriteI32Async(SoundId, cancellationToken);
await oprot.WriteFieldEndAsync(cancellationToken);
}
await oprot.WriteFieldStopAsync(cancellationToken);
await oprot.WriteStructEndAsync(cancellationToken);
}
finally
{
oprot.DecrementRecursionDepth();
}
}
public override bool Equals(object that)
{
if (!(that is AmbientDataItem other)) return false;
if (ReferenceEquals(this, other)) return true;
return ((__isset.Id == other.__isset.Id) && ((!__isset.Id) || (global::System.Object.Equals(Id, other.Id))))
&& ((__isset.AreaId == other.__isset.AreaId) && ((!__isset.AreaId) || (global::System.Object.Equals(AreaId, other.AreaId))))
&& ((__isset.SoundId == other.__isset.SoundId) && ((!__isset.SoundId) || (global::System.Object.Equals(SoundId, other.SoundId))));
}
public override int GetHashCode() {
int hashcode = 157;
unchecked {
if(__isset.Id)
{
hashcode = (hashcode * 397) + Id.GetHashCode();
}
if(__isset.AreaId)
{
hashcode = (hashcode * 397) + AreaId.GetHashCode();
}
if(__isset.SoundId)
{
hashcode = (hashcode * 397) + SoundId.GetHashCode();
}
}
return hashcode;
}
public override string ToString()
{
var tmp3 = new StringBuilder("AmbientDataItem(");
int tmp4 = 0;
if(__isset.Id)
{
if(0 < tmp4++) { tmp3.Append(", "); }
tmp3.Append("Id: ");
Id.ToString(tmp3);
}
if(__isset.AreaId)
{
if(0 < tmp4++) { tmp3.Append(", "); }
tmp3.Append("AreaId: ");
AreaId.ToString(tmp3);
}
if(__isset.SoundId)
{
if(0 < tmp4++) { tmp3.Append(", "); }
tmp3.Append("SoundId: ");
SoundId.ToString(tmp3);
}
tmp3.Append(')');
return tmp3.ToString();
}
}
}

View File

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