【整合】 框架使用规则

This commit is contained in:
Shiliang
2026-07-10 17:50:20 +08:00
parent db0143be83
commit 001d9e5ef5
45 changed files with 1716 additions and 4062 deletions

View File

@@ -1,95 +1,54 @@
# Hotfix 启动
## 分类
- 核心框架
## 目的
记录 hotfix 侧根入口和功能目录聚合,梳理游戏启动链路
记录非热更层更新启动代码、加载 AOT/HotUpdate 程序集并进入 `HFGameStart` 的稳定顺序
---
## 主要路径
## 核心路径
- 非热更入口:`Assets/Game/GameMono/Scripts/GameDllManager.cs`
- V2 公共下载层:`Assets/StrayFog/Core/SFSubPackageV2.cs`
- 热更入口:`Assets/Game/GameHFScripte/GameFunction/GameRoot/HFGameStart.cs`
- 普通资源检查:`Assets/Game/GameHFScripte/GameFunction/HFDownLoadSubPackage/`
- `Assets/Game/GameHFScripte/GameFunction/GameRoot/`
- 关键文件:`HFGameStart.cs``HFFunCatalogue_GameManager.cs`
- 程序集:`GameHF`
---
## 启动调用链
## 启动顺序
```text
HFGameStart.Init(GameSetting)
-> HFFunCatalogue.SetGameSetting
-> HFFunCatalogue.ChangeLanguage(enHFLanguageClassify.EN)
-> HFGameStartAssetsCheckManager.CheckFirstDownLoadWindowAsset
-> HFGameStart.LoadDB
-> Hotfix_ZYXlsx_GoogleProtobuf_Helper.LoadAllXlsx
-> HFFunCatalogue.Scene.LoadScene(enHFSceneName.Game)
-> HFGameStart.InitManager
-> HFFunCatalogue.UIManager.OpenWindow<登录窗口>
GameDllManager.GameStart
-> SetDownloadConfig
-> 获取远端 V2 subpackage.json
-> CheckAndDownloadBootstrapFiles
-> PersistentData / StreamingAssets / CDN 逐文件 MD5 校验
-> 按 LoadOrder 加载 AOT
-> 按 LoadOrder 加载 HotUpdate
-> 调用热更入口 HFGameStart.Init
-> CommitBootstrapCodeLoaded
-> CheckAndDownloadAllFiles 更新普通资源
-> 加载配置、场景和业务管理器
```
---
Bootstrap 阶段是普通资源的硬门禁。远端清单、代码下载、MD5、AOT、HotUpdate 或热更入口失败时,不提交新清单,也不进入普通资源比对。
## InitManager 初始化单例
## 失败处理
`HFGameStart.InitManager()` 负责初始化各业务/框架管理器,例如:
- 清单获取失败通过独立回调返回并阻断启动。
- 文件失败结果包含 Path、期望 MD5、错误和尝试 URL。
- 下载写临时文件MD5 成功后才替换 PersistentData 正式文件。
- AOT 始终先于 HotUpdate不同版本代码不能混合加载。
- 失败后可以重新执行整个 Bootstrap 阶段,已校验成功文件不会重复下载。
- `HFSeverManager`
- `HFMapManager`
- `HFDamageManager`
- `HFPopupTextManager`
- 各业务管理器(如背包、登录等,按项目实际)
## 热更入口
---
`HFGameStart` 负责设置 GameSetting、检查普通资源、加载配置和场景、初始化业务管理器。业务访问框架能力统一通过 `HFFunCatalogue`
## HFFunCatalogue 聚合入口
`HFFunCatalogue` 提供统一访问入口:
- `UIManager` — UI 窗口
- `Asset` — 资源加载
- `Scene` — 场景加载
- `Sprite` — 图集/精灵
- `EntityPool` — 实体池
- `LinePool` / `SoundPool` — 其他对象池
- `Event` — 事件系统
- `GameConstant` — 游戏常量
- `NDB` — 配置表读取
- `Bag` / `Tips` — 业务相关入口(按项目实际)
---
## 使用规则
- 业务代码通过 `HFFunCatalogue` 访问核心库,禁止直接实例化核心库内部类。
- `HFGameStart` 是 hotfix 侧启动入口,不要绕过它直接初始化管理器。
- 启动顺序修改需谨慎,确保配置表、资源、场景按正确顺序加载。
---
## 相关模块
- [UI 窗口系统](UI窗口系统/README.md)
- [资源管理](资源管理/README.md)
- [场景系统](场景系统.md)
- [数据/配置框架](数据配置框架.md)
---
启动顺序变化必须同时检查非热更层、V2 门禁、普通资源和 UI 首屏,不能只修改热更入口一侧。
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../模块索引.md) | 模块索引是项目知识总入口 |
| [modules/frameworks/UI窗口系统/README.md](UI窗口系统/README.md) | 启动后打开登录窗口依赖 UI 框架 |
| [modules/frameworks/资源管理/README.md](资源管理/README.md) | 启动流程涉及资源检查 |
| [modules/frameworks/场景系统.md](场景系统.md) | 启动流程涉及场景加载 |
| [modules/frameworks/数据配置框架.md](数据配置框架.md) | 启动流程涉及配置表加载 |
| [rules/10-架构说明.md](../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/30-模块登记规则.md](../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
| [模块索引](../模块索引.md) | 模块入口和状态 |
| [资源管理](资源管理/README.md) | V2 清单、Bootstrap 和普通资源 |
| [UI 窗口系统](UI窗口系统/README.md) | 启动后窗口加载 |
| [场景系统](场景系统.md) | 启动后的场景流程 |
| [架构说明](../../rules/10-架构说明.md) | 非热更层与热更层边界 |

View File

@@ -1,129 +1,66 @@
# UI 框架
## 分类
- 项目框架
# UI 窗口系统
## 目的
记录 UIWindow 管理、窗口基类、UIMono 桥接、窗口资源加载和 UI 生命周期
记录 StrayFog 窗口管理、资源加载、生命周期、UIMono 绑定和虚拟列表的稳定使用规则
## 已知线索
## 主要路径
- 管理路径:`Assets/Game/GameHFScripte/CopyToHF/UIWindowMgr/`
- 窗口实现:`Assets/Game/GameHFScripte/GameFunction/UIWindows/`
- Mono 桥接:`Assets/Game/GameMono/Scripts/UIMono/`
- 资源路径:`Assets/Game/GameHFResource/CN/UIWindows/`
| 类型 | 路径 | 说明 |
|---|---|---|
| ESF 源码 | `Assets/StrayFog/Editor/CopyToX/CopyToHF/UIWindowMgr/` | 窗口框架真实源码 |
| HF 生成代码 | `Assets/Game/GameHFScripte/CopyToHF/UIWindowMgr/` | CopyToX 结果,禁止手改 |
| 业务窗口 | `Assets/Game/GameHFScripte/GameFunction/UIWindows/` | 窗口与子组件业务逻辑 |
| Mono 组件 | `Assets/Game/GameMono/Scripts/UIMono/` | SFUI/UIMono 和列表包装 |
| UI 资源 | `Assets/Game/GameHFResource/<语言>/UIWindows/` | Prefab 与窗口资源 |
## 已确认链路
UI manager 初始化:
```text
HFUIWindowMgr.OnAfterCreateInstance()
-> OnInitializeCanvasAndCamera()
-> 为 enHFUICanvasLayer 创建 Canvas
-> 为 ScreenSpaceCamera Canvas 创建 UI Camera
-> 创建 CacheSiblingIndex Canvas
-> 创建或接管 EventSystem / StandaloneInputModule
```
打开窗口:
## 打开流程
```text
HFFunCatalogue.UIManager.OpenWindow<T>()
-> HFUIWindowMgr.OnOpenWindow<HFUIWindowSetting>()
-> 创建 HFDefaultTask / HFUIWindow_RespondTask
-> 读取窗口类型上的 HFAssetBundlePathAttribute 和 HFUIWindowLayerAttribute
-> OnSettingWindowSiblingIndex()
-> 从缓存队列复用窗口实例,或 Activator.CreateInstance 创建窗口逻辑实例
-> OnPreloadWindowInMemory()
-> HFABMgr.Instance.LoadInMemory(windowPrefabPath)
-> win.BindGameObject(prefab)
-> OnAttachWindow(win)
-> win.SyncWindowState()
-> AbsHFUIWindow.OnOpen()
-> HFUIWindowMgr 创建窗口任务
-> 读取 HFAssetBundlePath / HFUIWindowLayer
-> 复用缓存或创建窗口逻辑
-> LoadInMemory 加载 Prefab
-> BindGameObject / OnAttachWindow
-> OnRunAwake实例首次绑定
-> OnOpen每次打开
```
关闭窗口
关闭窗口会执行 `OnClose`、隐藏实例并放入窗口缓存。需要释放窗口与资源时使用 UI 管理器公开卸载入口,不直接 Destroy 窗口 Prefab 或绕过管理器修改父节点和 siblingIndex。
```text
CloseWindow<T>() / AbsHFUIWindow.CloseWindow()
-> HFUIWindowMgr.OnCloseWindow()
-> siblingIndexLayer.ToggleWindowActive(false)
-> 放入 mCacheWindowInstanceMaping
-> 处理 backtrack 自动恢复窗口
-> win.SyncWindowState()
-> AbsHFUIWindow.OnClose()
```
## 生命周期
窗口资源绑定:
| 生命周期 | 用途 |
|---|---|
| `OnRunAwake` | 查找控件、绑定按钮、配置并初始化虚拟列表,只执行一次 |
| `OnOpen` | 注册本次打开所需事件、刷新数据与显示状态 |
| `OnClose` | 移除事件、停止计时器和清理本次打开状态 |
- 窗口类通过 `[HFAssetBundlePath(...)]` 标记 prefab 路径
- 窗口类通过 `[HFUIWindowLayer(...)]` 标记 Canvas/Layer 和是否允许多开。
- `AbsHFUIWindowSetting.SetWindowType` 会读取上述 attribute生成 `winId``winTypeId` 和资源路径。
异步加载、动画和下载的成功、失败、取消路径都必须结束窗口任务,不能让窗口永久停在加载中
## 使用规则
- 窗口必须继承 `AbsHFUIWindow`
- 新窗口必须配置 `HFAssetBundlePath``HFUIWindowLayer`
- 打开窗口统一走 `HFFunCatalogue.UIManager.OpenWindow<T>()`
- 关闭窗口统一走 `CloseWindow()``HFFunCatalogue.UIManager.CloseWindow<T>()`
- `OnOpen` 中注册的事件必须在 `OnClose` 中移除
- 不直接 `Instantiate` / `Destroy` UI 窗口 prefab
- 不绕过 `HFUIWindowMgr` 手动修改窗口 active、父节点或 siblingIndex
- 普通业务窗口默认使用 `Dynamic`,提示类优先使用 `Tooltip`,强交互弹窗优先使用 `Popup``Highest` 仅用于明确的顶层兜底。
- UI prefab 放在 `Assets/Game/GameHFResource/<语言>/UIWindows/<模块>/`,代码中的 `HFAssetBundlePath` 使用相对 `UIWindows/...` 的路径。
- 优先使用项目已有 UIMono / ZYUGUI 绑定流程,不手写大量 `transform.Find`
- 列表、循环列表、网格优先使用已有 ZYUGUI / SuperScrollView 组件和编辑器转换工具。
- 异步加载或动画结束后再调用完成回调时,必须保证失败/取消路径不会卡住窗口状态。
- 窗口继承 `AbsHFUIWindow`,配置 `HFAssetBundlePath``HFUIWindowLayer`
- 打开和关闭统一通过 `HFFunCatalogue.UIManager`
- `OnOpen``OnClose` 中的事件注册和释放保持对称
- 引用绑定优先使用项目 UIMono/SFUI 流程,不堆叠 `transform.Find`
- 单列虚拟列表使用 LoopListView2 包装接口,多列布局使用 LoopGridView 包装接口
- 模板节点在 Prefab 中保持禁用,由虚拟列表创建实例
- UI 修改必须验证脚本、Prefab 层级、RectTransform、组件状态和运行时效果
## 新增 UI 窗口清单
## 子文档
1. 确认窗口所属功能模块。
2. 创建窗口类,继承 `AbsHFUIWindow`
3. 添加 `HFAssetBundlePath``HFUIWindowLayer`
4. 创建或更新 prefab。
5. 使用 UIMono / ZYUGUI 生成或绑定 UI 引用。
6.`HFFunCatalogue.UIManager.OpenWindow<T>()` 接入入口。
7.`OnOpen` / `OnClose` 对称处理事件、计时器、临时数据。
8. 验证打开、关闭、重复打开、切场景或语言变化时的表现。
9. 如果新增模块或改变 UI 架构,更新相关模块文档。
## 梳理任务
- [x] 梳理窗口基类和打开/关闭 API。
- [x] 梳理窗口 prefab 路径绑定方式。
- [ ] 梳理 UIMono 自动绑定和 partial 生成关系。
- [x] 梳理窗口生命周期Open/Close/状态同步。
- [x] 梳理 UI 事件订阅和释放规范。
- [x] 梳理功能 UI 和框架 UI 的边界。
## 待确认
- 常规入口已确认是 `HFFunCatalogue.UIManager` / `HFUIWindowMgr`,是否存在绕过路径待继续扫描。
- UI 资源会经过 `HFAssetBundlePathAttribute` 和语言路径规则,哪些 UI 真正有 CN/EN 差异待确认。
- UIMono 是否由编辑器工具生成。
- `OnOpen`/`OnClose` 外是否还有业务窗口统一订阅/释放规范待确认。
## 相关模块
- [资源框架](../资源管理/README.md)
- [事件框架](../事件系统/README.md)
- [ZYUGUI Tools](../../模块索引.md#编辑器工具)
---
- [UI 开发指南](开发指南.md)
- [界面拼接](界面拼接.md)
- [UI 拼接工具](../编辑器工具/UI拼接工具.md)
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../../模块索引.md) | ZYUGUI Tools |
| [modules/frameworks/事件系统/README.md](../事件系统/README.md) | 事件框架 |
| [modules/frameworks/资源管理/README.md](../资源管理/README.md) | 资源框架 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/20-开发规则.md](../../../rules/20-开发规则.md) | 开发规则与框架/生成规则互相引用 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
| TODO补充依赖或使用本框架的模块文档 | 如依赖本框架的功能模块 |
| [模块索引](../../模块索引.md) | 模块入口和状态 |
| [资源管理](../资源管理/README.md) | UI Prefab 加载与卸载 |
| [事件系统](../事件系统/README.md) | 窗口事件生命周期 |
| [架构说明](../../../rules/10-架构说明.md) | ESF/HF 源码边界 |
| [开发规则](../../../rules/20-开发规则.md) | UI 与 Prefab 通用约束 |

View File

@@ -1,231 +1,135 @@
# StrayFog 框架 UI 开发指南
# UI 开发指南
## 一、窗口开发
### 1.1 创建模板
```csharp
[HFAssetBundlePath(enHFLanguageRootFolder.ASSETS_GAME_GAMEHFRESOURCE, "UIWindows/MyWindow/MyWindow.prefab")]
[HFUIWindowLayer(enHFUICanvasLayer.UIWindow, enHFUIWindowLayer.Dynamic)]
public class MyWindow : AbsHFUIWindow
{
private SFUI_Button BtnClose;
窗口类继承 `AbsHFUIWindow`,通过 attribute 声明资源路径和 Canvas 层级:
```csharp
[HFAssetBundlePath("UIWindows/ExampleWindow/ExampleWindow.prefab")]
[HFUIWindowLayer(enHFUICanvasLayer.Dynamic)]
public partial class ExampleWindow : AbsHFUIWindow
{
protected override void OnRunAwake()
{
base.OnRunAwake();
BtnClose = FindUICtrlByName<SFUI_Button>("BtnClose");
BtnClose.OnAddClickListener += OnBtnClose;
// 查找控件、绑定按钮、初始化列表。
}
protected override void OnOpen(Action _onOpenComplete)
protected override void OnOpen(object[] _args)
{
base.OnOpen(_onOpenComplete);
RefreshUI();
base.OnOpen(_args);
// 注册事件并刷新本次显示数据。
}
private void OnBtnClose(GameObject go) => CloseWindow();
private void RefreshUI() { }
}
```
### 1.2 生命周期
| 方法 | 调用时机 | 推荐操作 |
|-----|---------|---------|
| `OnRunAwake` | 创建时一次 | 绑定 UI 事件、初始化滚动列表 `BindEvent` |
| `OnOpen` | 每次打开 | 注册 TC 事件、调用 `ShowView()` 刷新数据 |
| `OnClose` | 每次关闭 | 取消 TC 事件 |
**关键原则**
- **绑定类操作**(如 `BindEvent`)放在 `OnRunAwake`,只需执行一次
- **刷新类操作**(如 `ShowView`)放在 `OnOpen`,每次打开都需执行
### 1.3 窗口操作
```csharp
// 打开
HFFunCatalogue.UIManager.OpenWindow<MyWindow>();
// 关闭
HFFunCatalogue.UIManager.CloseWindow<MyWindow>();
```
---
## 二、事件绑定
| 事件类型 | 绑定位置 | 是否需要 `-=` |
|---------|---------|--------------|
| UI 按钮/拖拽 | `OnRunAwake` | 不需要 |
| 全局 TC 事件 | `OnOpen`/`OnClose` | 需要 |
---
## 三、常用组件
### 3.1 按钮
```csharp
SFUI_Button btn = GetComponent<SFUI_Button>();
btn.OnAddClickListener += OnBtnClick;
btn.SetSelfGray(true);
```
### 3.2 文本
```csharp
SFUI_TextMeshProUGUI txt = GetComponent<SFUI_TextMeshProUGUI>();
txt.text = "Hello";
```
### 3.3 图片
```csharp
SFUI_Image img = GetComponent<SFUI_Image>();
img.HFSetSprite(/* atlasName */, "Icon_Player", /* languageRoot */);
```
### 3.4 滚动列表(核心)
**生命周期正确划分示例**
```csharp
private SFUI_ScrollRect m_ScrollList;
private List<ItemData> m_ItemList;
protected override void OnRunAwake()
{
base.OnRunAwake();
m_ScrollList = FindUICtrlByName<SFUI_ScrollRect>("ScrollList");
// ✅ 绑定事件放在 Awake一次绑定永久生效
m_ScrollList.BindEvent(
() => m_ItemList.Count, // 数据源闭包
(idx) => itemPrefab.gameObject, // 生成预制体
OnUpdateItem, // 更新回调
(item) => item.gameObject.HFGetOrAddSimulateBehaviour<ItemSlot>() // 获取组件
);
}
protected override void OnOpen(Action _onOpenComplete)
{
base.OnOpen(_onOpenComplete);
m_ItemList = GetDataFromServer(); // 获取最新数据
m_ScrollList.ShowView(); // ✅ 刷新显示放在 Open
}
private void OnUpdateItem(int index, AbsHFUIWindow window)
{
ItemSlot slot = window as ItemSlot;
slot.SetData(m_ItemList[index]);
}
```
**设计要点**
- `BindEvent``OnRunAwake` 中执行,建立绑定机制
- `ShowView``OnOpen` 中执行,触发数据刷新
- 数据源通过闭包引用,`ShowView` 时自动获取最新数据
---
## 四、对象池
| 场景 | 推荐方式 |
|-----|---------|
| 滚动列表 | `SFUI_ScrollRect.BindEvent`(内置池) |
| 固定栏(装备/技能) | `HFSimulateBehaviourPool<T>` |
---
## 五、子组件
### 5.1 多态基类
```csharp
public abstract class AbsListItem<T> : AbsHFUIWindow
{
protected SFUI_Button m_BtnItem;
protected Action<T> m_OnSelect;
protected override void OnRunAwake()
protected override void OnClose()
{
base.OnRunAwake();
m_BtnItem = gameObject.GetComponent<SFUI_Button>();
m_BtnItem.OnAddClickListener += OnBtnClick;
// 移除事件和计时器。
base.OnClose();
}
public virtual void SetData(T data, Action<T> onSelect = null)
{
m_OnSelect = onSelect;
OnDataChanged(data);
}
protected abstract void OnDataChanged(T data);
private void OnBtnClick(GameObject go) => m_OnSelect?.Invoke(m_CurrentData);
}
// 子类
public class CardItem : AbsListItem<CardData>
{
protected override void OnDataChanged(CardData data) { }
}
```
---
业务打开窗口使用:
## 六、开发规范
```csharp
HFFunCatalogue.UIManager.OpenWindow<ExampleWindow>();
```
### 6.1 文件路径
不要直接实例化窗口 Prefab也不要直接调用内部 `HFUIWindowMgr` 单例。
#### 6.1.1 核心资源路径
## 二、生命周期规则
| 类型 | 路径 | 说明 |
|-----|------|------|
| **窗口脚本** | `Assets/Game/GameHFScripte/GameFunction/UIWindows/{WindowName}/` | 窗口逻辑脚本存放目录 |
| **窗口预制体** | `Assets/Game/GameHFResource/CN/UIWindows/{WindowName}/` | UI 窗口预制体存放目录 |
| **图标图集** | `Assets/Game/GameHFResource/CN/SpriteAtlas/{AtlasName}/` | SpriteAtlas 图集资源 |
| **字体资源** | `Assets/Game/GameHFResource/CN/Fonts/` | 字体文件存放目录 |
- `OnRunAwake`:实例首次绑定时执行一次,适合控件查找、按钮绑定和虚拟列表初始化。
- `OnOpen`:每次打开执行,适合注册事件、应用参数和刷新数据数量。
- `OnClose`:每次关闭执行,与 `OnOpen` 中的监听、计时器和临时状态对称清理。
- 列表初始化不能依赖数据已经到达;先以数量 `0` 初始化,数据准备后再刷新数量。
#### 6.1.2 工具脚本路径
## 三、单列虚拟列表
| 工具名称 | 路径 | 职责 |
|---------|------|------|
| **UIPrefabGenerator** | `Assets/Editor/Command/UIPrefabGenerator.cs` | JSON 转 UI 预制体 |
| **UIPrefabToJson** | `Assets/Editor/Command/UIPrefabToJson.cs` | 预制体转 JSON |
| **RemoteCommand** | `Assets/Editor/Command/RemoteCommand.cs` | 外部命令接口 |
| **ExternalCommandListener** | `Assets/Editor/Command/ExternalCommandListener.cs` | 外部命令监听器 |
单列或单行滚动使用 LoopListView2 包装接口:
#### 6.1.3 文档路径
```csharp
m_List.SetLoopListViewItemPrefabs(
new GameObject[] { m_ItemTemplate.gameObject }, 0, 1);
| 文档名称 | 路径 |
|---------|------|
| UI 开发指南 | [UI窗口系统/开发指南.md](开发指南.md) |
| UI 界面拼接设计指南 | [UI窗口系统/界面拼接.md](界面拼接.md) |
m_List.InitListView(
0,
OnGetItem,
OnCreateItem,
OnUpdateItem,
OnGetPrefabIndex,
false);
```
#### 6.1.4 命令触发路径
数据准备或变化后:
- **触发文件**: `{ProjectRoot}/Trigger_Command.txt`
- **JSON 配置**: `Assets/Editor/UI/{WindowName}.json`
```csharp
m_List.RefreshDataTotalCount(m_DataList == null ? 0 : m_DataList.Count);
```
### 6.2 性能优化
- 使用对象池复用 UI 元素
- 将图标打包到 SpriteAtlas
- 滚动列表用 `SFUI_ScrollRect.BindEvent`
回调职责:
---
| 回调 | 职责 |
|---|---|
| `OnGetItem` | 从已创建节点取得 `SFUI_ScrollRectItem` |
| `OnCreateItem` | 首次创建绑定类和 SimulateBehaviour |
| `OnUpdateItem` | 根据 dataIndex 更新显示,不能假设旧数据仍有效 |
| `OnGetPrefabIndex` | 多模板时返回模板索引,单模板返回 `0` |
**版本**: v1.1
**适用**: StrayFog 框架 UI 开发人员
垂直列表模板通常使用左上锚点和 pivot `(0, 1)`,宽度与 viewport 匹配;水平列表按滚动方向设置。模板尺寸或 pivot 错误会造成重叠、偏移或不可见。
---
## 四、多列虚拟列表
存在固定列数、`GridLayoutGroup` 语义或二维排布时使用 LoopGridView
```csharp
m_Grid.SetLoopGridViewItemPrefabs(
new GameObject[] { m_ItemTemplate.gameObject },
new Vector2(216, 293),
new Vector2(50, 30),
new RectOffset(55, 55, 0, 0),
4,
1);
m_Grid.InitGridView(
0,
OnGetGridItem,
OnCreateGridItem,
OnUpdateGridItem,
OnGetGridPrefabIndex);
```
刷新数量:
```csharp
m_Grid.RefreshGridDataTotalCount(m_DataList == null ? 0 : m_DataList.Count);
```
不要通过单列 ListView 模拟多列布局。Grid 的 itemSize、spacing、padding 和列数必须与 Prefab 视觉布局一致。
## 五、Prefab 设置
- 模板节点保持 `SetActive(false)`,但必须位于窗口层级中以便引用收集。
- `SFUI_ScrollRect`、viewport、content 和模板引用必须完整。
- 虚拟列表初始化会关闭旧 `LayoutGroup``ContentSizeFitter`,运行时布局由 SuperScrollView 管理。
- 不要因为这些旧组件在运行时被关闭就重新勾选或依赖它们计算虚拟列表尺寸。
- 检查模板上的 `LoopListViewItem2``LoopGridViewItem`;包装层会补组件,但 Prefab 结构仍需正确。
- UIMono 收集需要包含 inactive 子节点,不能只扫描激活节点。
## 六、验证清单
1. 构建 `SFMono.csproj` 和涉及的 `GameHF.csproj`
2. 刷新 Unity Assets 并读取 Console。
3. 检查 Prefab 的 viewport、content、模板、RectTransform 和组件启用状态。
4. 验证空数据、1 个元素、超出视口、多次刷新和重复打开。
5. 滚动后验证元素回收与复用,没有 `KeyNotFoundException`、旧数据显示或重复创建。
6. 多列列表验证最后一行不足列数时的尺寸和定位。
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [UI 窗口系统入口](README.md) | 本文件是 UI 窗口系统模块的子文档 |
| [UI 界面拼接设计指南](界面拼接.md) | 界面拼接是 UI 开发的补充设计指南 |
| [UI 拼接工具](../编辑器工具/UI拼接工具.md) | JSON 驱动 UI 生成工具说明 |
| [事件系统](../事件系统/README.md) | UI 事件订阅和释放规范 |
| [对象池与运行时对象管理](../内存对象管理/README.md) | UI 元素复用相关 |
| [资源管理](../资源管理/README.md) | UI 资源加载依赖资源管理 |
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/20-开发规则.md](../../../rules/20-开发规则.md) | UI 命名与编码规范互相引用 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
| [UI 窗口系统](README.md) | 窗口框架入口与生命周期 |
| [界面拼接](界面拼接.md) | Prefab 结构和视觉实现 |
| [UI 拼接工具](../编辑器工具/UI拼接工具.md) | JSON/Prefab 双向转换 |
| [开发规则](../../../rules/20-开发规则.md) | 通用 UI 和 C# 规则 |

View File

@@ -1,192 +1,80 @@
# UI 界面拼接设计指南
# UI 界面拼接
## 一、界面结构设计原则
## 一、输入与目标
### 1.1 模块化拆分
开始拼接前同时确认:
将界面按功能划分为独立模块:
- 设计原图、标注图或 PSD 中的尺寸、间距和层级。
- 项目现有 Sprite、SpriteAtlas、字体、材质和可复用 Prefab。
- 目标窗口名、Prefab 路径、Canvas 层级和脚本绑定方式。
- 目标分辨率、SafeArea、横竖屏和滚动区域。
| 模块类型 | 功能定位 | 示例 |
|---------|---------|------|
| **信息展示区** | 显示玩家状态、资源等静态信息 | 顶部信息栏、左侧状态栏 |
| **功能入口区** | 提供功能按钮入口 | 中央功能区、底部快捷栏 |
| **交互面板区** | 可展开/收起的功能面板 | 右侧功能面板、顶部按钮区 |
| **输入输出区** | 用户输入和系统反馈 | 聊天输入框、提示信息 |
不能只凭截图选择近似资源。设计图中的可检查对象必须使用语义正确的现有资源;缺失资源应明确记录,不能用无关图片顶替。
### 1.2 经典布局模式
## 二、推荐层级
```
┌─────────────────────────────────────────────────────┐
│ TopArea (信息展示 + 可收起功能按钮) │
├──────────┬───────────────────────┬──────────────────┤
│ Left │ │ Right │
Area │ CenterArea │ Area │
(状态) │ (核心功能入口) │ (功能面板) │
│ │ │
├──────────┴───────────────────────┴──────────────────┤
BottomArea (快捷功能 + 状态信息 + 输入) │
─────────────────────────────────────────────────────┘
```text
WindowName
├── ImgBg
├── HeaderArea
├── ContentArea
├── StaticContent
└── ListView
└── viewport
│ └── content
└── ItemTemplate (inactive)
── FooterArea
└── PopupOrEffectArea
```
---
- 按视觉和功能边界组织节点,不为每个图片增加无意义容器。
- 节点命名应能对应脚本字段和功能,例如 `Btn_Close``Txt_Title``Img_Icon`
- 复用 Item 或子面板时保持独立根节点,便于 UIMono 绑定和对象复用。
- 全屏背景使用稳定锚点;局部模块使用明确尺寸,避免依赖编辑器当前分辨率。
## 二、组件拆分方法
## 三、列表选择
### 2.1 基础组件识别
- 单列或单行滚动LoopListView2。
- 固定多列或网格LoopGridView。
- 模板节点保持禁用尺寸、pivot 和滚动方向必须匹配。
- 虚拟列表不依赖旧 LayoutGroup/ContentSizeFitter 运行时排版。
- 具体初始化接口见 [UI 开发指南](开发指南.md)。
| 组件类型 | 识别特征 | 处理方式 |
|---------|---------|---------|
| **按钮** | 可点击、有交互反馈 | 使用 `SFUI_Button` |
| **文本** | 静态/动态文字显示 | 使用 `SFUI_TextMeshProUGUI` |
| **图片** | 图标、背景图 | 使用 `SFUI_Image` |
| **进度条** | HP、MP、经验等 | 使用 `SFUI_Slider` |
| **列表** | 可滚动的项目列表 | 使用 `SFUI_ScrollRect` |
## 四、拼接流程
### 2.2 复合组件封装
1. 读取 [UnityAI README](../../../README.md) 中的 UI 入口和相关指南。
2. 检查目标 Prefab 是否已存在,导出或读取当前层级,避免覆盖有效结构。
3. 枚举目标资源目录和图集,建立设计元素到实际资源的对应关系。
4. 确定窗口根尺寸、锚点、主要功能区和列表类型。
5. 使用 Unity Editor 手工拼接,或通过 MCP 的 JSON 生成工具创建基础结构。
6. 绑定 SFUI/UIMono 组件与业务脚本,补齐交互、列表和动态数据。
7. 刷新资源并检查 Console、Prefab 序列化结构和运行时显示。
8. 对照原图检查尺寸、层级、颜色、字体、九宫格和点击区域。
将功能相关的基础组件组合为复合组件,便于复用和维护:
JSON 生成适合批量创建稳定层级和组件,不代替视觉校对、业务绑定和运行时验证。
**设计原则**
- 将同一功能模块的组件封装在一起(如玩家头像+血条+蓝条)
- 提供统一的数据设置接口
- 保持组件之间的逻辑独立性
## 五、修改已有 Prefab
---
- 先读取现有 Prefab 和脚本引用,再调整层级或组件。
- 移动、重命名节点前搜索 `FindUICtrlByName`、序列化字段和生成绑定代码。
- 只修改目标节点,避免让 Unity 重序列化无关 Prefab。
- 组件勾选状态属于 Prefab 行为的一部分;修复显示问题时同时检查 `enabled` 与 GameObject active。
- 列表迁移后,旧布局组件可保留序列化参数作为参考,但运行时由新列表包装关闭。
## 三、预制体层级规范
## 六、验收
### 3.1 标准层级结构
```
WindowName (RectTransform)
├── ModuleName (RectTransform) # 模块容器
│ ├── ComponentName (SFUI_*) # 功能组件
│ ├── SubModule (RectTransform) # 子模块
│ └── CollapsibleArea (RectTransform) # 可收起区域
└── BtnCollapse (SFUI_Button) # 收起控制按钮
```
### 3.2 命名规范
| 类型 | 命名规则 | 示例 |
|-----|---------|------|
| 模块容器 | PascalCase + Area | `TopArea` |
| 功能组件 | PascalCase | `BtnCollapseTop` |
| 可收起区域 | PascalCase + FuncArea | `TopFuncArea` |
| 状态变量 | m_ + PascalCase | `m_IsTopCollapsed` |
---
## 四、收起/展开功能设计
### 4.1 功能定义
| 按钮 | 控制区域 | 用途 |
|-----|---------|------|
| `BtnCollapseTop` | 顶部功能按钮区 | 收起/展开顶部按钮群 |
| `BtnCollapseRight` | 右侧功能面板区 | 收起/展开右侧功能面板 |
### 4.2 设计要点
**状态管理**:每个可收起区域需要一个布尔状态变量来记录当前收起/展开状态。
**交互逻辑**
1. 点击收起按钮时,切换对应区域的显示/隐藏状态
2. 同时切换按钮图标(收起时显示展开图标,展开时显示收起图标)
**布局要求**
- 收起按钮应放置在对应区域的边缘位置,便于用户找到
- 收起区域内的所有元素应作为一个整体进行显示/隐藏控制
- 收起状态应在窗口生命周期内保持记忆
---
## 五、实际案例:主界面拼接
### 5.1 模块化设计思想
**核心原则**:将界面按功能边界划分为独立模块,模块内可嵌套子模块。
**模块归类方法**
1. **一级模块**:按空间位置划分(顶部、左侧、中央、右侧、底部)
2. **二级模块**:按功能类型划分(信息展示区、功能按钮区、输入输出区)
3. **三级模块**:按具体功能划分(玩家头像、资源显示、功能按钮群)
**层级关系示例**
```
Window
├── TopArea (一级:位置)
│ ├── InfoPanel (二级:信息展示)
│ │ └── TxtWelcome (三级:具体组件)
│ └── FuncButtons (二级:功能按钮)
│ └── BtnUpload (三级:具体按钮)
└── LeftArea (一级:位置)
└── PlayerPortrait (二级:复合组件)
├── ImgAvatar (三级:头像)
└── BarHP (三级:血条)
```
**设计要点**
- 同一功能域内的元素归为同一模块(如头像、血条、蓝条归为玩家状态模块)
- 独立交互功能作为独立模块(如收起按钮单独作为控制模块)
- 可收起区域作为独立子模块,便于统一控制显示/隐藏
---
## 六、界面拼接流程
### 6.1 设计阶段
1. **需求分析**:明确界面功能和交互需求
2. **模块划分**:将界面拆分为独立功能模块
3. **层级设计**:设计预制体层级结构
### 6.2 开发阶段
1. **组件创建**:创建基础组件和复合组件
2. **预制体搭建**:按层级结构搭建预制体
3. **脚本编写**:实现交互逻辑和收起/展开功能
### 6.3 测试阶段
1. **功能测试**:验证所有按钮和交互功能
2. **收起测试**:测试收起/展开功能正常工作
3. **适配测试**:验证不同分辨率下的显示效果
---
## 七、注意事项
1. **组件复用**:相同功能的组件尽量复用,减少冗余
2. **状态管理**:收起状态需要正确保存和恢复
3. **图标反馈**:收起按钮图标应随状态变化
4. **性能优化**:可收起区域使用 `SetActive` 而非销毁重建
5. **命名规范**:统一的命名规则便于维护和查找
---
## 八、JSON 配置驱动的 UI 生成方式
本项目支持通过 JSON 配置快速生成 UI 预制体,详细工具说明、命令格式和部署步骤见 [UI 拼接工具](../编辑器工具/UI拼接工具.md)。
UI 相关人员核心工作流程:读取设计效果图和碎片图片,生成 JSON 配置文件,再通过工具自动生成 Unity 预制体。
---
**版本**: v1.2
**适用**: StrayFog 框架 UI 拼接开发人员
---
- Prefab 路径和窗口 attribute 一致。
- Sprite、字体、材质和图集引用有效。
- 锚点、pivot、sizeDelta 和层级与设计目标一致。
- 按钮、文本、列表、进度和弹窗交互正确。
- 空数据、极长文本、不同分辨率和重复打开不破版。
- Unity Console 无本次新增 Error运行时实际显示通过确认。
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [UI 窗口系统入口](README.md) | 本文件是 UI 窗口系统模块的子文档 |
| [UI 开发指南](开发指南.md) | 窗口开发与界面拼接互相补充 |
| [UI 拼接工具](../编辑器工具/UI拼接工具.md) | JSON 驱动 UI 生成工具说明 |
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/20-开发规则.md](../../../rules/20-开发规则.md) | UI 命名与资源规则互相引用 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
| [UI 窗口系统](README.md) | 窗口框架和资源路径 |
| [UI 开发指南](开发指南.md) | 生命周期和虚拟列表接口 |
| [UI 拼接工具](../编辑器工具/UI拼接工具.md) | MCP JSON/Prefab 工具 |
| [MCP 使用规则](../编辑器工具/MCP工具/CrystalBattle_Codex_Unity_MCP使用规则.md) | Codex 与 Unity 的调用规则 |

View File

@@ -148,7 +148,7 @@ public class ConfigManager : AbsHFSingleRunTime<ConfigManager>
**版本**: v1.0
**生效日期**: 2026年
**适用范围**: GiftGameX 项目业务开发人员
**适用范围**: 使用 StrayFog 框架的项目开发人员
---

View File

@@ -98,7 +98,7 @@ public class EventArgPool : MonoBehaviour
**版本**: v1.0
**生效日期**: 2026年
**适用范围**: GiftGameX 项目业务开发人员
**适用范围**: 使用 StrayFog 框架的项目开发人员
---

View File

@@ -83,7 +83,7 @@ HFMainCamera (主相机)
**版本**: v1.0
**生效日期**: 2026年
**适用范围**: GiftGameX 项目业务开发人员
**适用范围**: 使用 StrayFog 框架的项目开发人员
---

View File

@@ -1,176 +1,106 @@
# CrystalBattle Codex Unity MCP 使用规则
# CrystalBattle Codex Unity MCP 使用规则
## 1. 目标与定位
## 一、定位
MCPModel Context Protocol用于打通 Codex 与 Unity Editor。Codex 不直接操作 Unity 进程,而是通过本地 MCP Server 调用 Unity Editor 内的 HTTP Bridge,再由 Bridge 在 Unity 主线程执行 Editor 命令
Codex 通过本地 stdio MCP Server 调用 Unity Editor 内的 HTTP BridgeBridge 只监听 `127.0.0.1:17777`,并把 Unity API 工作排队到 Editor 主线程执行
当前首版定位:
当前能力只面向 Unity Editor 自动化,不覆盖 Play Mode、真机运行时调试或远程网络访问。
- 只支持 Unity Editor 工具桥,不覆盖 Play Mode 或真机运行时调试。
- 只监听本机 `127.0.0.1`,不开放局域网访问。
- 优先服务 UI 生成、Prefab 导出、资源刷新、日志读取等 Editor 自动化流程。
- Proxima 暂不作为 MCP 主通道,后续如做运行时调试再单独规划。
## 2. 工程结构
本文档只说明 Codex 如何对接 Unity MCP不说明 Unity Editor 内部脚本目录结构,也不在本节维护数据目录。
Codex 需要知道的对接点:
## 二、对接点
```text
Unity MCP Server: UnityAI/Tools/CodexUnityMcp/
MCP Server: UnityAI/Tools/CodexUnityMcp/
Codex config: .codex/config.toml
Unity Bridge URL: http://127.0.0.1:17777
Unity Bridge: http://127.0.0.1:17777
```
## 3. 启动与连接
本文档不维护 Unity Bridge 的打开方式或 Unity Editor 内部目录结构。
1. 确认 `.codex/config.toml` 中存在 `crystalbattle-unity` MCP server 配置。
2. 确认 MCP Server 入口指向 `UnityAI/Tools/CodexUnityMcp/src/server.mjs`
3. Codex 侧重启或新开线程后,会重新读取 MCP server 配置。
4. 每轮 Unity 自动化前,优先调用 `unity_health` 检查连通性。
5. 如果 `unity_health` 不通,只记录错误并停止后续 Unity 操作Unity 侧如何打开或重启 Bridge 不在本文档说明范围内。
## 三、可用工具
禁止事项:
| 工具 | 用途 |
|---|---|
| `unity_health` | 检查 Bridge、项目路径和 Unity 版本 |
| `unity_refresh_assets` | 执行 `AssetDatabase.Refresh()` |
| `unity_execute_menu_item` | 执行 Bridge 白名单中的菜单项 |
| `unity_get_console_logs` | 读取最近 Unity Console 日志 |
| `unity_generate_ui_prefab` | 根据 JSON 生成 UI Prefab |
| `unity_export_prefab_to_json` | 将 UI Prefab 导出为 JSON |
- 不允许把 Bridge 地址改成 `0.0.0.0` 或局域网 IP
- 不允许绕过 MCP 直接让 Codex 执行 Unity Editor 侧业务逻辑。
- 不允许在 Unity 后台线程直接调用 Unity API所有 Unity API 必须回到 Editor 主线程执行。
`unity_execute_menu_item` 不能执行任意菜单。新增菜单能力时必须同时修改 Bridge 白名单并验证风险
## 4. Codex 可用工具
## 四、标准调用流程
| 工具名 | 用途 | 规则 |
|---|---|---|
| `unity_health` | 检查 Unity Bridge 是否在线 | 每轮 Unity 自动化前优先调用 |
| `unity_refresh_assets` | 执行 `AssetDatabase.Refresh()` | 生成或导出资源后可调用 |
| `unity_execute_menu_item` | 执行白名单菜单项 | 只能执行 Bridge 内白名单 |
| `unity_get_console_logs` | 读取最近 Unity Console 日志 | 生成失败或验证结果时必须查看 |
| `unity_generate_ui_prefab` | 根据 JSON 生成 UI Prefab | 输入 JSON 必须使用 Unity 工程相对路径或绝对路径 |
| `unity_export_prefab_to_json` | 从 UI Prefab 导出 JSON | Prefab 路径必须是 Unity Asset 路径 |
1. 每轮 Unity 自动化前先调用 `unity_health`
2. 确认返回的项目路径就是当前目标工程。
3. 执行当前任务需要的最少工具调用。
4. 资源变化后调用 `unity_refresh_assets`
5. 调用 `unity_get_console_logs` 检查新增 Error 和关键 Warning。
6. Prefab/UI 继续做结构和实际显示验证,不能只确认命令返回成功。
## 5. MCP 数据目录规则
`unity_health` 失败时停止后续 Unity 命令并报告错误,不假设 Bridge 已经执行任何操作。
所有 MCP 数据必须按功能单独放在 Unity Editor 目录下,并直接按目标预制体名分文件夹;不再增加中间层目录。统一格式:
## 五、UI 生成
```text
Assets/Editor/MCP_{功能名}/<PrefabName>/
```
UI 任务必须先从 [UnityAI README](../../../../README.md) 进入 UI 文档,并阅读:
命名要求:
- [UI 开发指南](../../UI窗口系统/开发指南.md)
- [UI 界面拼接](../../UI窗口系统/界面拼接.md)
- [UI 拼接工具](../UI拼接工具.md)
- `{功能名}` 只表示功能类别,不写具体窗口名、临时任务名或个人名。
- `<PrefabName>` 必须沿用目标预制体名称,和最终生成的 `<PrefabName>.prefab` 保持一致。
- UI 相关数据统一放在 `Assets/Editor/MCP_UI/<PrefabName>/`
- 其它功能按能力命名,例如 `Assets/Editor/MCP_Config/<PrefabName>/``Assets/Editor/MCP_Map/<PrefabName>/`
- 新增、导出、临时验证用 JSON 都必须遵守该目录规则。
随后按以下步骤执行:
整理旧数据时,应迁移到 `Assets/Editor/MCP_UI/<PrefabName>/` 或对应功能目录
1. 检查设计图、目标 Prefab、窗口脚本和现有资源
2. 明确传入 JSON 路径和目标 Prefab 路径。
3. 调用 `unity_generate_ui_prefab`
4. 刷新 Assets 并读取 Console。
5. 检查 Prefab 层级、RectTransform、组件状态和资源引用。
6. 需要核对双向转换时调用 `unity_export_prefab_to_json`
7. 在 Unity 运行环境验证实际显示和交互。
## 6. UI 生成规则
不规定固定测试目录、测试窗口名或中间数据目录。任务产生的路径由调用方明确提供,覆盖正式 Prefab 前必须确认。
标准流程:
## 六、安全边界
1. 先读取 `UnityAI/README.md` 中的 UI 相关索引
2. 根据 README 的 UI 相关入口,优先阅读:
- `UnityAI/modules/frameworks/UI窗口系统/开发指南.md`
- `UnityAI/modules/frameworks/UI窗口系统/界面拼接.md`
3. 需要使用 JSON 与 Prefab 双向转换时,再阅读 README 中的 `编辑器工具脚本` 入口:
- `UnityAI/modules/frameworks/编辑器工具/UI工具/`
4. 按 UI 指南确认窗口命名、层级、资源路径和布局规则后,准备 UI JSON。
5. 调用 `unity_generate_ui_prefab`
6. 目标 Prefab 路径优先使用:
- Bridge 只监听 `127.0.0.1`,禁止改为 `0.0.0.0` 或局域网地址
- MCP Server 不直接操作 Unity 资源,资源变更交给 Unity Bridge。
- HTTP 线程只接收和返回数据Unity API 回到主线程执行。
- 不开放任意菜单、任意 C# 执行、批量删除、构建发布或 Git 操作。
- 生成和导出只修改调用参数明确指定的目标。
- 日志和错误返回包含命令、路径和失败原因,不返回无上下文的“失败”。
```text
Assets/Game/GameHFResource/CN/UIWindows/<WindowName>/<WindowName>.prefab
```
## 七、排错
7. 调用 `unity_get_console_logs` 查看是否有 Error 或关键 Warning。
8. 如需要验证双向转换,再调用 `unity_export_prefab_to_json`
### 连接失败
JSON 约束:
1. 调用 `unity_health` 保留原始错误。
2. 检查 `.codex/config.toml` 的 Node command 和 server 入口。
3. 确认 `Tools/CodexUnityMcp` 依赖已经安装。
4. 连接恢复前不执行其他 Unity 工具。
- 根对象使用 `UILayoutData` 结构:`name/type/layer/rectTransform/canvas/children`
- UI 节点使用 `UIElementData` 结构:`name/type/rectTransform/image/button/text/.../children`
- 资源路径使用 Unity Asset 路径,例如 `Assets/...`
- 测试 UI 命名必须带明确测试标识,例如 `CodexMcpTestWindow`
### 端口占用
当前注意事项:
- Bridge 如果发现同端口已经存在有效服务,应连接到现有监听状态而不是重复启动。
- 不能通过同时启动多个监听器解决端口冲突。
- 状态仍不一致时记录端口、进程和 Bridge 状态,再处理占用来源。
- `UnityEngine.JsonUtility` 解析递归 `children` 结构时可能出现 serialization depth warning。
- 如果 Prefab 已成功生成且没有 Error该 warning 暂不阻塞测试。
- 后续如大量使用 UI JSON应考虑把解析器替换为更适合递归结构的 JSON 库。
### UI 生成失败
## 7. 安全与权限
1. 读取 Unity Console。
2. 检查 JSON 和目标路径是否存在且格式正确。
3. 检查 Sprite、Font、Material 等 Asset 路径。
4. 检查 `rectTransform`、递归 `children` 和组件字段。
5. 检查目标 Prefab 是否被错误覆盖或无法写入。
MCP 工具可以触发 Unity Editor 行为,因此必须保持最小权限:
## 八、维护
- `execute_menu_item` 必须使用白名单,禁止开放任意菜单执行。
- 只允许监听 `127.0.0.1`
- 不在 MCP Server 中直接读写 Unity 资源,资源变更统一交给 Unity Bridge 执行。
- 生成测试资源时使用独立测试目录或明确测试命名,避免覆盖正式 UI。
- 覆盖已有 Prefab 前必须确认目标路径是否为预期文件。
- 不通过 MCP 执行批量删除、批量移动、构建发布、Git 操作等高风险动作。
新增 MCP tool 时同步更新:
## 8. 排错流程
- `Tools/CodexUnityMcp/src/server.mjs`
- Unity Bridge 对应命令
- 本文档工具列表和验证步骤
连接失败
1. 调用 `unity_health` 获取错误信息。
2. 用浏览器或 PowerShell 检查:
```powershell
Invoke-WebRequest -UseBasicParsing "http://127.0.0.1:17777/health"
```
3. 如果连接仍失败,停止后续 Unity 操作并记录错误Unity 侧启动、重启、面板操作不在本文档说明范围内。
MCP 工具不可见:
1. 确认 `UnityAI/Tools/CodexUnityMcp/node_modules` 已安装。
2. 确认 `.codex/config.toml``command``args` 路径有效。
3. 重启 Codex让 MCP server 配置重新加载。
UI 生成失败:
1. 调用 `unity_get_console_logs` 查看 Unity Console。
2. 检查 JSON 文件是否存在。
3. 检查 JSON 是否位于 `Assets/Editor/MCP_UI/<PrefabName>/`
4. 检查 `rectTransform` 数组字段是否齐全。
5. 检查 Sprite、Font、Material 等资源路径是否存在。
6. 确认 Prefab 目标目录可写且未被外部程序锁定。
## 9. 测试基准
每次修改 MCP 或 Unity Bridge 后,至少验证:
- `unity_health` 返回 `ok=true`
- `unity_get_console_logs` 能返回日志。
- `unity_generate_ui_prefab` 能生成一个最小测试 Prefab。
- 生成的 Prefab 文件存在,并包含预期节点名。
- `unity_export_prefab_to_json` 能把测试 Prefab 导出 JSON。
- Unity Console 无新增 Error。
UI 测试样例推荐路径:
```text
Assets/Editor/MCP_UI/CodexMcpTestWindow/CodexMcpTestWindow.json
Assets/Game/GameHFResource/CN/UIWindows/CodexMcpTestWindow/CodexMcpTestWindow.prefab
Assets/Editor/MCP_UI/CodexMcpTestWindow/CodexMcpTestWindow.exported.json
```
## 10. 维护规则
新增 MCP tool 时必须同步更新:
- `UnityAI/Tools/CodexUnityMcp/src/server.mjs`
- Unity Bridge 侧对应命令代码
- 本文档的工具列表与测试基准
新增 Unity 命令时必须满足:
- HTTP 请求只负责收包和回包。
- Unity API 在 `EditorApplication.update` 主线程队列执行。
- 返回值使用统一结构:
Bridge 返回统一结构
```json
{
@@ -181,25 +111,13 @@ Assets/Editor/MCP_UI/CodexMcpTestWindow/CodexMcpTestWindow.exported.json
}
```
文档版本:
- v1.4
- 生效日期2026-07-03
- 适用项目CrystalBattle_Client
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [MCP 工具入口](README.md) | 本文件是 MCP 工具模块的子文档 |
| [UI 开发指南](../../UI窗口系统/开发指南.md) | UI 生成规则参考 |
| [UI 界面拼接设计指南](../../UI窗口系统/界面拼接.md) | UI 布局规则参考 |
| [UI 拼接工具](../UI拼接工具.md) | JSON/Prefab 双向转换工具说明 |
| [Tools/CodexUnityMcp](../../../../Tools/CodexUnityMcp/README.md) | MCP Server 实现 |
| [modules/模块索引.md](../../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../../rules/10-架构说明.md) | 编辑器工具层的架构说明 |
| [rules/30-模块登记规则.md](../../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
| [MCP 工具入口](README.md) | MCP 模块入口 |
| [UI 开发指南](../../UI窗口系统/开发指南.md) | UI 生命周期和列表规则 |
| [UI 界面拼接](../../UI窗口系统/界面拼接.md) | Prefab 拼接流程 |
| [UI 拼接工具](../UI拼接工具.md) | JSON/Prefab 工具结构 |
| [MCP Server README](../../../../Tools/CodexUnityMcp/README.md) | Server 安装和实现 |
| [工作流清单](../../../../rules/40-工作流清单.md) | Unity/MCP 验证流程 |

View File

@@ -1,28 +1,31 @@
# MCP工具
# MCP 工具
本目录用于沉淀 Codex 与 Unity Editor 打通相关的规则、工具说明和排错流程
Codex 通过本地 Node MCP Server 与 Unity Editor Bridge 交互,当前实现已经在 CrystalBattle_Client 验证
当前文档:
## 实现位置
- [CrystalBattle Codex ↔ Unity MCP 使用规则](CrystalBattle_Codex_Unity_MCP使用规则.md)
当前实现位置:
- Unity Editor Bridge`CrystalBattle_Client/Assets/Editor/Command/`
- MCP Server`UnityAI/Tools/CodexUnityMcp/`
- Codex 配置:`.codex/config.toml`
- Unity Editor Bridge目标项目 `Assets/Editor/Command/`
- Codex 配置:工作区 `.codex/config.toml`
- 本地地址:`http://127.0.0.1:17777`
---
## 能力
- Unity 健康检查
- AssetDatabase 刷新
- 白名单菜单执行
- Console 日志读取
- UI JSON 生成 Prefab
- Prefab 导出 JSON
具体调用、UI 前置阅读、安全边界和排错见 [CrystalBattle Codex 与 Unity MCP 使用规则](CrystalBattle_Codex_Unity_MCP使用规则.md)。
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [编辑器工具入口](../README.md) | 本文件是编辑器工具模块的子文档 |
| [CrystalBattle Codex ↔ Unity MCP 使用规则](CrystalBattle_Codex_Unity_MCP使用规则.md) | 具体 MCP 使用规 |
| [Tools/CodexUnityMcp](../../../../Tools/CodexUnityMcp/README.md) | MCP Server 实现 |
| [modules/模块索引.md](../../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../../rules/10-架构说明.md) | 编辑器工具层的架构说明 |
| [rules/30-模块登记规则.md](../../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
| [编辑器工具入口](../README.md) | 编辑器工具模块入口 |
| [MCP 使用规则](CrystalBattle_Codex_Unity_MCP使用规则.md) | 具体用规 |
| [MCP Server README](../../../../Tools/CodexUnityMcp/README.md) | 安装和实现 |
| [模块索引](../../../模块索引.md) | 模块状态 |
| [工作流清单](../../../../rules/40-工作流清单.md) | Unity 自动化验证步骤 |

View File

@@ -1,61 +1,36 @@
# 编辑器工具
## 分类
- 编辑器工具
## 目的
汇总 Unity Editor 内使用的 StrayFog 相关编辑器扩展和项目编辑器资源。
---
## SF Editor
- **路径**`Assets/StrayFog/Editor/`
- **程序集**`SFEditor`
- **职责**
- StrayFog 编辑器扩展
- 打包、UGUI、SpriteAtlas、表格路径选择等编辑器工具
- 路径`Assets/StrayFog/Editor/`
- 程序集:`SFEditor`
- 职责CopyToX、AssetBundle、SpriteAtlas、配置生成和 StrayFog Editor 窗口。
---
`Assets/StrayFog/Editor/CopyToX` 保存生成运行时代码的真实源文件。生成后的 GameHF/GameGeneral 目标目录禁止手改。
## Project Editor Assets
- **路径**`Assets/Editor/`
- **职责**
- 项目编辑器资源与脚本
- 引导菜单、编辑器插件
---
- 路径:目标项目 `Assets/Editor/`
- 职责:项目级 Editor 菜单、构建辅助、UI 转换和 Unity MCP Bridge。
## 使用规则
- 编辑器工具可以读取/生成核心运行时代码,但运行时代码不要反向依赖 Editor 程序集
- 新增 Editor 工具时,优先在 `Assets/StrayFog/Editor/``Assets/Editor/` 下按功能分类存放
---
## 相关模块
- [通用框架](../通用框架/README.md)
- [资源管理](../资源管理/README.md)
- Unity Editor API 只在主线程执行
- 运行时代码不得引用 Editor 程序集
- 生成器修改后同时验证输入配置、生成差异、目标程序集和 Unity Console。
- 新增菜单时使用稳定路径MCP 可调用菜单必须进入显式白名单。
- 编辑器工具不能把机器绝对路径、个人目录或临时数据目录写成通用规则。
## 子文档
- [UI 拼接工具](UI拼接工具.md)
- [MCP 工具](MCP工具/README.md)
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [modules/frameworks/编辑器工具/UI拼接工具.md](UI拼接工具.md) | UI 预制体 JSON 双向转换工具 |
| [modules/frameworks/编辑器工具/MCP工具/README.md](MCP工具/README.md) | Codex ↔ Unity MCP 工具 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 编辑器工具层的架构说明 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
| [模块索引](../../模块索引.md) | 编辑器工具入口和状态 |
| [资源管理](../资源管理/README.md) | AssetBundle 与分包生成 |
| [架构说明](../../../rules/10-架构说明.md) | Editor 与运行时边界 |
| [工作流清单](../../../rules/40-工作流清单.md) | 生成和验证流程 |

View File

@@ -1,176 +0,0 @@
using UnityEditor;
using UnityEngine;
using System.IO;
[InitializeOnLoad]
public static class ExternalCommandListener
{
private static FileSystemWatcher watcher;
private static string triggerFilePath;
private static bool isInitialized = false;
static ExternalCommandListener()
{
EditorApplication.update += InitializeOnFirstUpdate;
}
private static void InitializeOnFirstUpdate()
{
if (!isInitialized)
{
isInitialized = true;
EditorApplication.update -= InitializeOnFirstUpdate;
SetupFileWatcher();
Debug.Log("ExternalCommandListener started");
}
}
private static void SetupFileWatcher()
{
triggerFilePath = Path.Combine(Application.dataPath, "..", "Trigger_Command.txt");
string watchFolder = Path.GetDirectoryName(triggerFilePath);
if (watcher != null) watcher.Dispose();
watcher = new FileSystemWatcher(watchFolder, "Trigger_Command.txt");
watcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite;
watcher.Created += OnFileChanged;
watcher.Changed += OnFileChanged;
watcher.EnableRaisingEvents = true;
}
private static void OnFileChanged(object sender, FileSystemEventArgs e)
{
if (e.ChangeType != WatcherChangeTypes.Created && e.ChangeType != WatcherChangeTypes.Changed)
return;
EditorApplication.delayCall += ExecuteCommandFromFile;
}
private static void ExecuteCommandFromFile()
{
try
{
if (!File.Exists(triggerFilePath)) return;
string content = File.ReadAllText(triggerFilePath).Trim();
File.Delete(triggerFilePath);
ParseAndExecute(content);
}
catch (System.Exception ex)
{
Debug.LogError("Execute failed: " + ex.Message);
}
}
private static void ParseAndExecute(string content)
{
content = content.Trim().Trim('"', '\'');
string[] parts = content.Split(':');
if (parts.Length == 0) return;
string command = parts[0].Trim();
string[] args = parts.Length > 1 ? parts[1..] : new string[0];
ExecuteCommand(command, args);
}
private static void ExecuteCommand(string command, string[] args)
{
switch (command)
{
case "ReadJson":
ExecuteReadJson(args);
break;
case "GenerateUIPrefab":
ExecuteGenerateUIPrefab(args);
break;
case "ExportPrefabToJson":
ExecuteExportPrefabToJson(args);
break;
default:
Debug.LogWarning("Unknown command: " + command);
break;
}
}
private static void ExecuteReadJson(string[] args)
{
try
{
var method = typeof(RemoteCommand).GetMethod("ReadJson",
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static,
null,
new[] { typeof(string[]) },
null);
if (method != null)
{
method.Invoke(null, new object[] { args });
Debug.Log("Executed: ReadJson(" + string.Join(", ", args) + ")");
}
else
{
Debug.LogError("RemoteCommand.ReadJson not found");
}
}
catch (System.Exception ex)
{
Debug.LogError("ReadJson failed: " + ex.Message);
}
}
private static void ExecuteGenerateUIPrefab(string[] args)
{
try
{
var method = typeof(RemoteCommand).GetMethod("GenerateUIPrefab",
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static,
null,
new[] { typeof(string[]) },
null);
if (method != null)
{
method.Invoke(null, new object[] { args });
Debug.Log("Executed: GenerateUIPrefab(" + string.Join(", ", args) + ")");
}
else
{
Debug.LogError("RemoteCommand.GenerateUIPrefab not found");
}
}
catch (System.Exception ex)
{
Debug.LogError("GenerateUIPrefab failed: " + ex.Message);
}
}
private static void ExecuteExportPrefabToJson(string[] args)
{
try
{
var method = typeof(RemoteCommand).GetMethod("ExportPrefabToJson",
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static,
null,
new[] { typeof(string[]) },
null);
if (method != null)
{
method.Invoke(null, new object[] { args });
Debug.Log("Executed: ExportPrefabToJson(" + string.Join(", ", args) + ")");
}
else
{
Debug.LogError("RemoteCommand.ExportPrefabToJson not found");
}
}
catch (System.Exception ex)
{
Debug.LogError("ExportPrefabToJson failed: " + ex.Message);
}
}
}

View File

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

View File

@@ -1,39 +0,0 @@
public static class RemoteCommand
{
public static void ReadJson(string[] args)
{
SFP.Error("收到参数: " + string.Join(", ", args));
}
public static void GenerateUIPrefab(string[] args)
{
if (args.Length == 0)
{
SFP.Error("GenerateUIPrefab: 需要提供JSON文件路径参数");
return;
}
string jsonPath = args[0];
string prefabPath = args.Length > 1 ? args[1] : null;
SFP.Error("GenerateUIPrefab: 开始生成UI, JSON路径: " + jsonPath);
UIPrefabGenerator.GeneratePrefabFromJson(jsonPath, prefabPath);
}
public static void ExportPrefabToJson(string[] args)
{
if (args.Length == 0)
{
SFP.Error("ExportPrefabToJson: 需要提供预制体文件路径参数");
return;
}
string prefabPath = args[0];
string jsonPath = args.Length > 1 ? args[1] : null;
SFP.Error("ExportPrefabToJson: 开始导出JSON, 预制体路径: " + prefabPath);
UIPrefabToJson.ExportPrefabToJson(prefabPath, jsonPath);
}
}

View File

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

View File

@@ -1,248 +1,104 @@
# UI 拼接工具
## 概述
## 目的
UI 拼接工具提供 JSON 配置与 Unity 预制体的双向转换,帮助 UI 相关人员通过 JSON 配置文件快速生成 UI 预制体,同时支持从现有预制体反向导出 JSON 用于版本控制或重新生成
UI 拼接工具通过 JSON 与 Unity Prefab 双向转换,帮助 Codex 和开发人员创建基础 UI 层级、检查现有 Prefab并继续在 Unity 中完成业务绑定和视觉校对
## 核心组件
## 当前实现
| 组件 | 职责 | 文件路径 |
|-----|------|---------|
| **UIPrefabGenerator** | 解析 JSON 并生成 UI 预制体 | `Assets/Editor/Command/UIPrefabGenerator.cs` |
| **UIPrefabToJson** | 从预制体反向导出 JSON | `Assets/Editor/Command/UIPrefabToJson.cs` |
| **RemoteCommand** | 提供外部调用接口 | `Assets/Editor/Command/RemoteCommand.cs` |
| **ExternalCommandListener** | 监听外部命令触发文件 | `Assets/Editor/Command/ExternalCommandListener.cs` |
| 组件 | 位置 | 职责 |
|---|---|---|
| `UIPrefabGenerator` | `Assets/Editor/Command/UIPrefabGenerator.cs` | 读取 UI JSON 并生成 Prefab |
| `UIPrefabToJson` | `Assets/Editor/Command/UIPrefabToJson.cs` | 导出 Prefab 的层级和组件数据 |
| Unity Bridge | `Assets/Editor/Command/CodexUnityEditorBridge.cs` | 在 Unity 主线程执行生成和导出命令 |
| MCP Server | `UnityAI/Tools/CodexUnityMcp/` | 把 Unity Bridge 命令暴露给 Codex |
工具脚本在文档库中的位置(已并入编辑器工具模块):
当前外部入口是 MCP Bridge不使用文件监听或文本触发命令。
```
modules/frameworks/编辑器工具/UI工具/
├── ExternalCommandListener.cs # 外部命令监听器
├── RemoteCommand.cs # 远程命令接口
├── UIPrefabGenerator.cs # JSON 转预制体生成器
└── UIPrefabToJson.cs # 预制体转 JSON 导出器
```
## 数据结构
## JSON 配置文件结构
根节点使用 `UILayoutData`
```json
{
"name": "BagWindow",
"width": 1024,
"height": 768,
"elements": [
{
"type": "Image",
"name": "BgPanel",
"anchorMin": [0, 0],
"anchorMax": [1, 1],
"position": [0, 0, 0],
"sizeDelta": [0, 0],
"sprite": "Assets/Game/GameHFResource/CN/UIWindows/BagWindow/Sprites/Bg.png"
},
{
"type": "Text",
"name": "TitleText",
"anchorMin": [0.5, 0.9],
"anchorMax": [0.5, 0.9],
"position": [0, 0, 0],
"sizeDelta": [200, 40],
"text": "背包",
"font": "Assets/Game/GameHFResource/CN/Fonts/FZY4K-M05S SDF.asset",
"fontSize": 28,
"color": [1, 1, 1, 1]
}
]
"name": "ExampleWindow",
"type": "Canvas",
"layer": 5,
"rectTransform": {
"anchorMin": [0.5, 0.5],
"anchorMax": [0.5, 0.5],
"position": [0, 0, 0],
"sizeDelta": [1080, 1920],
"pivot": [0.5, 0.5],
"localScale": [1, 1, 1],
"localRotation": [0, 0, 0]
},
"children": []
}
```
## 支持的 UI 元素类型
子节点使用 `UIElementData`,可配置 `rectTransform``image``button``text``layoutGroup``scrollRect``toggle``slider``inputField``customComponents` 和递归 `children`
| 类型 | 对应 Unity 组件 | 关键属性 |
|-----|-------------|---------|
| **Image** | Image | sprite, color, type, preserveAspect |
| **TextMeshProUGUI** | TextMeshProUGUI | text, font, fontSize, color, alignment |
| **Button** | Button | interactable, transition, navigationMode |
| **Slider** | Slider | minValue, maxValue, value, wholeNumbers, direction |
| **Toggle** | Toggle | isOn, toggleTransition, transition |
| **InputField** | InputField | text, placeholder, characterLimit, contentType |
| **ScrollRect** | ScrollRect | horizontal, vertical, movementType |
| **GridLayoutGroup** | GridLayoutGroup | cellSize, spacing, constraint, padding |
| **VerticalLayoutGroup** | VerticalLayoutGroup | spacing, childAlignment, padding |
| **HorizontalLayoutGroup** | HorizontalLayoutGroup | spacing, childAlignment, padding |
## 支持组件
## 外部命令触发机制
- `Image` / `SFUI_Image`
- `Button` / `SFUI_Button`
- `TextMeshProUGUI` / `SFUI_TextMeshProUGUI`
- `RectTransform``Canvas``CanvasScaler``GraphicRaycaster`
- `ScrollRect` / `SFUI_ScrollRect`
- `SFUI_Viewport``SFUI_Content``SFUI_ListViewItem`
- `GridLayoutGroup``VerticalLayoutGroup``HorizontalLayoutGroup`
- `Toggle` / `SFUI_Toggle`
- `Slider` / `SFUI_Slider`
- `InputField` / `SFUI_InputField`
### 触发方式
资源字段使用 Unity Asset 路径,例如 `Assets/Game/...`,路径分隔符统一使用 `/`
通过创建/修改 `Trigger_Command.txt` 文件触发 UI 生成:
## MCP 调用
```
GenerateUIPrefab:Assets/Editor/UI/BagWindow.json
生成 Prefab
```text
unity_generate_ui_prefab(jsonPath, prefabPath)
```
### 命令格式
导出 Prefab
```
<命令名>:<参数1>:<参数2>:...
```text
unity_export_prefab_to_json(prefabPath, jsonPath)
```
**支持的命令**
路径由当前任务明确传入,不规定固定测试目录或中间数据目录。目标正式窗口通常位于
| 命令 | 参数 | 说明 |
|-----|------|------|
| **GenerateUIPrefab** | `jsonPath` [, `prefabPath`] | 根据 JSON 生成 UI 预制体 |
| **ExportPrefabToJson** | `prefabPath` [, `jsonPath`] | 从预制体反向导出 JSON |
| **ReadJson** | [参数列表] | 读取 JSON 配置(测试用) |
### 完整执行流程
```
┌─────────────────────────────────────────────────────────────────────┐
│ 外部系统写入命令到 Trigger_Command.txt │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ ExternalCommandListener (FileSystemWatcher) 检测到文件变化 │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 解析命令格式GenerateUIPrefab:Assets/Editor/UI/BagWindow.json │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 通过反射调用 RemoteCommand.GenerateUIPrefab(string[] args) │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ UIPrefabGenerator.GeneratePrefabFromJson(jsonPath, prefabPath) │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 生成预制体到指定路径Assets/Game/GameHFResource/CN/UIWindows/... │
└─────────────────────────────────────────────────────────────────────┘
```text
Assets/Game/GameHFResource/<语言>/UIWindows/<WindowName>/<WindowName>.prefab
```
## 使用示例
## 标准流程
### 方式一:外部命令触发
1. 先读 [UI 界面拼接](../UI窗口系统/界面拼接.md) 和 [UI 开发指南](../UI窗口系统/开发指南.md)。
2. 调用 `unity_health` 确认 Unity 项目和版本。
3. 检查目标 Prefab、窗口脚本和可用资源。
4. 生成基础 Prefab或先导出现有 Prefab 再调整 JSON。
5. 调用 `unity_refresh_assets`
6. 调用 `unity_get_console_logs` 检查 Error 和关键 Warning。
7. 在 Unity 中检查层级、RectTransform、组件、资源引用和实际显示。
```bash
# 正向:从 JSON 生成预制体
echo "GenerateUIPrefab:Assets/Editor/UI/BagWindow.json" > "F:\JMNet\CrystalBattle_Client\Trigger_Command.txt"
工具生成成功只表示 Prefab 文件已创建,不表示 UI 已完成。业务脚本、列表初始化、字体、图集、点击区域和视觉结果仍需验证。
# 反向:从预制体导出 JSON
echo "ExportPrefabToJson:Assets/Game/GameHFResource/CN/UIWindows/BagWindow/BagWindow.prefab" > "F:\JMNet\CrystalBattle_Client\Trigger_Command.txt"
```
## 安全规则
### 方式二Unity 编辑器菜单
**正向生成**
1. 打开 Unity 编辑器
2. 点击菜单 `Tools > External > Test GenerateUIPrefab`
3. 查看 Console 窗口日志确认执行结果
**反向导出**
1. 打开 Unity 编辑器
2. 点击菜单 `Tools > UI > Export Prefab to JSON`
3. 选择要导出的预制体文件
### 方式三:代码调用
```csharp
// 正向:从 JSON 生成预制体
UIPrefabGenerator.GeneratePrefabFromJson(
"Assets/Editor/UI/BagWindow.json",
"Assets/Game/GameHFResource/CN/UIWindows/BagWindow/BagWindow.prefab"
);
// 反向:从预制体导出 JSON
UIPrefabToJson.ExportPrefabToJson(
"Assets/Game/GameHFResource/CN/UIWindows/BagWindow/BagWindow.prefab",
"Assets/Editor/UI/BagWindow.json"
);
```
## 注意事项
1. **路径格式**JSON 中的资源路径使用 `/` 分隔符,且为 Unity 项目相对路径
2. **资源引用**:确保 JSON 中引用的 Sprite、Font 等资源已存在于项目中
3. **编码格式**JSON 文件使用 UTF-8 编码
4. **坐标系统**:使用 Unity RectTransform 的 anchorMin/anchorMax/position/sizeDelta 四元组
5. **文件锁定**:生成前确保目标预制体文件未被其他进程占用
## 部署步骤
### 方法一:手动复制
1. 复制 `modules/frameworks/编辑器工具/UI工具/` 目录下的所有文件
2. 粘贴到目标项目的 `Assets/Editor/Command/` 目录中
3. 确保目录结构如下:
```
Assets/Editor/Command/
├── ExternalCommandListener.cs
├── RemoteCommand.cs
├── UIPrefabGenerator.cs
└── UIPrefabToJson.cs
```
### 方法二:自动部署脚本
创建部署脚本 `DeployUITools.bat`
```batch
@echo off
set "SOURCE_DIR=E:\AI\UnityAI\modules\frameworks\编辑器工具\UI工具"
set "TARGET_DIR=%cd%\Assets\Editor\Command"
mkdir "%TARGET_DIR%" 2>nul
xcopy "%SOURCE_DIR%\*.cs" "%TARGET_DIR%\" /y
xcopy "%SOURCE_DIR%\*.cs.meta" "%TARGET_DIR%\" /y
echo UI 工具部署完成
pause
```
### 部署检查项
| 检查项 | 要求 |
|-------|------|
| **目标路径** | 必须放置在 `Assets/Editor/Command/` 目录下 |
| **文件完整性** | 必须包含所有 4 个 cs 文件及其 meta 文件 |
| **Unity 版本** | 支持 Unity 2020 及以上版本 |
| **依赖组件** | 需要导入 TextMeshPro 包 |
### 验证部署
部署完成后,在 Unity 编辑器中验证:
1. 打开 Unity 编辑器
2. 检查菜单 `Tools > UI` 是否存在
3. 检查菜单 `Tools > External` 是否存在
4. 在 Console 窗口确认无编译错误
---
**版本**: v1.2
**适用**: StrayFog 框架 UI 拼接开发人员
---
- 覆盖已有 Prefab 前确认目标路径和现有内容。
- 不通过 UI 生成工具批量删除、移动资源或执行构建发布。
- Unity API 必须由 Bridge 排队到 Editor 主线程执行。
- 生成后检查 Git 差异,避免无意义重序列化其他资源。
- JSON 递归结构出现 serialization depth warning 时检查实际生成结果;新增 Error 必须处理。
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [编辑器工具入口](README.md) | 本文件是编辑器工具模块的子文档 |
| [UI 窗口系统](../UI窗口系统/README.md) | UI 拼接工具服务于 UI 窗口系统 |
| [UI 界面拼接设计指南](../UI窗口系统/界面拼接.md) | 设计指南说明 JSON 驱动的界面拼接原则 |
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义编辑器工具层与运行时层边界 |
| [rules/20-开发规则.md](../../../rules/20-开发规则.md) | 编辑器工具开发规则互相引用 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
| [编辑器工具入口](README.md) | 编辑器工具模块入口 |
| [UI 窗口系统](../UI窗口系统/README.md) | 窗口框架和资源路径 |
| [UI 界面拼接](../UI窗口系统/界面拼接.md) | 视觉与 Prefab 流程 |
| [MCP 使用规则](MCP工具/CrystalBattle_Codex_Unity_MCP使用规则.md) | Codex 调用和安全边界 |

View File

@@ -1,142 +1,170 @@
# 资源管理
## 分类
- 核心框架
## 目的
管理 AssetBundle、语言资源、图集、prefab、DllBytes 等资源的加载、缓存和释放
资源管理负责 AssetBundle、依赖、语言资源、SpriteAtlas、内存卸载、启动代码更新和普通资源分包下载
---
## 源码边界
## 核心路径
| 类型 | 路径 | 说明 |
|---|---|---|
| ESF AssetBundle 源码 | `Assets/StrayFog/Editor/CopyToX/CopyToHF/AssetBundle/` | 框架修改源头 |
| HF AssetBundle 代码 | `Assets/Game/GameHFScripte/CopyToHF/AssetBundle/` | CopyToX 生成,禁止手改 |
| V2 公共运行时 | `Assets/StrayFog/Core/SFSubPackageV2.cs` | 非热更层与热更层共享下载状态 |
| V2 编辑器构建 | `Assets/StrayFog/Editor/Tools/AssestBundle/` | 包配置、校验、清单和枚举生成 |
| 热更业务门面 | `Assets/Game/GameHFScripte/GameFunction/HFDownLoadSubPackage/` | 普通资源下载 API |
| 启动代码入口 | `Assets/Game/GameMono/Scripts/GameDllManager.cs` | Bootstrap 校验、下载和加载 |
- AssetBundle`Assets/Game/GameHFScripte/CopyToHF/AssetBundle/`
- 图集:`Assets/Game/GameHFScripte/CopyToHF/SpriteAtlas/`
- 资源目录:`Assets/Game/GameHFResource/CN/``EN/``DllBytes/`
修改 `ESF_` 后执行 CopyToX再检查生成的 `HF_` 差异和 `GameHF` 编译结果。
---
## 核心类
| 类 | 职责 |
|---|---|
| `HFABMgr` | AssetBundle 资源管理器单例 |
| `HFAssetLoader` | 具体资源加载器 |
| `HFAssetRequest` | 资源加载请求 |
| `HFAssetBundlePathAttribute` | 资源路径属性,标记资源根目录、相对路径、语言包规则等 |
| `HFGlobal_SpriteAtlas` | 图集全局管理 |
---
## 资源路径规则
`HFAssetBundlePathAttribute` 记录:
- 资源根目录
- 相对路径
- 是否忽略语言包
- 是否场景
- 线程优先级
`ChangeLanguage` 生成:
- `editorPath`
- `assetBundlePath`
- `assetBundleKey`
- `assetName`
`HFABMgr.OnCheckAssetBundlePathAttributeLanguage` 在当前语言无资源时回退默认语言。
---
## 加载流程
## AssetBundle 加载
```text
HFABMgr.LoadInMemory(pathAttr)
-> OnReadAssetBundleManifest()
-> OnCheckAssetBundlePathAttributeLanguage()
-> new HFAssetRequest(...)
-> OnStartLoad()
-> 获取或创建 HFAssetLoader
-> HFAssetLoader.LoadAsset()
HFFunCatalogue.Asset / HFABMgr.LoadInMemory
-> 读取 AssetBundleManifest
-> 语言与路径修正
-> 创建或复用 HFAssetLoader
-> 加载依赖 Bundle
-> 加载主 Bundle
-> HFAssetResponse.GetAsset<T>()
```
---
`HFABMgr` 继续作为统一入口,并使用 partial 文件组织 Manifest、Loader、GC、路径和模式逻辑。内部职责可以封装但业务入口不拆散。
## 加载模式
### Manifest 与来源顺序
### Editor 模式
Editor 模式直接读取工程资源。Player 本地模式按以下顺序初始化 Manifest
```text
enHFAbLoadModel.Editor
-> SFAssetUtility_Unity.LoadFromFile(editorPath)
-> 直接读取编辑器资源
PersistentData Manifest
-> 不存在或无效时读取 StreamingAssets Manifest
-> 两处都不可用且已配置 CDN 时读取 CDN Manifest
```
### 非 Editor 模式
- PersistentData 保存热更新后的可写资源,优先于母包资源。
- StreamingAssets 是只读随包资源Android、WebGL 等 URL 路径通过 UnityWebRequest 协程读取。
- CDN 是本地 Manifest 都不可用或资源需要更新时的远端来源。
- 显式 CDN/小游戏模式按平台适配层和缓存回调工作,不假设普通文件系统可用。
- `Application.dataPath` 只用于 `UNITY_EDITOR` 下的资源定位和分析。
PersistentData 普通文件优先使用 `AssetBundle.LoadFromFileAsync`。StreamingAssets URL、CDN byte[]、小游戏缓存和需要解密的数据继续使用 `LoadFromMemoryAsync`
### CDN 落盘
`SetCdnSaveRootPath(string)` 允许平台层传入保存根目录。有效外部路径优先;未设置时使用 `SFGlobalData.assetBundleRoot`,即项目 PersistentData 资源根目录。保存时保留相对目录结构。
## 内存与卸载
- GC 与主动卸载共用 loader 释放流程。
- 释放时调用 `AssetBundle.Unload(false)`,销毁 loader并同步清理 loader 映射、生命周期池和内存统计。
- 按资源名卸载只在唯一匹配且 loader 可卸载时成功;未找到、重名、加载未完成或仍有请求时返回 `false`
- 内存大小使用 loader 统计或本地文件长度估算,只用于运行时观察,不替代 Unity Profiler。
公开使用方式见 [资源管理使用指南](使用指南.md)。
## 分包 V2
V2 将编辑器构建清单与设备下载状态分离。运行时只消费编辑器生成的 V2 清单,不识别旧 `SPI_*` 结构。
### 编辑器阶段
资源包通过 Unity 编辑器自由配置XML 只负责持久化。每个 Package 包含:
- `Id``DisplayName``Priority``BuiltIn`
- `RootPaths[]`
- `DependencyPackageIds[]`
保存和构建会校验空/重复 ID、无效路径、自依赖、缺失依赖和循环依赖。根路径使用标准化小写路径与目录边界前缀匹配重叠目录警告但允许构建。
构建顺序:
```text
读取 AssetBundleManifest
-> 解析依赖
-> AssetBundle.LoadFromMemoryAsync 读取主资源
普通 AssetBundle
-> AOT 与 HotUpdate DLL 产物
-> 收集最终文件
-> 生成 V2 subpackage.json
-> 复制 BuiltIn 文件并生成分析结果
```
### CDN / 边玩边下
生成清单的同时生成 `enHFSubPackageId.cs`。枚举成员由 PackageId 转换XML 注释使用 DisplayName业务通过映射恢复原始 PackageId生成文件禁止手改。
### V2 清单
```text
缺失资源进入 WaitDownload
-> HFAssetsDownLoadManager 下载
-> HFABMgr.SaveFile 保存到本地
-> 加载完成
Version / Platform / BuildId
BootstrapFiles[] AOT 与 HotUpdate 启动代码
Files[] 普通 AB 的 Path、MD5、Size、Dependencies
Packages[] 包配置、FilePaths 和 Size
```
---
- `BootstrapFiles``CodeKind``LoadOrder` 控制 AOT、HotUpdate 加载顺序。
- `Files` 以标准化 Path 全局唯一,依赖来自 Unity AssetBundleManifest。
- `Packages` 没有固定业务类型PackageId 的业务意义由调用方决定。
- Package 显式依赖和 AB 文件依赖是两套关系,运行时都会递归展开。
## 图集加载
### 运行时顺序
```text
SpriteAtlasManager.atlasRequested
-> HFGlobal_SpriteAtlas.Instance.GetSpriteAtlasPath(spriteAtlasName)
-> HFABMgr.OnLoadInMemory(spriteAtlasAttribute)
-> callback(SpriteAtlas)
下载并校验远端 V2 清单
-> 比对 BootstrapFiles
-> PersistentData 命中则复用
-> 否则检查 StreamingAssets
-> 仍不匹配则从 CDN 下载到临时文件并校验 MD5
-> AOT 加载成功
-> HotUpdate 加载与入口执行成功
-> CommitBootstrapCodeLoaded 提交清单和状态
-> 普通资源比对与下载开放
```
---
Bootstrap 是普通资源的硬门禁。清单、下载、MD5、AOT、HotUpdate 或入口任一步失败都不会开放普通资源阶段。
## 使用规则
CDN URL 使用:
- 资源路径统一通过 `HFAssetBundlePathAttribute` 配置。
- 加载资源统一走 `HFFunCatalogue.Asset.LoadAsset()``HFABMgr.LoadInMemory()`
- 语言资源按 `GameHFResource/<lang>/` 分目录存放。
- 图集按业务或 UI 模块划分。
```text
{BaseUrl}/{AppPartner}/{AppVersion}/{Platform}/{FilePath}
```
---
BaseUrl 按配置顺序尝试,使用 `/` 进行 URI 拼接。Bootstrap 文件最多三轮,每轮尝试全部有效 BaseUrl普通文件一次请求按顺序尝试有效地址。
## 相关模块
### 普通资源更新范围
- [UI 窗口系统](../UI窗口系统/README.md)
- [实体系统](../实体系统/README.md)
- [场景系统](../场景系统.md)
`CheckAndDownloadAllFiles` 不会无条件下载整个 `Files[]`,它处理:
## 子文档
- 未归属任何 Package 的基础文件。
- `BuiltIn = true` 的 Package 及依赖包。
- `DownloadedPackageIds` 中历史成功下载的 Package 及依赖包。
- 上述文件的完整 AB 依赖闭包。
- [资源管理使用指南](使用指南.md)
- [资源加载重构方案](资源加载重构方案.md)
从未下载、非 BuiltIn 的可选 Package 不会被全量更新误下载。业务可按 PackageId 主动下载,资源加载缺文件时也可按 `Files[]` 下载目标文件依赖闭包。
---
### 状态与去重
`subpackage_state.json` 当前保存:
- `BuildId`
- `DownloadedFiles` 的 Path 与 MD5
- `DownloadedPackageIds`
Package 完整成功后才记录请求包和依赖包。查询 Package 是否完成时会同时检查当前清单、PackageId 状态、依赖包状态和所有文件 MD5。
去重分为三层:清单 Path 唯一、进行中任务按 `Path + MD5` 合并、本地文件和状态校验通过后复用。文件只有完整写入并通过 MD5 后才更新状态和进度计数。
## 验证重点
- PersistentData、StreamingAssets 和 CDN 的 Manifest 回退不会卡住主线程。
- 本地文件使用正确加载策略,平台 URL 使用协程。
- GC/主动卸载后能重新加载loader 映射和内存统计正确。
- V2 差异结果在下载前回调,失败信息包含 Path、期望 MD5 和尝试 URL。
- 冷启动会从状态继续按文件校验,已完成文件不重复下载。
- 已下载 Package 在远端 MD5、依赖或 FilePaths 变化后参与更新。
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [modules/frameworks/UI窗口系统/README.md](../UI窗口系统/README.md) | UI 资源加载依赖资源管理 |
| [modules/frameworks/实体系统/README.md](../实体系统/README.md) | 实体资源加载依赖资源管理 |
| [modules/frameworks/场景系统.md](../场景系统.md) | 场景资源加载依赖资源管理 |
| [modules/frameworks/资源管理/资源加载重构方案.md](资源加载重构方案.md) | AssetBundle 加载链路重构方案 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
| [资源管理使用指南](使用指南.md) | 业务调用、卸载、内存和分包 API |
| [模块索引](../../模块索引.md) | 模块入口和状态 |
| [UI 窗口系统](../UI窗口系统/README.md) | UI Prefab 加载与卸载 |
| [Hotfix 启动](../Hotfix启动.md) | Bootstrap 代码加载入口 |
| [架构说明](../../../rules/10-架构说明.md) | ESF/HF 源码边界 |
| [工作流清单](../../../rules/40-工作流清单.md) | 生成和平台验证流程 |

View File

@@ -1,102 +1,174 @@
# 资源管理使用指南
## 一、AssetBundle 资源加载
## 一、加载资源
### 1.1 加载资源示例
业务统一通过 `HFFunCatalogue.Asset` 调用:
```csharp
public class ResourceLoader : MonoBehaviour
HFFunCatalogue.Asset.LoadAsset(@"Perfabs\Tower\LavaGuard.prefab", (response, args) =>
{
public void LoadPlayerModel(int resId, Action<GameObject> onLoaded)
GameObject prefab = response.GetAsset<GameObject>();
if (prefab == null)
{
// 通过 HFFunCatalogue 统一入口加载资源
// resId: 从 Res 配置表中读取的资源 ID
HFFunCatalogue.Asset.LoadAsset(resId, (res, obj) =>
{
if (obj != null)
{
GameObject model = res.GetAsset<GameObject>();
onLoaded?.Invoke(model);
}
else
{
Debug.LogError($"加载失败: resId={resId}");
}
});
SFP.Error("Load LavaGuard failed.");
return;
}
}
```
### 1.2 实体资源加载示例
```csharp
// 在 Entity 中加载资源
HFFunCatalogue.Asset.LoadAsset(data.EntityRes.Id, (res, obj) => {
if (!entity.isDeath)
{
var Boddy = res.GetAsset<GameObject>();
data.SetBoddy(Boddy);
}
GameObject instance = GameObject.Instantiate(prefab);
});
```
---
资源路径仍按项目 `HFAssetBundlePathAttribute`、语言目录和 AssetBundle 配置解析。业务不要直接创建 `HFAssetLoader` 或依赖 assetBundleKey。
## 二、资源管理 API
## 二、主动卸载
### 2.1 常用方法
业务使用易读名称接口:
| 类别 | 管理器 | 常用方法 |
|-----|-------|---------|
| **资源** | `HFABMgr` | `LoadAsset()`, `OnStartLoad()` |
### 2.2 资源加载流程
```
资源加载流程:
OnStartLoad(request)
检查缓存 → 获取/创建 HFAssetLoader
loader.AddRequest(request)
loader.LoadAsset() → 异步加载
加载完成 → 回调处理
```csharp
bool prefabUnloaded = HFFunCatalogue.Asset.UnloadPrefabObject("LavaGuard");
bool assetUnloaded = HFFunCatalogue.Asset.UnloadAssetObject("common_icon");
```
---
调用前必须确认业务实例和窗口已经释放引用。框架会查找唯一 loader并检查加载、请求和释放状态未找到、重名或仍不可卸载时返回 `false`
## 三、资源管理注意事项
`AssetBundle.Unload(false)` 不会自动销毁已经实例化的 GameObject。业务仍应先关闭窗口或 Destroy 实例,再请求卸载 Bundle 内存。
### 3.1 性能优化建议
同名资源存在多个 loader 时不要随机卸载;使用更完整的资源路径,或根据错误日志定位冲突来源。
- 及时释放不再使用的资源
- 使用异步加载避免卡顿
- 合理使用资源缓存
## 三、内存查看
---
```csharp
long bytes = HFFunCatalogue.Asset.GetLoadedAssetBundleMemorySize();
float kb = HFFunCatalogue.Asset.GetLoadedAssetBundleMemorySizeKB();
float mb = HFFunCatalogue.Asset.GetLoadedAssetBundleMemorySizeMB();
string text = HFFunCatalogue.Asset.GetLoadedAssetBundleMemorySizeText();
HFFunCatalogue.Asset.LogLoadedAssetBundleMemorySize();
```
**版本**: v1.0
**生效日期**: 2026年
**适用范围**: GiftGameX 项目业务开发人员
该数值是已完成 loader 的 AssetBundle 内存估算。`LoadFromFileAsync` 没有托管 byte[] 时使用文件长度估算,因此只用于调试面板和趋势观察。
---
## 四、AssetBundle CDN 保存路径
平台层可以在初始化时配置:
```csharp
HFFunCatalogue.Asset.SetCdnSaveRootPath(platformSaveRoot);
```
未配置或传入无效值时使用 PersistentData 资源根目录。平台专用目录由平台适配层传入,不在通用 ESF 逻辑中硬编码。
## 五、分包 V2 配置
非热更启动层使用:
```csharp
SFSubPackageRuntimeV2.Instance.SetDownloadConfig(
downloadSetting.WebBaseUrlBase,
downloadSetting.AppVersion,
downloadSetting.AppPartner);
```
如平台使用自定义文件系统,调用 `SetLocalStorage` 重载传入 exists、read、write 和 delete 回调。默认存储根目录为 PersistentData。
`SetDownLoadUri(string)` 只作为单地址兼容入口,新代码优先使用 `SetDownloadConfig`
## 六、启动代码更新
`GameDllManager` 负责调用:
```csharp
SFSubPackageRuntimeV2.Instance.CheckAndDownloadBootstrapFiles(
OnProgress,
OnBootstrapCompleted,
OnManifestFailed);
```
成功下载不等于启动完成。AOT、HotUpdate 和热更入口成功后,调用:
```csharp
SFSubPackageRuntimeV2.Instance.CommitBootstrapCodeLoaded(OnCommitted);
```
提交成功后普通资源 API 才可使用。失败时调用 `InvalidateBootstrapCode` 保持门禁关闭,并向业务展示重试或退出。
## 七、更新普通资源
### 更新基础、BuiltIn 和历史包
```csharp
HFSubPackageManager.Instance.CheckAndDownloadAllFiles(
progress => UpdateLoading(progress),
() => OnResourceUpdateCompleted(),
result => SFP.Error(SFDownloadResultLog.Format("普通资源下载失败", result)));
```
该接口会先完成差异扫描并回调差异结果再开始删除旧遮挡文件和下载。它只处理基础文件、BuiltIn 包、历史已下载包及依赖闭包。
### 按枚举下载 Package
```csharp
HFSubPackageManager.Instance.CheckAndDownloadPackage(
enHFSubPackageId.PACKAGE_1001,
progress => UpdateLoading(progress),
() => OnPackageReady(),
result => SFP.Error(SFDownloadResultLog.Format("Package 下载失败", result)));
```
枚举由编辑器生成,成员注释来自 Package DisplayName。不要手改 `Generated/enHFSubPackageId.cs`
字符串接口仍保留:
```csharp
HFSubPackageManager.Instance.CheckAndDownloadPackage(
packageId, progress, finish, failed);
```
### 查询 Package 状态
```csharp
bool ready = HFSubPackageManager.Instance.IsPackageDownloaded(
enHFSubPackageId.PACKAGE_1001);
```
返回 `true` 需要Bootstrap 已提交、Package 存在于当前清单和 `DownloadedPackageIds`、依赖包已记录、全部文件状态符合当前 MD5。`BuiltIn = true` 本身不等于业务已经下载过。
### 按文件兜底
资源加载知道目标 AB 路径时可以使用 `CheckAndDownloadFile`。运行时会从 `Files[]` 找到目标文件,递归展开 Dependencies下载差异后重试原加载流程。
## 八、进度和失败
进度对象包含:
- `Stage``Max``DownloadedCount`
- `TotalBytes``DownloadedBytes`
- `CurrentPath``CurrentFileSize`
- `CurrentFileDownloadedBytes``CurrentFileProgress``Progress`
失败结果包含:
- `DifferenceFiles``DeletedOldFiles``UpdatedFiles`
- `FailedFiles[].Path`
- `ExpectedMD5``Error``TriedUrls`
单文件只有写入完成且 MD5 通过后才增加完成计数。进程被结束后,下次启动会重新读取远端清单和本地状态,逐文件检查;已完成文件复用,未完成文件从文件头重新下载,不做单文件字节级续传。
## 九、验证清单
- PersistentData 文件正确时不访问 CDN。
- PersistentData 错误但 StreamingAssets 正确时使用母包文件并处理遮挡旧文件。
- 两处均无有效文件时从 CDN 下载到默认或外部保存路径。
- 差异日志在普通资源下载进度之前输出。
- 两个 Package 共享依赖时只建立一个 `Path + MD5` 任务。
- 远端 MD5 变化后历史 Package 会参与 `CheckAndDownloadAllFiles`
- 下载失败释放进行中任务,下一次请求可以重试。
- Android/WebGL 的 StreamingAssets 失败会继续 fallback不会阻塞主线程。
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [资源管理入口](README.md) | 本文件是资源管理模块的子文档 |
| [UI 窗口系统](../UI窗口系统/README.md) | UI 资源加载依赖资源管理 |
| [实体系统](../实体系统/README.md) | 实体资源加载依赖资源管理 |
| [场景系统](../场景系统.md) | 场景资源加载依赖资源管理 |
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
| [资源管理入口](README.md) | 架构、来源顺序和 V2 数据流 |
| [UI 窗口系统](../UI窗口系统/README.md) | UI 加载与卸载调用方 |
| [Hotfix 启动](../Hotfix启动.md) | Bootstrap 接入 |
| [开发规则](../../../rules/20-开发规则.md) | 业务入口和异步规则 |

View File

@@ -1,380 +0,0 @@
# ESF_ 资源加载重构方案
## 一、目标与边界
本方案用于规划 StrayFog 框架中 AssetBundle 资源加载链路的重构。当前阶段先文档化设计,不直接修改代码。
重构范围只包含 `Assets/StrayFog/Editor/CopyToX/...` 下的 `ESF_` 框架源头代码。`ESF_` 后续会通过 CopyTo 流程同步到 `GameHFScripte` 目录,并替换为业务运行时使用的 `HF_` 命名。因此:
- 框架优化只修改 `ESF_` 源头。
- 不直接修改 CopyTo 后的 `HF_` 副本代码。
- 对外调用接口保持兼容,业务仍通过现有 `HF_` API 使用资源系统。
需要保持兼容的公开入口:
- `ESF_ABMgr.LoadInMemory(...)`
- `ESF_ABMgr.SetModel(...)`
- `ESF_ABMgr.SetDownloadWhilePlaying(...)`
- `ESF_ABMgr.SetDownLoadUri(...)`
- `ESF_ABMgr.RegisterDownloadMainifestFun(...)`
- `ESF_AssetBundlePathAttribute` 现有构造与使用方式
计划新增的公开入口:
```csharp
public bool TryUnloadPrefabInMemory(string _prefabName);
public bool TryUnloadAssetInMemory(string _assetName);
public long GetLoadedAssetBundleMemorySize();
public void SetCdnSaveRootPath(string _saveRootPath);
```
说明:
- `TryUnloadPrefabInMemory(string)` 供业务按预制体名称主动请求卸载,例如 `TryUnloadPrefabInMemory("GameLogicFightWindow")`
- `TryUnloadAssetInMemory(string)` 供业务按资源名称主动请求卸载,适用于非 prefab 资源。
- 业务接口不暴露 `assetBundleKey``ESF_AssetBundlePathAttribute``_unloadAllLoadedObjects`,避免上层误用内部概念。
- 内部卸载统一使用 `AssetBundle.Unload(false)`,只释放 Bundle 文件内存,不强制销毁已经实例化出去的对象。
- 框架必须先判断资源已经没有框架侧活跃引用;如果仍在加载中、正在被窗口/任务使用、处于 ACTIVE 生命周期,或名称匹配不唯一,则拒绝卸载并返回 `false`
- `GetLoadedAssetBundleMemorySize()` 返回当前 ESF_ABMgr 统计到的已加载 AssetBundle 内存大小,单位为 byte用于运行时监控面板或调试日志。
- `SetCdnSaveRootPath(string)` 供外部按平台传入 CDN 热更新文件落盘根目录;如果未传入或传入空值,默认使用 PersistentData 资源根路径。
## 二、当前问题
### 2.1 职责封装需要加强
`ESF_ABMgr` 当前通过 partial 分布类承载 Manifest 读取、资源路径修正、Loader 管理、依赖查询、加载模式控制、GC 回收等职责。这种设计保留了统一入口,方便后续 CopyTo 和上层调用,是框架既有设计方向。
后续优化不应拆散 `ESF_ABMgr` 的统一入口,而是需要继续在 `ESF_ABMgr` 内部强化职责封装:把 Manifest 查询、加载来源选择、下载落盘、GC 回收、主动卸载等流程封装成清晰的内部方法或内部服务,减少方法之间直接互相修改状态。`ESF_AssetLoader` 内部也需要把依赖加载、主包加载、下载等待、进度计算和回调分发拆成更明确的状态步骤。
### 2.2 本地加载内存峰值偏高
PersistentData 等普通本地文件路径当前存在 `File.ReadAllBytes` + `AssetBundle.LoadFromMemoryAsync` 的加载方式。结合 Unity 官方说明和当前框架实现,这个判断成立,但需要精确理解:
- `File.ReadAllBytes(path)` 会先把整个 AssetBundle 文件读入托管 `byte[]`,这一步本身就会产生一份完整文件大小的内存占用。
- `AssetBundle.LoadFromMemoryAsync(byte[])` 是从内存中的 byte[] 创建 AssetBundle适合已经下载、解密或只能拿到 byte[] 的场景。
- `AssetBundle.LoadFromFileAsync(path)` 是从磁盘文件异步加载 AssetBundleUnity 官方说明它支持各种压缩类型,并且未压缩和 Chunk 压缩包可以直接从磁盘读取。
- 当前框架构建 AssetBundle 时使用了 `BuildAssetBundleOptions.ChunkBasedCompression`,因此 PersistentData 下的普通本地文件优先改为 `LoadFromFileAsync` 是合理方向。
需要注意:如果资源包需要先经过 `LoadFileByDecryptFunc` 解密,或者来源是 StreamingAssets URL、CDN 下载数据、小游戏文件系统 byte[],仍然需要保留 `LoadFromMemoryAsync` 路径。
### 2.3 StreamingAssets 存在忙等阻塞
`ESF_AssetBundleHelper.OnReadStreamingAssets` 使用 `UnityWebRequest.SendWebRequest()``while (!async.isDone)` 空循环等待,会阻塞主线程。该问题在移动端和 WebGL/小游戏类环境中风险更高。
### 2.4 下载队列缺少统一并发上限
`ESF_DownLoadGroup``m_MaxTask` 由任务文件数量传入,可能导致一次任务内所有文件同时下载。资源数量较多时容易造成网络拥塞、内存峰值和下载回调竞争。
### 2.5 进度与回收存在边界风险
`ESF_ABMgr.progress` 在没有 active loader 时存在除零风险。GC 回收时销毁 loader GameObject 后,字典与内存统计需要同步清理,否则可能残留失效 loader 或重复统计。
## 三、目标架构
重构后仍保留 `ESF_ABMgr` 作为统一对外入口,并继续允许使用 partial 分布类组织代码;优化重点是把内部职责封装成更明确的方法组或内部服务。
| 模块 | 职责 |
|-----|-----|
| Manifest 管理封装 | 读取 Manifest、维护带 hash 包名映射、处理语言回退、提供依赖查询 |
| Loader 管理封装 | 管理 assetBundleKey 到 loader 的映射、创建/复用/重载 loader |
| Bundle 读取策略封装 | 根据加载模式选择 `LoadFromFileAsync``LoadFromMemoryAsync` 或下载数据加载 |
| 下载队列封装 | 控制下载并发、失败重试、进度回调、下载完成数据回传 |
| GC 缓存策略封装 | 维护 ACTIVE/Normal/LAZY 生命周期,回收时同步移除 loader 映射和内存统计 |
| CDN 落盘路径策略封装 | 管理 CDN 下载后的保存根目录,支持外部传入平台专用路径,默认回退 PersistentData |
### 3.1 资源加载主流程
```
LoadInMemory
Manifest 管理:确认 StreamingAssets / PersistentData / CDN 来源
Manifest 管理:按当前语言和各来源 Manifest 修正 ESF_AssetBundlePathAttribute
Loader 管理:创建或复用 ESF_AssetLoader
ESF_AssetLoader加载依赖包
Bundle 读取策略:加载主 AssetBundle
ESF_AssetResponse按原有回调返回资源
```
### 3.2 本地加载策略
Unity 常用资源路径语义:
| 路径 | Unity 语义 | 常见平台表现 | 框架用途 |
|-----|-----------|-------------|---------|
| `Application.streamingAssetsPath` | 随包只读资源目录,构建时原样拷贝 `Assets/StreamingAssets` 内容 | Editor/Windows/Linux 多数是普通目录macOS、iOS 有各自包内路径Android 为 APK/JAR 内 URLWebGL/Web 平台也是 URL 类路径 | 放母包内置 AssetBundle作为首次启动和无热更文件时的基础资源 |
| `Application.persistentDataPath` | 运行期可写、跨启动保留的数据目录 | iOS/Android 更新 App 后通常保留Windows/macOS/Linux 为用户目录WebGL/Web 为浏览器持久化文件系统tvOS 不支持 | 放热更下载后的 AssetBundle作为 StreamingAssets 缺失后的本地补充资源 |
| `Application.dataPath` | 当前项目或 Player 数据目录 | 本框架只允许在 Editor 宏包裹的代码中使用Editor 下指向项目 `Assets` | 仅用于 Editor 下路径推导和资源分析,不参与 Player 运行期加载策略 |
框架目标读取优先级:
```text
非 CDN 模式:
StreamingAssets Manifest
├─ Manifest 包含目标资源:从 StreamingAssets 加载随包资源
└─ Manifest 不包含目标资源
PersistentData Manifest
├─ Manifest 包含目标资源:从 PersistentData 加载热更资源
└─ Manifest 不包含目标资源
如果配置 CDN 路径,则从 CDN 下载
CDN 模式:
CDN / 小游戏缓存 / 小游戏平台文件系统
└─ 不走 StreamingAssets / PersistentData fallback
```
具体策略:
- 非 CDN 模式不直接按文件是否存在做第一判断,而是先读取 StreamingAssets 下的 Manifest。
- 如果 StreamingAssets Manifest 包含目标资源,则从 `SFGlobalData.streamingAssetsRoot` 加载对应 AssetBundle。
- 如果 StreamingAssets Manifest 不包含目标资源,再读取 PersistentData 下的 Manifest。
- 如果 PersistentData Manifest 包含目标资源,则从 `SFGlobalData.assetBundleRoot` 加载对应 AssetBundle。
- 如果两个本地 Manifest 都不包含目标资源,且配置了 CDN 路径,则从 CDN 下载目标资源;如果没有配置 CDN 路径,则返回明确失败。
- StreamingAssets 是随包母包资源PersistentData 是热更落盘资源;二者通过各自 Manifest 判断是否拥有目标资源。
- PersistentData 命中且是普通文件系统路径时,优先使用 `AssetBundle.LoadFromFileAsync(path)`,避免 `File.ReadAllBytes + LoadFromMemoryAsync` 带来的额外 byte[] 内存峰值。
- Android、WebGL/Web 等 StreamingAssets URL 类路径不能用普通 `File.Exists` / `File.ReadAllBytes` 判断和读取,需要通过 `UnityWebRequest` 异步读取。
- StreamingAssets 读取到 byte[] 后仍使用 `AssetBundle.LoadFromMemoryAsync(data)`
- `Application.dataPath` 只允许出现在 `UNITY_EDITOR` 宏包裹的编辑器逻辑中用于路径推导、AssetDatabase 资源定位或构建分析;运行期加载不能依赖该路径。
- 显式 CDN 模式仍视为独立资源来源:优先走 CDN/小游戏缓存/小游戏文件系统逻辑,不参与 StreamingAssets 和 PersistentData 的本地 fallback。
- 非 CDN 模式下的 CDN 下载只作为本地两个 Manifest 都不包含资源后的最后补充来源,前提是已经配置 CDN 地址。
- CDN 下载返回的数据、小程序缓存读取返回的数据、需要 `LoadFileByDecryptFunc` 解密的数据,仍保持 byte[] 加载路径。
- 后续如果发现部分平台对 `LoadFromFileAsync` 支持不一致,再增加内部策略开关;第一版不暴露为公开 API。
### 3.2.1 CDN 热更新落盘路径
CDN 下载属于热更新链路。下载成功后,需要把资源保存到当前平台可读写的位置,供后续启动或二次加载复用。
落盘根目录优先级:
```text
外部传入 CDN 保存根目录
├─ 有效:使用外部路径
└─ 无效或未设置:使用 SFGlobalData.assetBundleRoot / PersistentData
```
计划新增接口:
```csharp
public void SetCdnSaveRootPath(string _saveRootPath);
```
行为要求:
- 外部可在初始化资源系统时传入平台专用保存根目录。
- 如果 `_saveRootPath``null`、空字符串或非法路径,则不覆盖默认值。
- 未设置外部路径时CDN 下载文件统一保存到 `SFGlobalData.assetBundleRoot`,也就是框架的 PersistentData 资源根路径。
- 保存路径只影响 CDN 下载后的本地落盘位置,不改变 CDN 下载 URL。
- 保存时仍保留 AssetBundle 的相对路径结构,避免不同 bundle 重名覆盖。
- 小游戏平台如果有专用文件系统路径,应通过平台宏或外部初始化代码传入,不在通用逻辑里硬编码平台路径。
- 后续读取 CDN 已下载文件时,优先从该保存根目录查找;未命中再走 CDN 下载。
平台隔离原则:
- 通用 ESF_ 逻辑只维护“外部传入路径优先,否则 PersistentData”的规则。
- 平台差异通过宏定义包裹在平台适配层例如小游戏、WebGL、原生移动端等各自传入保存路径。
- 不在业务层暴露 `assetBundleKey` 或内部 Manifest 路径;业务只配置 CDN 地址和可选保存根目录。
### 3.3 StreamingAssets 异步读取
`OnReadStreamingAssets` 不再提供阻塞式读取给加载主链路使用。主链路应改为异步读取:
- 使用 `UnityWebRequest.Get`
- 通过协程或 loader 状态机等待完成。
- 下载成功后使用 `AssetBundle.LoadFromMemoryAsync(data)`
- 下载失败时进入统一失败处理或 CDN fallback。
### 3.4 下载队列
下载管理器增加内部并发上限,建议默认值为 3。
要求:
- 单个 `ESF_DownLoadGroup` 不再以文件数量作为最大并发数。
- 高优先级任务优先执行,但不能绕过全局并发上限。
- 下载失败保持现有回调语义。
- `WaitDownload` 回调保持兼容。
### 3.5 GC 与 loader 映射
GC 回收和主动卸载共用同一套内部释放逻辑,避免自动回收与业务手动卸载产生两套状态。
GC 自动回收时需要同时处理:
- `AssetBundle.Unload(false)`,默认不销毁已经实例化出去的对象。
- Destroy loader GameObject。
-`mESF_AssetLoaderMaping` 移除对应 key。
- 从生命周期池和内存统计中移除对应 key。
回收后再次加载同一资源时,必须能够重新创建 loader 并正常触发回调。
业务主动卸载接口:
```csharp
public bool TryUnloadPrefabInMemory(string _prefabName);
public bool TryUnloadAssetInMemory(string _assetName);
```
行为要求:
- 根据 `_prefabName``_assetName` 在当前已加载 loader 中查找匹配资源,匹配字段以 `abPathAttr.assetName` 为准。
- 找到唯一 loader且 loader 已完成加载、没有排队请求、没有 active 任务、生命周期不为 ACTIVE 时,执行内部释放流程并返回 `true`
- 未找到 loader、名称匹配多个 loader、loader 未完成加载、资源正在使用、或资源已被释放时,返回 `false`,不抛异常。
- 如果资源正在加载中,第一版不强制中断异步加载,避免破坏现有回调语义。
- 卸载成功后,`mESF_AssetLoaderMaping`、生命周期池、内存统计必须同步移除对应 key。
- 后续再次请求同一资源时,必须走正常创建 loader 和加载流程。
- `AssetBundle.Unload(false)` 不会销毁已经实例化出去的 GameObject但为了避免业务继续依赖原始 asset 对象,业务接口仍必须通过框架引用判断后才允许释放。
内部卸载能力:
```csharp
bool OnUnloadInMemory(int _assetBundleKey);
bool OnCanUnloadInMemory(ESF_AssetLoader _loader);
```
说明:
- 按 key 卸载只作为框架内部实现和调试面板基础能力,不作为普通业务接口。
- 所有卸载入口都必须经过 `OnCanUnloadInMemory` 安全判断。
- 后续如果需要更精确的“完全没有引用”,应引入框架级引用计数或资源句柄释放机制;第一版以 loader 生命周期、请求队列和窗口/任务活跃状态作为安全判断依据。
内存查询接口:
```csharp
public long GetLoadedAssetBundleMemorySize();
```
行为要求:
- 返回当前 AssetBundle 加载器统计到的内存大小,单位为 byte。
- 统计值以 loader 的 `Size` 为准;`LoadFromFileAsync` 无 byte[] 时,使用文件长度作为估算值。
- 每次主动卸载或 GC 回收后同步扣减。
- 只作为运行时查看和调试依据,不作为精确 Profiler 替代。
## 四、分批实施顺序
### 第一批:低风险修复
- 修正 `ESF_ABMgr.progress` 无 loader 时除零问题。
- 新增 `TryUnloadPrefabInMemory(...)``TryUnloadAssetInMemory(...)` 业务主动卸载接口。
- 新增 `GetLoadedAssetBundleMemorySize()` 内存统计查询接口。
- 下载队列增加内部并发上限。
- GC 回收时同步移除失效 loader。
- 保持现有公开接口兼容。
### 第二批:加载策略重构
-`ESF_AssetLoader` 引入更清晰的内部状态。
- 非 CDN 模式读取优先级改为 `StreamingAssets Manifest -> PersistentData Manifest -> CDN fallback`
- Manifest 管理支持同时维护 StreamingAssets 与 PersistentData 两套本地 Manifest 查询结果。
- CDN 下载支持外部传入保存根目录,未设置时默认保存到 PersistentData。
- PersistentData 普通文件命中时优先使用 `AssetBundle.LoadFromFileAsync`
- 保留 StreamingAssets、CDN、小游戏缓存的 byte[] 加载路径。
- 保持 `ESF_AssetResponse.GetAsset<T>()` 行为不变。
### 第三批StreamingAssets 异步化
- 移除主链路中的忙等读取。
- 将 StreamingAssets 读取接入 loader 状态机。
- 统一成功、失败、fallback、进度回调。
### 第四批:内部封装整理
- 保留 `ESF_ABMgr` 统一入口和 partial 分布类组织方式。
- 在不改变公开接口的前提下,将 Manifest、Loader 管理、Bundle 读取策略、下载队列、GC 策略封装成更清晰的内部方法组或内部服务。
- 清理重复路径拼接和重复错误处理。
## 五、验证方案
验证以 Editor 菜单测试为主,建议新增或复用一个内部测试入口,用于覆盖以下场景:
| 场景 | 验证点 |
|-----|-------|
| Editor 模式加载 prefab | `LoadInMemory` 成功回调,`GetAsset<GameObject>()` 正常 |
| PersistentData 本地 AB | 普通文件路径优先走 `LoadFromFileAsync` |
| StreamingAssets 加载 | 不出现主线程忙等卡死 |
| Manifest 缺失 | 返回清晰失败日志,不静默卡住 |
| 资源缺失 | 触发 failed 回调,错误信息包含资源路径 |
| 语言回退 | 当前语言缺资源时回退默认语言 |
| CDN/边玩边下 | 触发 `WaitDownload`,下载完成后继续加载 |
| SpriteAtlas 请求 | `SpriteAtlasManager.atlasRequested` 能正确加载图集 |
| 多资源并发下载 | 并发数不超过内部上限 |
| 非 CDN 读取优先级 | StreamingAssets Manifest 包含目标资源时不读取 PersistentData 资源 |
| PersistentData fallback | StreamingAssets Manifest 不包含目标资源时,再查 PersistentData Manifest |
| CDN fallback | 两个本地 Manifest 都不包含目标资源且配置 CDN 时,触发 CDN 下载 |
| 无 CDN fallback | 两个本地 Manifest 都不包含目标资源且未配置 CDN 时,返回明确失败 |
| CDN 模式路径隔离 | CDN 模式不走 StreamingAssets / PersistentData fallback |
| CDN 默认落盘路径 | 未设置外部保存路径时,下载文件保存到 PersistentData 资源根目录 |
| CDN 外部落盘路径 | 设置外部保存路径时,下载文件保存到外部路径并保留相对目录结构 |
| 平台路径隔离 | 平台专用保存路径通过宏或初始化代码传入,通用逻辑不硬编码平台路径 |
| GC 后重新加载 | 回收后能重新创建 loader 并成功加载 |
| 主动卸载资源 | `TryUnloadPrefabInMemory` 返回成功,内存统计下降,再次加载正常 |
| 正在使用资源卸载 | 资源处于 ACTIVE 或仍有框架侧引用时返回 `false`,游戏对象不受影响 |
| 重名资源卸载 | 多个已加载资源同名时返回 `false`,要求后续使用更明确名称或内部诊断工具处理 |
| 查询加载内存 | `GetLoadedAssetBundleMemorySize` 返回 byte 数,加载/卸载后变化合理 |
## 六、风险点
- `LoadFileByDecryptFunc` 依赖 byte[],如果某些本地包需要解密,不能直接走 `LoadFromFileAsync`
- StreamingAssets 在不同平台路径格式不同,异步读取必须保留原有平台兼容性。
- 小游戏环境使用 `MiniGame` 抽象,不能直接套用普通文件系统逻辑。
- GC 逻辑改动会影响资源复用,需要重点验证窗口、图集、材质、文本配置等常驻资源。
- CopyTo 后 `ESF_` 会替换为 `HF_`,新增内部类命名必须遵守替换规则,避免生成后引用不一致。
## 七、Unity 路径参考
- Unity Manual - Streaming Assets: `https://docs.unity3d.com/2022.3/Documentation/Manual/StreamingAssets.html`
- Unity API - `Application.streamingAssetsPath`: `https://docs.unity3d.com/2022.3/Documentation/ScriptReference/Application-streamingAssetsPath.html`
- Unity API - `Application.persistentDataPath`: `https://docs.unity3d.com/2022.3/Documentation/ScriptReference/Application-persistentDataPath.html`
- Unity API - `Application.dataPath`: `https://docs.unity3d.com/2022.3/Documentation/ScriptReference/Application-dataPath.html`
- Unity API - `AssetBundle.LoadFromMemoryAsync`: `https://docs.unity3d.com/2022.3/Documentation/ScriptReference/AssetBundle.LoadFromMemoryAsync.html`
- Unity API - `AssetBundle.LoadFromFileAsync`: `https://docs.unity3d.com/2022.3/Documentation/ScriptReference/AssetBundle.LoadFromFileAsync.html`
- Unity API - `BuildAssetBundleOptions.ChunkBasedCompression`: `https://docs.unity3d.com/2022.3/Documentation/ScriptReference/BuildAssetBundleOptions.ChunkBasedCompression.html`
## 八、默认决策
- 第一阶段只文档化方案,不改 ESF_ 代码。
- 后续代码实现只改 `ESF_` 源头,不直接改 `HF_` 副本。
- 公开接口保持兼容。
- 允许新增 `TryUnloadPrefabInMemory(...)``TryUnloadAssetInMemory(...)``GetLoadedAssetBundleMemorySize()` 作为可选公开能力。
- 允许新增 `SetCdnSaveRootPath(...)` 作为 CDN 热更新落盘根目录配置入口。
- 允许新增内部 `ESF_` 类和内部辅助方法。
- 非 CDN 模式默认按 Manifest 查找顺序读取StreamingAssets Manifest、PersistentData Manifest、CDN fallback。
- PersistentData 普通文件默认优先 `LoadFromFileAsync`
- StreamingAssets、CDN、小游戏路径保持内存加载。
- 下载证书校验暂不作为第一批主目标,后续单独处理。
- CDN 下载保存根目录允许外部传入;未传入时默认使用 PersistentData 资源根目录。
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [资源管理入口](README.md) | 本文件是资源管理模块的子文档 |
| [资源管理使用指南](使用指南.md) | 当前资源加载使用指南 |
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -1,84 +1,53 @@
# 模块名称
## 分类
- 项目功能 / 项目框架 / 编辑器工具
## 目的
TODO一句话说明这个模块是做什么的,从玩家/产品/技术视角写
TODO一句话说明模块解决的问题
## 主要路径
| 类型 | 路径 | 说明 |
|---|---|---|
| 逻辑代码 | `Assets/...` | TODO |
| UI 代码 | `Assets/...` | TODO |
| 资源 | `Assets/...` | TODO |
| 配置 | `XLSX_SRC/...``GoogleProtobufNetwork/...` | TODO |
| 码 | `Assets/...` | TODO |
| 生成代码 | `Assets/...` | 没有则删除本行 |
| 资源/配置 | `Assets/...` | TODO |
## 入口
## 边界
- 运行入口TODO
- 编辑器入口TODO
- 资源入口TODO
- 本模块负责TODO
- 本模块不负责TODO
- 源码与生成结果TODO
## 数据流
```text
TODO用箭头图描述关键流程
TODO入口 -> 处理 -> 输出
```
## 关键类 / API
## 关键类与接口
| 类/方法 | 用途 |
|---|---|
| `ClassName` | TODO |
## 依赖
- 依赖的框架模块TODO
- 依赖的其他功能模块TODO
- 依赖的第三方插件TODO
## 当前状态
## 使用规则
- TODO
## 变更记录
## 验证
| 日期 | 变更内容 | 关联 spec/change | 影响范围 |
|---|---|---|---|
| YYYY-MM-DD | 简要描述 | `[<change-name>]`(对应 `specs/openspec/<change-name>/业务参考总入口.md` | 相关模块/文件 |
- TODO编译、Unity、运行时或目标平台验证方式。
## 当前状态
- 已识别 / 初步梳理 / 待验证 / 已完成 / 未发现实现
## 文档关联
本模块发生变更时,以下文档必须同步更新:
| 文档 | 关联原因 |
|---|---|
| [模块索引](模块索引.md) | 模块入口和状态 |
| [模块登记规则](../rules/30-模块登记规则.md) | 文档分类和结构 |
| [架构说明](../rules/10-架构说明.md) | 代码分层和依赖方向 |
- [模块索引](模块索引.md) — 如果新增/删除模块,或模块分类/状态改变
- [模块登记规则](../rules/30-模块登记规则.md) — 如果新增模块、分类口径或依赖关系变化
- [架构说明](../rules/10-架构说明.md) — 如果涉及架构变化
- TODO链接相关功能/框架模块文档(如依赖方或被依赖方)
- TODO链接相关 spec 目录(如 `../../specs/openspec/<change-name>/业务参考总入口.md`
> 注意:本模板位于 `modules/` 根目录。如果复制到 `modules/features/` 或 `modules/frameworks/` 下,请把 `../rules/` 和 `模块索引.md` 调整为 `../../rules/` 和 `../模块索引.md`。
## 下一步
- [ ] TODO
## 相关文档
- [模块索引](模块索引.md)
- [模块登记规则](../rules/30-模块登记规则.md)
- [架构说明](../rules/10-架构说明.md)
- TODO链接相关功能/框架模块文档
## 是否需要拆分文档
轻量模块可以只保留这个 README。
当模块出现独立计划、持续进度或多项待办时,再增加:
- `progress.md`
- `tasks.md`
轻量模块只保留 README。公共 API 较多时增加 `使用指南.md``开发指南.md`,特殊限制较多时增加 `注意事项.md`;一次性任务文档规则见 [工作流清单](../rules/40-工作流清单.md)。

View File

@@ -1,87 +1,49 @@
# 模块索引
这里是 UnityAI / StrayFog 框架知识库的项目知识总入口。
模块清单、模块状态和模块入口以本文件为准;`rules/` 只维护规则和约束,不重复维护模块事实表。
---
## 重要口径
- **核心框架**支撑游戏运行的内部系统例如实体系统、UI 窗口系统、事件系统、资源管理、对象池、场景管理。
- **编辑器工具**Unity Editor 内使用的生成器、Inspector、菜单、配置工具。
- **业务参考**:外部实际项目的业务实现示例,仅作参考,不作为框架规范。见 [business-reference](../business-reference/playballoons/业务参考总入口.md)。
轻量模块先在本文件登记;复杂模块再拆独立目录。
---
## 状态口径
见 [模块登记规则](../rules/30-模块登记规则.md#状态定义)。
不要使用“已开始”,优先使用“已识别 / 初步梳理 / 待验证 / 已完成 / 未发现实现”。
---
这里是 UnityAI / StrayFog 框架知识总入口。模块入口和状态以本文件为准,状态定义见 [模块登记规则](../rules/30-模块登记规则.md)
## 核心框架
| 框架模块 | 主要路径 | 当前判断 | 状态 |
| 模块 | 主要路径 | 职责 | 状态 |
|---|---|---|---|
| [模拟行为系统](frameworks/通用框架/README.md) | `Assets/StrayFog/Core/SimulateBehaviour/` | 解耦 MonoBehaviour模拟完整生命周期 | 已完成 |
| [对象池](frameworks/内存对象管理/README.md) | `Assets/Game/GameHFScripte/CopyToHF/MemoryManager/` | GameObject 对象池、脚本对象池、类实例池 | 已完成 |
| [事件系统](frameworks/事件系统/README.md) | `Assets/Game/GameHFScripte/CopyToHF/Event/` | CTC/STC 事件参数和事件派发 | 已完成 |
| [资源管理](frameworks/资源管理/README.md) | `Assets/Game/GameHFScripte/CopyToHF/AssetBundle/` | AssetBundle、语言资源、图集、加载规则含 [资源加载重构方案](frameworks/资源管理/资源加载重构方案.md) | 已完成 |
| [相机系统](frameworks/相机系统/README.md) | `Assets/Game/GameHFScripte/CopyToHF/` | 多相机管理、相机堆栈渲染顺序 | 初步梳理 |
| [实体系统](frameworks/实体系统/README.md) | `Assets/Game/GameHFScripte/GameFunction/GameEntity/` | 实体基类、模型/系统、实体池 | 已完成 |
| [UI 窗口系统](frameworks/UI窗口系统/README.md) | `Assets/Game/GameHFScripte/CopyToHF/UIWindowMgr/` | UI 窗口管理、窗口基类、生命周期 | 已完成 |
| [技能系统](frameworks/技能系统/README.md) | `Assets/Game/GameHFScripte/GameFunction/GameEntity/` | 技能触发、释放器、配置联动 | 初步梳理 |
| [场景系统](frameworks/场景系统.md) | `Assets/Game/GameHFScripte/GameFunction/SceneManager/` | 场景加载、资源配置、生命周期 | 初步梳理 |
| [网络系统](frameworks/网络系统.md) | `Assets/Game/GameHFScripte/CopyToHF/Network/` | WebSocket、protobuf消息映射 | 初步梳理 |
| [配置生成](frameworks/数据配置框架.md) | `XLSX_SRC/``Assets/AutoBuildScript/` | 表格配置源、解析、生成代码 | 初步梳理 |
| [Hotfix 启动](frameworks/Hotfix启动.md) | `Assets/Game/GameHFScripte/GameFunction/GameRoot/` | hotfix 侧根入口和功能目录聚合 | 初步梳理 |
---
| [通用框架](frameworks/通用框架/README.md) | `Assets/StrayFog/Core/``GameGeneralScripte/` | 模拟行为、全局数据和通用工具 | 已完成 |
| [对象池](frameworks/内存对象管理/README.md) | `CopyToHF/MemoryManager/` | GameObject、脚本和类实例池 | 已完成 |
| [事件系统](frameworks/事件系统/README.md) | `CopyToHF/Event/` | CTC/STC 事件派发 | 已完成 |
| [资源管理](frameworks/资源管理/README.md) | `CopyToHF/AssetBundle/``StrayFog/Core/SFSubPackageV2.cs` | AssetBundle、图集、卸载、分包 V2 和下载 | 已完成 |
| [相机系统](frameworks/相机系统/README.md) | `GameHFScripte/` | 多相机堆栈渲染顺序 | 初步梳理 |
| [实体系统](frameworks/实体系统/README.md) | `GameFunction/GameEntity/` | 实体、System、Model 和实体池 | 已完成 |
| [UI 窗口系统](frameworks/UI窗口系统/README.md) | `CopyToHF/UIWindowMgr/``GameFunction/UIWindows/` | 窗口生命周期、加载、卸载和列表 | 已完成 |
| [技能系统](frameworks/技能系统/README.md) | `GameFunction/GameEntity/` | 技能触发、释放配置联动 | 初步梳理 |
| [场景系统](frameworks/场景系统.md) | `GameFunction/SceneManager/` | 场景加载生命周期 | 初步梳理 |
| [网络系统](frameworks/网络系统.md) | `CopyToHF/Network/``GameFunction/WebSocket/` | WebSocket、protobuf消息映射 | 初步梳理 |
| [数据配置](frameworks/数据配置框架.md) | 项目表格、proto 和生成目录 | 配置源、解析和代码生成 | 初步梳理 |
| [Hotfix 启动](frameworks/Hotfix启动.md) | `GameFunction/GameRoot/``GameDllManager` | AOT、热更 DLL 和业务入口 | 初步梳理 |
## 编辑器工具
| 工具模块 | 主要路径 | 当前判断 | 状态 |
| 模块 | 主要路径 | 职责 | 状态 |
|---|---|---|---|
| [SF Editor](frameworks/编辑器工具/README.md#sf-editor) | `Assets/StrayFog/Editor/` | StrayFog 编辑器扩展 | 待验证 |
| [项目 Editor Assets](frameworks/编辑器工具/README.md#project-editor-assets) | `Assets/Editor/` | 项目编辑器资源与脚本 | 待验证 |
| [UI 拼接工具](frameworks/编辑器工具/UI拼接工具.md) | `Assets/Editor/Command/` | UI 预制体 JSON 双向转换 | 待验证 |
| [MCP 工具](frameworks/编辑器工具/MCP工具/README.md) | `Tools/CodexUnityMcp/` | Codex Unity Editor MCP | 待验证 |
| [SF Editor](frameworks/编辑器工具/README.md#sf-editor) | `Assets/StrayFog/Editor/` | StrayFog 生成器和 Editor 扩展 | 初步梳理 |
| [项目 Editor Assets](frameworks/编辑器工具/README.md#project-editor-assets) | `Assets/Editor/` | 项目级 Editor 工具 | 已识别 |
| [UI 拼接工具](frameworks/编辑器工具/UI拼接工具.md) | `Assets/Editor/Command/` | UI Prefab 与 JSON 双向转换 | 已完成 |
| [MCP 工具](frameworks/编辑器工具/MCP工具/README.md) | `Tools/CodexUnityMcp/`、Unity Bridge | Codex Unity Editor 工具桥 | 已完成 |
---
## 业务参考
## 文档拆分规则
- [PlayBalloons](../business-reference/playballoons/业务参考总入口.md):外部项目业务实现示例,不作为框架规范。
- 核心框架按技术职责命名,可以对应 `GameEntity``UIWindowMgr``Network``AssetBundle``Event` 这类内部系统。
- 轻量模块只在本文件登记。
- 复杂模块再拆 `modules/<module>/`
- `模块索引.md` 写稳定说明。
- `progress.md` 写阶段状态。
- `tasks.md` 写下一步待办。
- 跨模块计划放到 [plans](../plans/)。
## 文档拆分
---
## 相关文档
- [项目规则入口](../rules/项目规则入口.md)AI 工作方式、扫描范围、归档规则和规则索引。
- [模块登记规则](../rules/30-模块登记规则.md):分类口径、状态定义和新增模块登记规则。
- [架构说明](../rules/10-架构说明.md):总体分层、程序集边界和依赖方向。
---
- 轻量模块只保留 README。
- 公共 API 较多时增加使用指南或开发指南。
- 特殊限制较多时增加注意事项。
- 一次性任务不扩展模块文档结构,具体规则见 [工作流清单](../rules/40-工作流清单.md)。
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [rules/30-模块登记规则.md](../rules/30-模块登记规则.md) | 模块索引是模块登记规则的唯一事实来源 |
| [rules/10-架构说明.md](../rules/10-架构说明.md) | 模块分类、分层与架构说明互相影响 |
| [rules/项目规则入口.md](../rules/项目规则入口.md) | 模块索引是项目规则入口引用的项目知识入口 |
| [ai/AI接入入口.md](../ai/AI接入入口.md) | AI 接入入口引用模块索引作为必读文档 |
| [modules/frameworks/*](frameworks/) | 框架模块文档新增/拆分时需同步索引 |
| [模块登记规则](../rules/30-模块登记规则.md) | 分类、状态和拆分规则 |
| [架构说明](../rules/10-架构说明.md) | 模块代码分层和源码边界 |
| [项目规则入口](../rules/项目规则入口.md) | 项目知识入口 |
| [AI 接入入口](../ai/AI接入入口.md) | AI 必读模块索引 |