【整合】 框架使用规则

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,33 +1,19 @@
# UnityAI - Claude Code 入口
本文件是 Claude Code 的入口包装
处理本项目任务时,**先读取 `ai/AI接入入口.md`**,再按其中说明读取项目规则、项目知识和共享 hook 文档。
---
## AI 接入入口
- **[ai/AI接入入口.md](ai/AI接入入口.md)** — 所有 AI / IDE 工具的接入总入口。
处理本项目任务时,先读取 [AI 工具接入入口](ai/AI接入入口.md),再按其中顺序读取项目规则、工作流、模块知识和共享检查
## 说明
- 不要把项目规则重复写在你的系统提示里
- 所有项目规则维护在 `rules/`
- 所有项目模块知识维护在 `modules/`
- 共享 hook 规则和检查脚本维护在 `hooks/`
- 若已配置 OpenSpecClaude Code 用户可使用命令:`/opsx:propose``/opsx:apply``/opsx:archive`
- 只有用户明确说“提交”“提交代码”“commit”或使用 `/commit` 时,才触发提交流程。
- 若已配置自定义提交命令 `/commit` 或 git `prepare-commit-msg` hook按项目实际配置使用。
---
- 项目规则只维护在 `rules/`
- 框架知识只维护在 `modules/`
- 检查脚本只维护在 `hooks/`
- 重大变更按 [工作流清单](rules/40-工作流清单.md) 在对话中确认,稳定结论写入模块文档
- 只有用户明确要求提交或推送时才触发 Git 写操作
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [ai/AI接入入口.md](ai/AI接入入口.md) | CLAUDE.md 指向 AI 接入入口,入口变更需同步 |
| [AGENTS.md](AGENTS.md) | Claude Code 入口与 AGENTS.md 互为替代入口 |
| [RULES.md](RULES.md) | 通用兼容入口需与 CLAUDE.md 保持一致 |
| [AI 工具接入入口](ai/AI接入入口.md) | Claude Code 统一接入入口 |
| [AGENTS.md](AGENTS.md) | Codex 替代入口 |
| [RULES.md](RULES.md) | 通用兼容入口 |

111
README.md
View File

@@ -1,108 +1,69 @@
# UnityAI - StrayFog 框架文档中心
本仓库是 **StrayFog 框架的知识库**,同时服务两类读者:
UnityAI 是 StrayFog 框架的通用工作流与知识库。它面向 AI 工具、业务开发人员和框架维护人员;具体 Unity 项目提供真实实现与验证环境,当前主要验证对象为 `CrystalBattle_Client`
- **人工开发者**:通过 `modules/frameworks/` 下的框架模块文档和 `rules/` 下的规则文档学习使用 StrayFog 框架。
- **AI 工具**:通过 `ai/``rules/``modules/``hooks/` 下的规则、模块、hook 文档快速理解框架结构和工作方式。
---
## 🚀 快速入口
## 快速入口
| 读者 | 入口 |
|------|------|
| AI 工具 | [AGENTS.md](AGENTS.md) / [CLAUDE.md](CLAUDE.md) / [RULES.md](RULES.md) → [ai/AI接入入口.md](ai/AI接入入口.md) |
| 人工开发者 | 继续阅读下方中文文档索引 |
|---|---|
| Codex | [AGENTS.md](AGENTS.md) |
| Claude Code | [CLAUDE.md](CLAUDE.md) |
| 其他 AI / 人工 | [RULES.md](RULES.md) |
| 项目工作流 | [工作流清单](rules/40-工作流清单.md) |
| 框架知识 | [模块索引](modules/模块索引.md) |
---
## 项目规则
## 🤖 AI 工具说明
- [项目概览](rules/00-项目概览.md)
- [架构说明](rules/10-架构说明.md)
- [开发规则](rules/20-开发规则.md)
- [模块登记规则](rules/30-模块登记规则.md)
- [工作流清单](rules/40-工作流清单.md)
- [文档同步规则](rules/50-文档同步规则.md)
- [扫描范围](rules/70-项目设置.md)
处理本仓库任务时AI 工具应:
## 核心框架
1. 读取 [ai/AI接入入口.md](ai/AI接入入口.md)
2. 读取 [rules/项目规则入口.md](rules/项目规则入口.md)
3. 按需读取 [modules/模块索引.md](modules/模块索引.md) 中的框架模块
4. 如需了解外部项目业务参考,读取 [business-reference/playballoons/业务框架映射接口.md](business-reference/playballoons/业务框架映射接口.md)
---
## 📋 中文文档索引
### 框架架构
- [通用框架](modules/frameworks/通用框架/README.md) — General/StrayFog Core 通用能力
- [框架架构分析](modules/frameworks/通用框架/框架架构.md) — StrayFog 整体架构分析
### 核心框架
- [通用框架](modules/frameworks/通用框架/README.md)
- [实体系统](modules/frameworks/实体系统/README.md)
- [Entity 开发指南](modules/frameworks/实体系统/开发指南.md)
- [Unity Entities 注意事项](modules/frameworks/实体系统/注意事项.md)
- [技能系统](modules/frameworks/技能系统/README.md)
- [通用技能系统使用指南](modules/frameworks/技能系统/使用指南.md)
- [UI 窗口系统](modules/frameworks/UI窗口系统/README.md)
- [UI 开发指南](modules/frameworks/UI窗口系统/开发指南.md)
- [UI 界面拼接设计指南](modules/frameworks/UI窗口系统/界面拼接.md)
- [界面拼接](modules/frameworks/UI窗口系统/界面拼接.md)
- [事件系统](modules/frameworks/事件系统/README.md)
- [事件系统使用指南](modules/frameworks/事件系统/使用指南.md)
- [对象池与运行时对象管理](modules/frameworks/内存对象管理/README.md)
- [对象池使用指南](modules/frameworks/内存对象管理/使用指南.md)
- [相机系统](modules/frameworks/相机系统/README.md)
- [相机系统使用指南](modules/frameworks/相机系统/使用指南.md)
- [资源管理](modules/frameworks/资源管理/README.md)
- [资源管理使用指南](modules/frameworks/资源管理/使用指南.md)
- [资源加载重构方案](modules/frameworks/资源管理/资源加载重构方案.md)
- [场景系统](modules/frameworks/场景系统.md)
- [网络系统](modules/frameworks/网络系统.md)
- [数据配置框架](modules/frameworks/数据配置框架.md)
- [Hotfix 启动](modules/frameworks/Hotfix启动.md)
- [第三方插件](modules/frameworks/第三方插件.md)
### 编辑器工具
## 编辑器工具
- [编辑器工具](modules/frameworks/编辑器工具/README.md)
- [UI 拼接工具](modules/frameworks/编辑器工具/UI拼接工具.md)
- [MCP 工具](modules/frameworks/编辑器工具/MCP工具/README.md)
- [UI 拼接工具](modules/frameworks/编辑器工具/UI拼接工具.md)
- [MCP 工具](modules/frameworks/编辑器工具/MCP工具/README.md)
- [CrystalBattle Codex 与 Unity MCP 使用规则](modules/frameworks/编辑器工具/MCP工具/CrystalBattle_Codex_Unity_MCP使用规则.md)
### 项目规则
- [项目规则入口](rules/项目规则入口.md)
- [架构说明](rules/10-架构说明.md)
- [开发规则](rules/20-开发规则.md)
- [工作流清单](rules/40-工作流清单.md)
## 工作方式
### 编辑器工具脚本
- [UI 工具](modules/frameworks/编辑器工具/UI工具/) — UI 预制体与 JSON 双向转换工具
- UnityAI 定义通用标准,不把单个项目的业务实现写成框架强制规则。
- 重大变更按 [工作流清单](rules/40-工作流清单.md) 在对话中确认,长期结论直接更新模块文档。
- 公共接口、架构和稳定流程变化后,直接更新对应模块文档。
- `ESF_` 框架源码通过 CopyToX 生成 `HF_` 代码,生成目录禁止手改。
- Git 提交和推送只在用户明确要求时执行,文档仓库与 Unity 项目分别处理。
---
## 业务参考
## 🎯 快速入门
| 场景 | 推荐文档 |
|-----|---------|
| 新人开发 | [开发规则](rules/20-开发规则.md) |
| 使用对象池 | [对象池与运行时对象管理](modules/frameworks/内存对象管理/README.md) |
| 使用事件系统 | [事件系统](modules/frameworks/事件系统/README.md) |
| 遇到问题 | [开发规则/常见问题](rules/20-开发规则.md#常见问题) |
| AI 工具接入 | [ai/AI接入入口.md](ai/AI接入入口.md) |
---
## 📦 业务参考
- [business-reference/playballoons/](business-reference/playballoons/) — 来自 PlayBalloons 实际项目的业务参考,与 StrayFog 框架核心分离。
---
- [PlayBalloons 业务参考](business-reference/playballoons/):外部项目案例,不作为框架规范。
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [ai/AI接入入口.md](ai/AI接入入口.md) | AI 工具入口说明 |
| [rules/项目规则入口.md](rules/项目规则入口.md) | 项目规则总入口 |
| [modules/模块索引.md](modules/模块索引.md) | 框架模块索引 |
| [rules/00-项目概览.md](rules/00-项目概览.md) | 项目概览中的人工开发者使用方式 |
---
**版本**: v1.0
**适用项目**: GiftGameX
| [AI 工具接入入口](ai/AI接入入口.md) | AI 工具统一入口 |
| [项目规则入口](rules/项目规则入口.md) | 项目规则总入口 |
| [模块索引](modules/模块索引.md) | 模块清单与状态 |
| [项目概览](rules/00-项目概览.md) | UnityAI 定位与适用范围 |

View File

@@ -1,65 +1,59 @@
# AI 工具接入入口
本文件是 UnityAI / StrayFog 框架知识库面向 Codex、Claude Code、Cursor、Windsurf、GitHub Copilot 等 AI / IDE 工具的统一接入说明
本文件是 Codex、Claude Code、Cursor、Windsurf、GitHub Copilot 等工具进入 UnityAI 的统一入口。根目录 `AGENTS.md``CLAUDE.md``RULES.md` 只负责跳转,不复制项目规则
根目录入口文件只做适配和跳转:
- `AGENTS.md`Codex 默认项目入口。
- `CLAUDE.md`Claude Code 默认项目入口。
- `RULES.md`:通用兼容入口,给其他 AI、IDE 或人工快速查看。
这些入口文件不要复制项目规则;真实内容统一维护在 ai/rules/modules/hooks/ 下。
## 所有 AI 先读
处理本项目任务时,按顺序读取:
## 读取顺序
1. [项目规则入口](../rules/项目规则入口.md)
2. [项目知识入口](../modules/模块索引.md)
3. [共享 hook 入口](../hooks/Hook共享层.md)
2. [工作流清单](../rules/40-工作流清单.md)
3. [模块索引](../modules/模块索引.md)
4. 任务相关的模块文档
5. [共享检查](../hooks/Hook共享层.md)
然后根据任务范围按需读取具体规则、模块、spec、plan 或 hook 文档
随后读取目标 Unity 项目的真实代码、Prefab、配置、项目版本和 Git 状态。UnityAI 是通用标准,具体工程是实现和验证依据
## 文档分层
| 层级 | 文档 | 用途 |
| 层级 | 位置 | 用途 |
|---|---|---|
| AI 接入 | `ai/AI接入入口.md` | 说明不同 AI / IDE 工具如何进入项目 |
| 项目规则 | `rules/项目规则入口.md` | 工作方式、扫描范围、任务流程、归档规则 |
| 项目知识 | `modules/模块索引.md` | 项目框架、核心功能、编辑器工具总入口 |
| 业务参考 | `business-reference/` | 外部实际项目业务参考,与框架核心分离 |
| Hook 共享层 | `hooks/Hook共享层.md` | 共享 hook 原则、检查脚本、触发方式 |
| Spec / Plan 层 | `specs/Spec索引.md``plans/` | 大需求、新模块、跨模块计划和归档 |
| AI 接入 | `ai/AI接入入口.md` | 工具如何进入工作流 |
| 项目规则 | `rules/` | 架构、开发、工作流、文档同步和扫描范围 |
| 框架知识 | `modules/` | 模块职责、API、数据流和验证方式 |
| 业务参考 | `business-reference/` | 外部项目案例,不定义框架标准 |
| 共享检查 | `hooks/` | 只读工作流检查和提交检查 |
| 可执行工具 | `Tools/` | MCP Server 等工具实现 |
## Hook 接入原则
重大变更的确认和长期文档落盘方式统一遵循 [工作流清单](../rules/40-工作流清单.md)。
- 共享 hook 规则、脚本和文档关联逻辑统一放在 `hooks/`
- 各 AI 工具自己的目录只放适配器,不重复维护 hook 逻辑。
- 若已配置 Codex适配器 `.codex/hooks/archive-reminder.ps1` 可调用 `hooks/archive-check.ps1`
- Claude Code 可以通过 command / hook 配置调用 `hooks/archive-check.ps1`
- 只有用户明确说“提交”“提交代码”“commit”时才触发提交流程。
- 若项目配置了自定义提交命令 `/commit` 或 git `prepare-commit-msg` hook按实际配置使用默认提交信息格式为 `[模块][feat]:内容描述`
- 如需在关联文档中自动留下变更提醒,可调用 `hooks/sync-doc-associations.ps1`(只追加、不覆盖,执行后需 review
- 其他 AI / IDE 如果支持任务结束 hook也应调用 `hooks/archive-check.ps1`
- 如果某个工具不支持自动 hook任务结束时手动运行或按 `hooks/Hook共享层.md` 执行归档检查。
## 工具接入原则
## 新接入 AI 工具时
- 工具专属入口只链接本文件,不复制 `rules/``modules/` 内容。
- 只有用户明确要求提交或推送时才执行 Git 写操作。
- UnityAI 与目标 Unity 项目分别检查、验证和提交。
- 任务结束时运行只读工作流检查:
1. 确认该工具默认读取什么入口文件。
2. 如果该工具有默认入口文件,只在入口文件中指向 `ai/AI接入入口.md`
3. 如果该工具支持 hook配置它调用 `hooks/archive-check.ps1`
4. 不要把 `rules/``modules/``hooks/` 的内容复制进工具私有目录。
5. 如需新增工具专属说明,只写适配方式,不写项目规则副本。
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File hooks/workflow-check.ps1 `
-UnityProjectRoot <Unity工程路径>
```
- 检查脚本只给出状态、生成边界、验证和文档建议,不自动写文件。
## 接入新工具
1. 确认工具自动读取的入口文件。
2. 在入口文件中链接本文件。
3. 如支持任务结束命令,调用 `hooks/workflow-check.ps1`
4. 不在工具私有目录维护项目规则副本。
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [AGENTS.md](../AGENTS.md) | Codex 入口应指向本 AI 接入入口 |
| [CLAUDE.md](../CLAUDE.md) | Claude Code 入口应指向本 AI 接入入口 |
| [RULES.md](../RULES.md) | 通用兼容入口应指向本 AI 接入入口 |
| [rules/项目规则入口.md](../rules/项目规则入口.md) | 项目规则入口由本文档引用 |
| [modules/模块索引.md](../modules/模块索引.md) | 项目知识入口由本文档引用 |
| [hooks/Hook共享层.md](../hooks/Hook共享层.md) | 共享 hook 入口由本文档引用 |
| [AGENTS.md](../AGENTS.md) | Codex 入口 |
| [CLAUDE.md](../CLAUDE.md) | Claude Code 入口 |
| [RULES.md](../RULES.md) | 通用兼容入口 |
| [项目规则入口](../rules/项目规则入口.md) | 项目规则入口 |
| [工作流清单](../rules/40-工作流清单.md) | 唯一任务流程 |
| [模块索引](../modules/模块索引.md) | 项目知识入口 |
| [共享检查](../hooks/Hook共享层.md) | 脚本入口 |

View File

@@ -156,12 +156,6 @@ LoginAuthS2C
| 规则 | 文档 | 关联原因 |
|---|---|---|
| 模块登记规则 | [30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 登录模块登记与状态 |
| 归档规则 | [50-归档规则.md](../../../rules/50-归档规则.md) | 变更后需同步归档相关文档 |
| 文档同步规则 | [50-文档同步规则.md](../../../rules/50-文档同步规则.md) | 长期接口或流程变化时同步模块文档 |
| 架构说明 | [10-架构说明.md](../../../rules/10-架构说明.md) | 登录为游戏入口,涉及整体架构 |
| 开发规则 | [20-开发规则.md](../../../rules/20-开发规则.md) | 网络协议、UI 窗口开发规范 |
### 关联 Spec / OpenSpec
| Spec | 文档 | 关联原因 |
|---|---|---|
| 规则工作流 Spec | [2026-06-11-rules-workflow-v1.md](../../../specs/Spec索引.md) | 项目文档和规则系统建立 |

View File

@@ -163,12 +163,6 @@ PlayerResourceChangeS2C
| 规则 | 文档 | 关联原因 |
|---|---|---|
| 模块登记规则 | [30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 背包模块登记与状态 |
| 归档规则 | [50-归档规则.md](../../../rules/50-归档规则.md) | 变更后需同步归档相关文档 |
| 文档同步规则 | [50-文档同步规则.md](../../../rules/50-文档同步规则.md) | 长期接口或流程变化时同步模块文档 |
| 架构说明 | [10-架构说明.md](../../../rules/10-架构说明.md) | 背包为全局数据模块,涉及架构分层 |
| 开发规则 | [20-开发规则.md](../../../rules/20-开发规则.md) | 数据管理、事件使用规范 |
### 关联 Spec / OpenSpec
| Spec | 文档 | 关联原因 |
|---|---|---|
| 规则工作流 Spec | [2026-06-11-rules-workflow-v1.md](../../../specs/Spec索引.md) | 项目文档和规则系统建立 |

View File

@@ -1,89 +1,59 @@
# Hook 共享层
# 共享检查
> 本目录存放 UnityAI / StrayFog 框架知识库共享的 hook 脚本和规则
>
> 各 AI 工具自己的私有目录只放适配器,不重复维护 hook 逻辑。
`hooks/` 提供只读工作流检查和提交信息检查。脚本不自动修改代码、资源或文档
---
## 脚本清单
| 脚本 | 用途 |
|---|---|
| [archive-check.ps1](archive-check.ps1) | 读取 git diff 中变更的 `.md` 文件,解析 `## 文档关联` 表,输出需要同步检查的关联文档 |
| [sync-doc-associations.ps1](sync-doc-associations.ps1) | Python 脚本的 PowerShell 包装器 |
| [sync_doc_associations.py](sync_doc_associations.py) | 在关联文档中追加变更提醒(只追加、不覆盖,执行后需 review |
| [commit-check.ps1](commit-check.ps1) | 提交前检查:推断变更文件所属模块,校验提交信息格式 |
| [commit-module-mappings.json](commit-module-mappings.json) | `commit-check.ps1` 使用的目录-模块映射配置 |
---
## 归档检查
所有任务结束后都应执行归档检查,判断是否需要同步更新长期文档。
## 工作流检查
```powershell
# PowerShell
.\docs\hooks\archive-check.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File hooks/workflow-check.ps1 `
[-UnityProjectRoot <path>] [-DocsRoot <path>]
```
该脚本只输出建议,不会自动修改任何文档。最终是否更新关联文档,需要用户确认。
项目定位顺序:
---
1. `-UnityProjectRoot`
2. `UNITY_PROJECT_ROOT`
3. 当前目录本身是 Unity 项目
4. UnityAI 同级唯一 Unity 项目
5. 未找到时只检查文档仓库
## 文档关联同步
检查内容:
如需在关联文档中自动留下变更提醒,可运行:
- UnityAI 与 Unity 项目的 tracked、staged、unstaged 和 untracked 文件。
- 中文路径和未跟踪 Markdown。
- ESF/General 源码与 CopyTo 生成目标是否同时变化。
- 改动路径对应的建议构建、Unity 和平台验证。
- 可能需要复核的模块文档。
- 本次修改 Markdown 中的相对链接。
```powershell
.\docs\hooks\sync-doc-associations.ps1
```
该脚本只追加、不覆盖,执行后必须 review。
---
`archive-check.ps1` 是兼容包装,新接入统一调用 `workflow-check.ps1`
## 提交检查
提交前可运行:
```powershell
.\docs\hooks\commit-check.ps1 -Message "[entity][docs]: 补充实体池说明"
powershell -NoProfile -ExecutionPolicy Bypass -File hooks/commit-check.ps1 `
-RepositoryRoot <path> `
-Message "[模块][类型]: 描述"
```
检查项包括:
脚本优先检查 staged 文件;没有 staged 文件时检查整个工作树。支持 UnityAI 和目标 Unity 项目,路径映射维护在 `commit-module-mappings.json`
- 提交信息是否符合 `[模块][类型]: 描述` 格式
- 变更文件是否能映射到已知模块
- 模块映射配置来自 [commit-module-mappings.json](commit-module-mappings.json)
允许类型:`feat``fix``docs``refactor``test``chore`
---
提交和推送仍然只在用户明确要求时执行。检查通过不代表可以自动提交。
## 触发方式
## 维护规则
| 触发方式 | 命令/路径 | 说明 |
|---|---|---|
| Codex Stop hook | `.codex/hooks/archive-reminder.ps1` | 若已配置,每次任务结束自动调用 `hooks/archive-check.ps1` |
| Claude Code 手动 | 直接运行脚本 | 任务结束时手动调用 `hooks/archive-check.ps1` |
| 手动脚本 | `hooks/archive-check.ps1` | 直接运行 PowerShell 脚本 |
---
## 新增 Hook 时
1. 把脚本放入 `hooks/`
2. 在本文件中登记。
3. 在 [ai/AI接入入口.md](../ai/AI接入入口.md) 中说明触发方式。
4. 如果涉及提交信息格式,同步更新 [rules/40-工作流清单.md](../rules/40-工作流清单.md)。
---
- 新增稳定模块路径时更新工作流与提交映射。
- 脚本保持 Windows PowerShell 5.1 可运行,不依赖写入仓库的缓存。
- 脚本只输出建议;源码边界警告不自动回退任何文件。
- 新增检查后补无改动、中文路径、无 Unity 工程和双仓库场景验证。
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [ai/AI接入入口.md](../ai/AI接入入口.md) | AI 接入入口引用 hook 共享层 |
| [rules/项目规则入口.md](../rules/项目规则入口.md) | 规则入口索引归档触发机制 |
| [rules/40-工作流清单.md](../rules/40-工作流清单.md) | 提交前检查与工作流清单互相引用 |
| [工作流清单](../rules/40-工作流清单.md) | 检查对应的任务阶段 |
| [文档同步规则](../rules/50-文档同步规则.md) | 文档影响判断 |
| [开发规则](../rules/20-开发规则.md) | 源码与生成代码约束 |
| [AI 接入入口](../ai/AI接入入口.md) | AI 工具调用入口 |

View File

@@ -1,181 +1,10 @@
# hooks/archive-check.ps1
# Check changed .md files and suggest related docs to update.
# This script only outputs suggestions; it does NOT modify any files.
#
# Mechanism:
# For every changed .md file, parse its "## 文档关联" section and extract
# Markdown links. Those links are the documents that may need syncing.
param(
[string]$UnityProjectRoot = '',
[string]$DocsRoot = ''
)
$ErrorActionPreference = "SilentlyContinue"
$scriptPath = $MyInvocation.MyCommand.Path
if (-not [System.IO.Path]::IsPathRooted($scriptPath)) {
$scriptPath = Join-Path (Get-Location) $scriptPath
}
$scriptPath = [System.IO.Path]::GetFullPath($scriptPath)
$scriptDir = Split-Path -Parent $scriptPath
$projectRoot = [System.IO.Path]::GetFullPath((Join-Path $scriptDir ".."))
Set-Location $projectRoot
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
function Resolve-RelativePath {
param(
[string]$BaseFile,
[string]$LinkPath
)
# Skip anchors, query strings, and external URLs
if ($LinkPath -match '^[a-zA-Z][a-zA-Z0-9+.-]*://') { return $null }
if ([string]::IsNullOrWhiteSpace($LinkPath)) { return $null }
# Drop fragment
$pathOnly = ($LinkPath -split '#')[0]
if ([string]::IsNullOrWhiteSpace($pathOnly)) { return $null }
$baseDir = Split-Path -Parent $BaseFile
$combined = Join-Path $baseDir $pathOnly
try {
$resolved = [System.IO.Path]::GetFullPath($combined)
} catch {
return $null
}
# Convert to relative path from project root, normalizing separators
$fullProjectRoot = [System.IO.Path]::GetFullPath($projectRoot)
if ($resolved.StartsWith($fullProjectRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
$relative = $resolved.Substring($fullProjectRoot.Length).TrimStart('\', '/')
return $relative -replace '\\', '/'
}
return $pathOnly -replace '\\', '/'
}
function Get-DocumentAssociations {
param(
[string]$MdFile
)
$fullPath = Join-Path $projectRoot $MdFile
if (-not (Test-Path $fullPath)) { return @() }
$lines = [System.IO.File]::ReadAllLines($fullPath, [System.Text.Encoding]::UTF8)
$associations = @()
$inSection = $false
$inCodeBlock = $false
foreach ($line in $lines) {
# Toggle fenced code block state (``` or ~~~)
if ($line -match '^\s*```' -or $line -match '^\s*~~~') {
$inCodeBlock = -not $inCodeBlock
continue
}
if ($inCodeBlock) { continue }
# Match exactly '## 文档关联' (not '## 文档关联机制' etc.).
if ($line -match "^##\s+文档关联\s*$") {
$inSection = $true
continue
}
if ($inSection -and $line -match '^##\s+') {
break
}
if ($inSection) {
# Extract markdown links: [text](path)
$regex = '\[(?<text>[^\]]*)\]\((?<path>[^\)]+)\)'
$matches = [regex]::Matches($line, $regex)
foreach ($match in $matches) {
$linkPath = $match.Groups['path'].Value.Trim()
$resolved = Resolve-RelativePath -BaseFile $MdFile -LinkPath $linkPath
if ($resolved) {
$associations += $resolved
}
}
}
}
return $associations | Select-Object -Unique
}
function Test-WildcardMatch {
param(
[string]$Pattern,
[string]$Path
)
$regex = "^" + ($Pattern -replace "\.", "\." -replace "\*", ".*" -replace "\?", ".") + "$"
return $Path -match $regex
}
# ---------------------------------------------------------------------------
# Collect changed .md files (tracked modifications + untracked new files)
# ---------------------------------------------------------------------------
$changedFiles = @(cmd /c "git diff --name-only 2>nul") | Where-Object { $_ }
if ($changedFiles.Count -eq 0) {
$changedFiles = @(cmd /c "git diff --name-only --cached 2>nul") | Where-Object { $_ }
}
# Also include untracked .md files so new documents are checked too
$untrackedFiles = @(cmd /c "git ls-files --others --exclude-standard 2>nul") | Where-Object { $_ }
$allChangedFiles = @($changedFiles) + @($untrackedFiles) | Where-Object { $_ } | Select-Object -Unique
if (-not $allChangedFiles) {
Write-Output "[archive-check] No documentation changes detected (git diff is empty and no untracked .md files)."
exit 0
}
$changedMdFiles = $allChangedFiles | Where-Object { $_ -like "*.md" } | Select-Object -Unique
if ($changedMdFiles.Count -eq 0) {
Write-Output "[archive-check] No .md documentation changes detected."
exit 0
}
# ---------------------------------------------------------------------------
# Build suggestions from each changed file's "## 文档关联" section
# ---------------------------------------------------------------------------
$suggestions = @{}
foreach ($changed in $changedMdFiles) {
$relatedDocs = Get-DocumentAssociations -MdFile $changed
foreach ($related in $relatedDocs) {
# Normalize related path
$relatedNormalized = $related -replace '\\', '/'
if (-not $suggestions.ContainsKey($relatedNormalized)) {
$suggestions[$relatedNormalized] = @()
}
if ($suggestions[$relatedNormalized] -notcontains $changed) {
$suggestions[$relatedNormalized] += $changed
}
}
}
# ---------------------------------------------------------------------------
# Output results
# ---------------------------------------------------------------------------
Write-Output "[archive-check] Changed documentation files:"
foreach ($changed in $changedMdFiles | Sort-Object) {
Write-Output " - $changed"
}
if ($suggestions.Count -eq 0) {
Write-Output ""
Write-Output "[archive-check] No associations found in changed files' `"## 文档关联`" sections."
exit 0
}
Write-Output ""
Write-Output "[archive-check] Related docs that may need updating:"
foreach ($related in $suggestions.Keys | Sort-Object) {
$sources = $suggestions[$related] | Sort-Object
$sourceList = $sources -join ", "
Write-Output " - $related (mentioned by: $sourceList)"
}
Write-Output ""
Write-Output "[archive-check] Please confirm whether to sync-update the above docs."
exit 0
Write-Warning 'archive-check.ps1 is a compatibility entry. Use workflow-check.ps1 for new integrations.'
& (Join-Path $PSScriptRoot 'workflow-check.ps1') `
-UnityProjectRoot $UnityProjectRoot `
-DocsRoot $DocsRoot
exit $LASTEXITCODE

View File

@@ -1,111 +1,106 @@
<#
.SYNOPSIS
提交前共享检查:推断变更文件所属模块,校验提交信息格式。
.DESCRIPTION
读取 git diff 中变更的文件,根据 hooks/commit-module-mappings.json
推断模块,然后校验提交信息是否符合 [模块][类型]: 描述 格式。
.PARAMETER Message
待校验的提交信息。
.EXAMPLE
.\docs\hooks\commit-check.ps1 -Message "[entity][docs]: 补充实体池说明"
#>
param(
[Parameter(Mandatory = $true)]
[string]$Message
[string]$Message,
[string]$RepositoryRoot = ''
)
$ErrorActionPreference = 'Stop'
if ([string]::IsNullOrWhiteSpace($RepositoryRoot)) {
$RepositoryRoot = (Get-Location).Path
}
$RepositoryRoot = [System.IO.Path]::GetFullPath($RepositoryRoot)
$gitRoot = & git -C $RepositoryRoot rev-parse --show-toplevel 2>$null | Select-Object -First 1
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($gitRoot)) {
Write-Error "Git repository was not found: $RepositoryRoot"
exit 1
}
$RepositoryRoot = [System.IO.Path]::GetFullPath($gitRoot)
$mappingFile = Join-Path $PSScriptRoot 'commit-module-mappings.json'
if (-not (Test-Path $mappingFile)) {
Write-Error "Module mapping file not found: $mappingFile"
$mapping = Get-Content -LiteralPath $mappingFile -Raw -Encoding UTF8 | ConvertFrom-Json
function Get-CommitFiles {
param([string]$Root)
$staged = @(& git -C $Root -c core.quotepath=false diff --cached --name-only --diff-filter=ACMRD)
$staged = @($staged | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
if ($staged.Count -gt 0) {
return $staged
}
$files = @()
$statusLines = & git -C $Root -c core.quotepath=false status --porcelain=v1 --untracked-files=all
foreach ($line in $statusLines) {
if ([string]::IsNullOrWhiteSpace($line) -or $line.Length -lt 4) {
continue
}
$path = $line.Substring(3).Trim()
if ($path.Contains(' -> ')) {
$path = ($path -split ' -> ')[-1]
}
$files += $path.Replace('\', '/')
}
return @($files | Select-Object -Unique)
}
function Get-Modules {
param([string[]]$Files)
$modules = @()
foreach ($file in $Files) {
foreach ($entry in $mapping.mappings) {
if ($file -match $entry.pattern) {
$modules += $entry.module
break
}
}
}
return @($modules | Select-Object -Unique)
}
if ($Message -notmatch '^\[([^\]]+)\]\[([^\]]+)\]:\s+(.+)$') {
Write-Error 'Commit message format must be: [module][type]: description'
exit 1
}
$mapping = Get-Content $mappingFile -Raw | ConvertFrom-Json
function Get-ChangedFiles {
$staged = (cmd /c "git diff --cached --name-only 2>nul") -split "`r?`n" | Where-Object { $_ }
if (-not $staged) { $staged = @() }
$unstaged = (cmd /c "git diff --name-only 2>nul") -split "`r?`n" | Where-Object { $_ }
if (-not $unstaged) { $unstaged = @() }
$all = @($staged) + @($unstaged) | Select-Object -Unique | Where-Object { $_ }
return $all
$messageModule = $Matches[1]
$messageType = $Matches[2]
$description = $Matches[3]
if ([string]::IsNullOrWhiteSpace($description)) {
Write-Error 'Commit message description is empty.'
exit 1
}
if ($mapping.validTypes -notcontains $messageType) {
Write-Error ("Invalid commit type '{0}'. Allowed types: {1}" -f
$messageType, ($mapping.validTypes -join ', '))
exit 1
}
function Get-ModuleForFile($file) {
foreach ($entry in $mapping.mappings) {
if ($file -match $entry.pattern) {
return $entry.module
}
}
return $null
}
$files = @(Get-CommitFiles $RepositoryRoot)
$modules = @(Get-Modules $files)
function Get-ModulesFromChangedFiles($files) {
$modules = @()
foreach ($file in $files) {
$module = Get-ModuleForFile $file
if ($module) {
$modules += $module
}
}
return $modules | Select-Object -Unique
}
function Test-CommitMessage($message, $modules) {
if ($message -notmatch '^\[([^\]]+)\]\[([^\]]+)\]:\s*(.+)$') {
return @{ IsValid = $false; Reason = '提交信息格式应为:[模块][类型]: 描述' }
}
$msgModule = $Matches[1]
$msgType = $Matches[2]
$msgDesc = $Matches[3]
if (-not $msgDesc) {
return @{ IsValid = $false; Reason = '提交信息缺少描述内容' }
}
if ($mapping.validTypes -notcontains $msgType) {
return @{ IsValid = $false; Reason = "无效的类型 '$msgType'。有效类型:$($mapping.validTypes -join ', ')" }
}
if ($modules -and $modules -notcontains $msgModule) {
Write-Warning "提交信息中的模块 [$msgModule] 与变更文件推断的模块 [$($modules -join ', ')] 不一致,请确认是否 intentional。"
}
return @{ IsValid = $true; Reason = '' }
}
$files = Get-ChangedFiles
if (-not $files) {
Write-Host "未检测到变更文件。" -ForegroundColor Yellow
exit 0
}
$modules = Get-ModulesFromChangedFiles $files
Write-Host "变更文件:"
$files | ForEach-Object { Write-Host " - $_" }
if ($modules) {
Write-Host "推断模块:$($modules -join ', ')"
Write-Output ("Repository: {0}" -f $RepositoryRoot)
if ($files.Count -eq 0) {
Write-Warning 'No staged, unstaged, or untracked files were found. Only the message was validated.'
}
else {
Write-Warning "未能从变更文件推断出模块,请检查提交信息是否手动指定了正确模块。"
Write-Output 'Commit scope files:'
foreach ($file in $files) {
Write-Output (" - {0}" -f $file)
}
}
$result = Test-CommitMessage $Message $modules
if (-not $result.IsValid) {
Write-Error $result.Reason
exit 1
if ($modules.Count -gt 0) {
Write-Output ("Detected modules: {0}" -f ($modules -join ', '))
if ($modules -notcontains $messageModule) {
Write-Warning ("Message module [{0}] does not match detected modules [{1}]. Review the commit scope." -f
$messageModule, ($modules -join ', '))
}
}
else {
Write-Warning 'No path mapping matched. Review the commit module manually.'
}
Write-Host "提交信息格式正确。" -ForegroundColor Green
Write-Output 'Commit message is valid.'
exit 0

View File

@@ -1,81 +1,38 @@
{
{
"mappings": [
{
"pattern": "rules/.*",
"module": "rules"
},
{
"pattern": "modules/frameworks/实体系统.*",
"module": "entity"
},
{
"pattern": "modules/frameworks/UI窗口系统.*",
"module": "ui"
},
{
"pattern": "modules/frameworks/事件系统.*",
"module": "event"
},
{
"pattern": "modules/frameworks/资源管理.*|modules/frameworks/通用框架.*",
"module": "resource"
},
{
"pattern": "modules/frameworks/内存对象管理.*",
"module": "pool"
},
{
"pattern": "modules/frameworks/相机系统.*",
"module": "camera"
},
{
"pattern": "modules/frameworks/场景系统.*",
"module": "scene"
},
{
"pattern": "modules/frameworks/网络系统.*",
"module": "network"
},
{
"pattern": "modules/frameworks/数据配置框架.*",
"module": "config"
},
{
"pattern": "modules/frameworks/技能系统.*",
"module": "skill"
},
{
"pattern": "modules/frameworks/Hotfix启动.*",
"module": "hotfix"
},
{
"pattern": "modules/frameworks/编辑器工具.*|modules/frameworks/第三方插件.*",
"module": "editor"
},
{
"pattern": "hooks/.*",
"module": "hooks"
},
{
"pattern": "ai/.*|AGENTS\\.md|CLAUDE\\.md|RULES\\.md",
"module": "ai"
},
{
"pattern": "specs/.*|plans/.*",
"module": "spec"
},
{
"pattern": "modules/frameworks/编辑器工具/UI工具/.*",
"module": "tools"
},
{
"pattern": "business-reference/.*",
"module": "business-reference"
},
{
"pattern": "README\\.md",
"module": "docs"
}
{ "pattern": "^rules/", "module": "rules" },
{ "pattern": "^ai/|^(AGENTS|CLAUDE|RULES)\\.md$", "module": "ai" },
{ "pattern": "^hooks/", "module": "hooks" },
{ "pattern": "^Tools/CodexUnityMcp/|^modules/frameworks/编辑器工具/MCP工具/", "module": "mcp" },
{ "pattern": "^modules/frameworks/UI窗口系统/|^modules/frameworks/编辑器工具/UI拼接工具\\.md$", "module": "ui" },
{ "pattern": "^modules/frameworks/资源管理/", "module": "resource" },
{ "pattern": "^modules/frameworks/实体系统/", "module": "entity" },
{ "pattern": "^modules/frameworks/事件系统/", "module": "event" },
{ "pattern": "^modules/frameworks/内存对象管理/", "module": "pool" },
{ "pattern": "^modules/frameworks/相机系统/", "module": "camera" },
{ "pattern": "^modules/frameworks/场景系统", "module": "scene" },
{ "pattern": "^modules/frameworks/网络系统", "module": "network" },
{ "pattern": "^modules/frameworks/数据配置框架", "module": "config" },
{ "pattern": "^modules/frameworks/技能系统/", "module": "skill" },
{ "pattern": "^modules/frameworks/Hotfix启动", "module": "hotfix" },
{ "pattern": "^modules/frameworks/编辑器工具/", "module": "editor" },
{ "pattern": "^modules/frameworks/", "module": "framework" },
{ "pattern": "^modules/", "module": "docs" },
{ "pattern": "^business-reference/", "module": "business-reference" },
{ "pattern": "^README\\.md$", "module": "docs" },
{ "pattern": "^Assets/StrayFog/Editor/CopyToX/CopyToHF/(AssetBundle|SpriteAtlas)/", "module": "resource" },
{ "pattern": "^Assets/StrayFog/Core/SFSubPackageV2\\.cs$|^Assets/StrayFog/Editor/Tools/AssestBundle/", "module": "resource" },
{ "pattern": "^Assets/StrayFog/Editor/CopyToX/CopyToHF/UIWindowMgr/", "module": "ui" },
{ "pattern": "^Assets/StrayFog/Editor/", "module": "editor" },
{ "pattern": "^Assets/StrayFog/Core/", "module": "framework" },
{ "pattern": "^Assets/Editor/Command/", "module": "mcp" },
{ "pattern": "^Assets/Game/GameHFScripte/GameFunction/HFDownLoadSubPackage/|^Assets/Game/GameMono/Scripts/GameDllManager\\.cs$", "module": "resource" },
{ "pattern": "^Assets/Game/GameHFScripte/GameFunction/UIWindows/|^Assets/Game/GameMono/Scripts/UIMono/", "module": "ui" },
{ "pattern": "^Assets/Game/GameHFScripte/", "module": "gamehf" },
{ "pattern": "^Assets/Game/GameGeneralScripte/", "module": "general" },
{ "pattern": "^Assets/Game/GameMono/", "module": "mono" },
{ "pattern": "^Assets/Game/GameHFResource/", "module": "asset" }
],
"validTypes": [
"feat",

View File

@@ -1,22 +0,0 @@
# hooks/sync-doc-associations.ps1
# Wrapper for sync_doc_associations.py
# This script appends change reminders to associated .md files.
# It only appends to standard sections; it never overwrites existing content.
$ErrorActionPreference = "Stop"
$scriptPath = $MyInvocation.MyCommand.Path
if (-not [System.IO.Path]::IsPathRooted($scriptPath)) {
$scriptPath = Join-Path (Get-Location) $scriptPath
}
$scriptPath = [System.IO.Path]::GetFullPath($scriptPath)
$scriptDir = Split-Path -Parent $scriptPath
$pythonScript = Join-Path $scriptDir "sync_doc_associations.py"
if (-not (Test-Path $pythonScript)) {
Write-Error "Python script not found: $pythonScript"
exit 1
}
python "$pythonScript"
exit $LASTEXITCODE

View File

@@ -1,218 +0,0 @@
#!/usr/bin/env python3
# hooks/sync_doc_associations.py
#
# 当 md 文件变更时,自动在关联文档的标准位置追加变更提醒:
# - 有 ## 变更记录 表格的模块文档:追加一行变更记录
# - 有 ## 下一步 清单的 progress/tasks 文档:追加一个待检查项
# - 其他文档:追加 ## 最近关联变更 章节
#
# 本脚本只追加、不覆盖正文;执行后请通过 git diff 人工确认。
import os
import re
import subprocess
import sys
from datetime import datetime
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
EXCLUDED_PREFIXES = (
'.claude/', '.codex/', '.git/',
'Library/', 'Temp/', 'Logs/', 'UserSettings/',
'obj/', 'bin/', 'HybridCLRData/',
'Assets/ThirdPlugins/',
'Assets/.WX-WASM-SDK-V2/',
'Packages/',
)
def run_git(args):
result = subprocess.run(
["git"] + args,
cwd=PROJECT_ROOT,
capture_output=True,
text=True,
encoding="utf-8",
)
return result.stdout.strip(), result.returncode
def get_changed_md_files():
"""获取 tracked 改动 + untracked 新 md 文件。"""
changed, code = run_git(["diff", "--name-only"])
if code != 0 or not changed:
changed = ""
untracked, code = run_git(["ls-files", "--others", "--exclude-standard"])
if code == 0 and untracked:
changed = (changed + "\n" + untracked).strip()
if not changed:
return []
files = [line.strip() for line in changed.splitlines() if line.strip().endswith(".md")]
files = [f for f in files if not f.startswith(EXCLUDED_PREFIXES)]
return sorted(set(files))
def resolve_link(source_file: Path, link: str):
"""解析相对链接为项目相对路径。"""
if re.match(r"^[a-zA-Z][a-zA-Z0-9+.-]*://", link):
return None
if link.startswith("#"):
return None
link = link.split("#")[0]
if not link:
return None
target = (source_file.parent / link).resolve()
try:
target.relative_to(PROJECT_ROOT)
except ValueError:
return None
rel = target.relative_to(PROJECT_ROOT)
return str(rel).replace("\\", "/")
def extract_associations(md_path: Path):
"""读取 md 的 ## 文档关联 章节,返回关联文件相对路径列表。"""
content = md_path.read_text(encoding="utf-8")
in_section = False
targets = []
for line in content.splitlines():
if re.match(r"^##\s+文档关联\s*$", line):
in_section = True
continue
if in_section and re.match(r"^##\s+", line):
break
if in_section:
for _, link in re.findall(r"\[([^\]]*)\]\(([^\)]+)\)", line):
resolved = resolve_link(md_path, link)
if resolved:
targets.append(resolved)
return targets
def find_section_range(lines, title_re):
"""返回 [start, end) 行号end 为下一个 ## 标题或文件末尾。"""
start = None
for i, line in enumerate(lines):
if re.match(title_re, line):
start = i
continue
if start is not None and re.match(r"^##\s+", line):
return start, i
if start is not None:
return start, len(lines)
return None, None
def append_changelog(target_path: Path, source_rel: str, date_str: str) -> bool:
"""在 ## 变更记录 表格追加一行;成功返回 True。"""
content = target_path.read_text(encoding="utf-8")
lines = content.splitlines()
start, end = find_section_range(lines, r"^##\s+变更记录\s*$")
if start is None:
return False
# 找到表格最后一行(跳过标题和分隔线)
insert_pos = end
for i in range(start + 1, end):
if lines[i].strip().startswith("|") and not re.match(r"^\|[-:\s|]+\|$", lines[i].strip()):
insert_pos = i + 1
new_line = f"| {date_str} | [{source_rel}](../../{source_rel}) 发生变更 | 待补充影响范围 | 待确认 |"
lines.insert(insert_pos, new_line)
target_path.write_text("\n".join(lines) + ("\n" if content.endswith("\n") else ""), encoding="utf-8")
return True
def append_next_step(target_path: Path, source_rel: str) -> bool:
"""在 ## 下一步 清单追加一个待检查项;成功返回 True。"""
content = target_path.read_text(encoding="utf-8")
lines = content.splitlines()
start, end = find_section_range(lines, r"^##\s+下一步\s*$")
if start is None:
return False
new_line = f"- [ ] 检查 [{source_rel}](../../{source_rel}) 的变更是否影响本文档"
# 避免重复
for line in lines[start:end]:
if source_rel in line and "变更是否影响本文档" in line:
return True # 已存在,视为成功但不写入
lines.insert(end, new_line)
target_path.write_text("\n".join(lines) + ("\n" if content.endswith("\n") else ""), encoding="utf-8")
return True
def append_recent_changes_section(target_path: Path, source_rel: str, date_str: str) -> bool:
"""在没有 变更记录/下一步 的文档末尾追加 ## 最近关联变更。"""
content = target_path.read_text(encoding="utf-8")
# 生成从 target 到 source 的相对链接
try:
link = os.path.relpath(PROJECT_ROOT / source_rel, target_path.parent).replace("\\", "/")
except ValueError:
link = source_rel
if "## 最近关联变更" in content:
lines = content.splitlines()
start, end = find_section_range(lines, r"^##\s+最近关联变更\s*$")
if start is not None:
new_line = f"- {date_str}:关联文档 [{source_rel}]({link}) 发生变更,需同步检查。"
lines.insert(end, new_line)
target_path.write_text("\n".join(lines) + ("\n" if content.endswith("\n") else ""), encoding="utf-8")
return True
section = (
"\n---\n\n"
"## 最近关联变更\n\n"
f"- {date_str}:关联文档 [{source_rel}]({link}) 发生变更,需同步检查。\n"
)
target_path.write_text(content.rstrip() + section, encoding="utf-8")
return True
def sync_associations():
changed_files = get_changed_md_files()
if not changed_files:
print("[sync-doc-assoc] No .md changes detected.")
return 0
date_str = datetime.now().strftime("%Y-%m-%d")
modified = []
for source_rel in changed_files:
source_path = PROJECT_ROOT / source_rel
if not source_path.exists():
continue
targets = extract_associations(source_path)
for target_rel in targets:
target_path = PROJECT_ROOT / target_rel
if not target_path.is_file():
# Skip directories, globs, or missing targets
continue
if target_rel == source_rel:
continue
done = (
append_changelog(target_path, source_rel, date_str)
or append_next_step(target_path, source_rel)
or append_recent_changes_section(target_path, source_rel, date_str)
)
if done:
modified.append((source_rel, target_rel))
if not modified:
print("[sync-doc-assoc] No associated docs need updating.")
return 0
print("[sync-doc-assoc] Updated associated docs:")
for source, target in modified:
print(f" - {source} -> {target}")
print("\n[sync-doc-assoc] Please review the changes with git diff before committing.")
return 0
if __name__ == "__main__":
sys.exit(sync_associations())

311
hooks/workflow-check.ps1 Normal file
View File

@@ -0,0 +1,311 @@
param(
[string]$UnityProjectRoot = '',
[string]$DocsRoot = ''
)
$ErrorActionPreference = 'Stop'
function Get-FullPath {
param([string]$Path, [string]$BasePath)
if ([string]::IsNullOrWhiteSpace($Path)) {
return $null
}
if (-not [System.IO.Path]::IsPathRooted($Path)) {
$Path = Join-Path $BasePath $Path
}
return [System.IO.Path]::GetFullPath($Path)
}
function Test-UnityProject {
param([string]$Path)
if ([string]::IsNullOrWhiteSpace($Path)) {
return $false
}
return (Test-Path -LiteralPath (Join-Path $Path 'Assets')) -and
(Test-Path -LiteralPath (Join-Path $Path 'ProjectSettings/ProjectVersion.txt'))
}
function Resolve-UnityProject {
param([string]$ExplicitPath, [string]$DocumentationRoot)
if (-not [string]::IsNullOrWhiteSpace($ExplicitPath)) {
$resolved = Get-FullPath $ExplicitPath (Get-Location).Path
if (-not (Test-UnityProject $resolved)) {
throw "Unity project was not found: $resolved"
}
return $resolved
}
if (-not [string]::IsNullOrWhiteSpace($env:UNITY_PROJECT_ROOT)) {
$resolved = Get-FullPath $env:UNITY_PROJECT_ROOT (Get-Location).Path
if (-not (Test-UnityProject $resolved)) {
throw "UNITY_PROJECT_ROOT is not a Unity project: $resolved"
}
return $resolved
}
$current = [System.IO.Path]::GetFullPath((Get-Location).Path)
if (Test-UnityProject $current) {
return $current
}
$parent = Split-Path -Parent $DocumentationRoot
$candidates = @()
if (Test-Path -LiteralPath $parent) {
$directories = Get-ChildItem -LiteralPath $parent -Directory -ErrorAction SilentlyContinue
foreach ($directory in $directories) {
if (Test-UnityProject $directory.FullName) {
$candidates += $directory.FullName
}
}
}
if ($candidates.Count -eq 1) {
return [System.IO.Path]::GetFullPath($candidates[0])
}
if ($candidates.Count -gt 1) {
Write-Warning 'Multiple sibling Unity projects were found. Use -UnityProjectRoot to select one.'
}
return $null
}
function Get-GitRoot {
param([string]$Path)
$result = & git -C $Path rev-parse --show-toplevel 2>$null
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($result)) {
return $null
}
return [System.IO.Path]::GetFullPath(($result | Select-Object -First 1))
}
function Get-GitChanges {
param([string]$RepositoryRoot, [string]$RepositoryName)
$result = @()
if ([string]::IsNullOrWhiteSpace($RepositoryRoot)) {
return $result
}
$lines = & git -C $RepositoryRoot -c core.quotepath=false status --porcelain=v1 --untracked-files=all
if ($LASTEXITCODE -ne 0) {
throw "Could not read git status: $RepositoryRoot"
}
foreach ($line in $lines) {
if ([string]::IsNullOrWhiteSpace($line) -or $line.Length -lt 4) {
continue
}
$path = $line.Substring(3).Trim()
if ($path.Contains(' -> ')) {
$path = ($path -split ' -> ')[-1]
}
$result += [pscustomobject]@{
Repository = $RepositoryName
Root = $RepositoryRoot
Status = $line.Substring(0, 2)
Path = $path.Replace('\', '/')
}
}
return $result
}
function Show-Changes {
param([string]$Title, [object[]]$Changes)
Write-Output ""
Write-Output "[$Title]"
if ($Changes.Count -eq 0) {
Write-Output ' Clean'
return
}
foreach ($change in $Changes) {
Write-Output (" {0} {1}" -f $change.Status, $change.Path)
}
}
function Get-MarkdownLinkFailures {
param([string]$DocumentationRoot, [object[]]$Changes)
$failures = @()
$markdownChanges = $Changes | Where-Object { $_.Path -like '*.md' -and $_.Status -notmatch 'D' }
foreach ($change in $markdownChanges) {
$fullPath = Join-Path $DocumentationRoot $change.Path
if (-not (Test-Path -LiteralPath $fullPath)) {
continue
}
$content = [System.IO.File]::ReadAllText($fullPath, [System.Text.Encoding]::UTF8)
$matches = [regex]::Matches($content, '!?(?:\[[^\]]*\])\((?<path>[^\)]+)\)')
foreach ($match in $matches) {
$link = $match.Groups['path'].Value.Trim()
if ([string]::IsNullOrWhiteSpace($link) -or $link.StartsWith('#') -or
$link -match '^[a-zA-Z][a-zA-Z0-9+.-]*://' -or $link -match '^mailto:') {
continue
}
if ($link.StartsWith('<') -and $link.EndsWith('>')) {
$link = $link.Substring(1, $link.Length - 2)
}
$pathOnly = ($link -split '#')[0]
if ([string]::IsNullOrWhiteSpace($pathOnly)) {
continue
}
$decoded = [System.Uri]::UnescapeDataString($pathOnly)
$target = Get-FullPath $decoded (Split-Path -Parent $fullPath)
if (-not (Test-Path -LiteralPath $target)) {
$failures += [pscustomobject]@{
Source = $change.Path
Link = $link
}
}
}
}
return $failures
}
$scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
if ([string]::IsNullOrWhiteSpace($DocsRoot)) {
$DocsRoot = Split-Path -Parent $scriptRoot
}
$DocsRoot = Get-FullPath $DocsRoot (Get-Location).Path
$docsGitRoot = Get-GitRoot $DocsRoot
if ([string]::IsNullOrWhiteSpace($docsGitRoot)) {
throw "Documentation git repository was not found: $DocsRoot"
}
$DocsRoot = $docsGitRoot
$resolvedUnityRoot = Resolve-UnityProject $UnityProjectRoot $DocsRoot
$unityGitRoot = $null
if (-not [string]::IsNullOrWhiteSpace($resolvedUnityRoot)) {
$unityGitRoot = Get-GitRoot $resolvedUnityRoot
if ([string]::IsNullOrWhiteSpace($unityGitRoot)) {
throw "Unity project is not a git repository: $resolvedUnityRoot"
}
}
$docsChanges = @(Get-GitChanges $DocsRoot 'UnityAI')
$unityChanges = @()
if (-not [string]::IsNullOrWhiteSpace($unityGitRoot)) {
$unityChanges = @(Get-GitChanges $unityGitRoot (Split-Path -Leaf $resolvedUnityRoot))
}
Write-Output '[workflow-check] Read-only StrayFog workflow check'
Write-Output ("Documentation repository: {0}" -f $DocsRoot)
if ($resolvedUnityRoot) {
Write-Output ("Unity project: {0}" -f $resolvedUnityRoot)
$versionFile = Join-Path $resolvedUnityRoot 'ProjectSettings/ProjectVersion.txt'
$versionLine = Get-Content -LiteralPath $versionFile -Encoding UTF8 |
Where-Object { $_ -match '^m_EditorVersion:' } | Select-Object -First 1
if ($versionLine) {
Write-Output ("Unity version: {0}" -f (($versionLine -split ':', 2)[1].Trim()))
}
}
else {
Write-Warning 'Unity project was not found. Running documentation-only checks.'
}
Show-Changes 'UnityAI changes' $docsChanges
Show-Changes 'Unity project changes' $unityChanges
$unityPaths = @($unityChanges | ForEach-Object { $_.Path })
$copyRelationships = @(
[pscustomobject]@{
Name = 'CopyToHF'
SourcePattern = '^Assets/StrayFog/Editor/CopyToX/CopyToHF/'
TargetPattern = '^Assets/Game/GameHFScripte/CopyToHF/'
GeneratedName = 'HF'
},
[pscustomobject]@{
Name = 'CopyToGeneral'
SourcePattern = '^Assets/StrayFog/Editor/CopyToX/CopyToGeneral/'
TargetPattern = '^Assets/Game/GameGeneralScripte/CopyToGeneral/'
GeneratedName = 'General'
}
)
Write-Output ''
Write-Output '[Source and generation boundary]'
$relationshipDetected = $false
foreach ($relationship in $copyRelationships) {
$hasSource = @($unityPaths | Where-Object {
$_ -match $relationship.SourcePattern
}).Count -gt 0
$hasTarget = @($unityPaths | Where-Object {
$_ -match $relationship.TargetPattern
}).Count -gt 0
if (-not $hasSource -and -not $hasTarget) {
continue
}
$relationshipDetected = $true
if ($hasSource -and -not $hasTarget) {
Write-Warning ("{0} source changed but generated target did not change. Run CopyToX and inspect the generated diff." -f
$relationship.Name)
}
elseif ($hasTarget -and -not $hasSource) {
Write-Warning ("Generated {0} files changed without {1} source changes. Confirm that generated files were not edited directly." -f
$relationship.GeneratedName, $relationship.Name)
}
else {
Write-Output (" Source and generated target changes were both detected for {0}." -f
$relationship.Name)
}
}
if (-not $relationshipDetected) {
Write-Output ' No CopyToX source/target relationship was detected.'
}
$mappingPath = Join-Path $scriptRoot 'workflow-path-mappings.json'
$mapping = Get-Content -LiteralPath $mappingPath -Raw -Encoding UTF8 | ConvertFrom-Json
$documents = @{}
$verifications = @{}
foreach ($path in $unityPaths) {
foreach ($entry in $mapping.mappings) {
if ($path -match $entry.pattern) {
$documents[$entry.document] = $true
$verifications[$entry.verification] = $true
}
}
}
Write-Output ''
Write-Output '[Suggested verification]'
if ($verifications.Count -eq 0) {
Write-Output ' No Unity verification suggestions for the current diff.'
}
else {
foreach ($item in $verifications.Keys | Sort-Object) {
Write-Output (" - {0}" -f $item)
}
}
Write-Output ''
Write-Output '[Documentation impact]'
if ($documents.Count -eq 0) {
Write-Output ' No module document mapping matched the Unity diff.'
}
else {
foreach ($document in $documents.Keys | Sort-Object) {
$changed = @($docsChanges | Where-Object { $_.Path -eq $document }).Count -gt 0
$marker = if ($changed) { 'updated' } else { 'review if long-term behavior changed' }
Write-Output (" - {0} ({1})" -f $document, $marker)
}
}
$linkFailures = @(Get-MarkdownLinkFailures $DocsRoot $docsChanges)
Write-Output ''
Write-Output '[Markdown links]'
if ($linkFailures.Count -eq 0) {
Write-Output ' Changed Markdown links are valid.'
}
else {
foreach ($failure in $linkFailures) {
Write-Error ("Broken link in {0}: {1}" -f $failure.Source, $failure.Link)
}
exit 1
}
Write-Output ''
Write-Output '[workflow-check] Completed without writing files.'
exit 0

View File

@@ -0,0 +1,59 @@
{
"mappings": [
{
"pattern": "^Assets/StrayFog/Editor/CopyToX/CopyToHF/(AssetBundle|SpriteAtlas)/",
"document": "modules/frameworks/资源管理/README.md",
"verification": "执行 CopyToX检查 HF 生成差异,构建 GameHF并验证 Unity Console。"
},
{
"pattern": "^Assets/StrayFog/Core/SFSubPackageV2\\.cs$|^Assets/StrayFog/Editor/Tools/AssestBundle/",
"document": "modules/frameworks/资源管理/README.md",
"verification": "构建 StrayFogCore 或 SFEditor并验证 V2 清单、MD5、状态和目标平台下载。"
},
{
"pattern": "^Assets/Game/GameHFScripte/GameFunction/HFDownLoadSubPackage/|^Assets/Game/GameMono/Scripts/GameDllManager\\.cs$",
"document": "modules/frameworks/资源管理/使用指南.md",
"verification": "构建 GameHF 或 SFMono验证 Bootstrap 门禁、差异回调和普通资源更新。"
},
{
"pattern": "^Assets/StrayFog/Editor/CopyToX/CopyToHF/UIWindowMgr/|^Assets/Game/GameHFScripte/GameFunction/UIWindows/|^Assets/Game/GameMono/Scripts/UIMono/",
"document": "modules/frameworks/UI窗口系统/README.md",
"verification": "构建 GameHF/SFMono刷新 Unity检查 Prefab 层级、列表回收和实际显示。"
},
{
"pattern": "^Assets/Editor/Command/|^Tools/CodexUnityMcp/",
"document": "modules/frameworks/编辑器工具/MCP工具/README.md",
"verification": "检查 Unity Editor 编译、unity_health、目标命令和 Console 日志。"
},
{
"pattern": "^Assets/Game/GameHFResource/.+\\.prefab$",
"document": "modules/frameworks/UI窗口系统/界面拼接.md",
"verification": "刷新 Unity检查 RectTransform、组件状态、资源引用和运行时视觉结果。"
},
{
"pattern": "^Assets/StrayFog/Core/",
"document": "modules/frameworks/通用框架/README.md",
"verification": "构建 StrayFogCore并检查 Unity Console。"
},
{
"pattern": "^Assets/StrayFog/Editor/",
"document": "modules/frameworks/编辑器工具/README.md",
"verification": "构建 SFEditor 或触发 Unity 编译,并验证对应菜单或生成器。"
},
{
"pattern": "^Assets/Game/GameGeneralScripte/",
"document": "modules/frameworks/通用框架/README.md",
"verification": "构建 GameGeneral生成目录变化时确认 CopyToGeneral 源码同步。"
},
{
"pattern": "^Assets/Game/GameHFScripte/",
"document": "modules/模块索引.md",
"verification": "构建 GameHF并检查 Unity Console。"
},
{
"pattern": "^Assets/Game/GameMono/",
"document": "modules/模块索引.md",
"verification": "构建 SFMono并检查 Unity Console。"
}
]
}

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 必读模块索引 |

View File

@@ -1,119 +1,57 @@
# 项目概览
> 本文档是 UnityAI / StrayFog 框架知识库的长期规则,为 Codex (AGENTS.md) 和 Claude Code (CLAUDE.md) 共同引用。
## 定位
## 摘要
`UnityAI` 是 StrayFog 框架的通用知识库和工作流标准,服务 AI 工具、业务开发人员与框架维护人员。本仓库不保存完整游戏业务代码,具体结论必须结合接入的 Unity 项目验证。
`UnityAI`**StrayFog 框架文档中心**,用于沉淀 StrayFog 框架的架构设计、核心功能使用规范、开发流程和项目管理规则。
本仓库本身不包含游戏业务代码,而是一个**面向 AI 工具与人工开发者的知识库**。AI 工具通过 ai/rules/modules/ 下的规则与模块文档快速理解框架;人工开发者可以通过 `modules/frameworks/` 下的框架模块文档和 `rules/` 下的规则文档学习使用。
- **项目名**UnityAI
- **副标题**StrayFog 框架文档中心
- **目标读者**AI 工具、业务开发人员、框架维护人员
- **通用标准仓库**`UnityAI`
- **当前主要验证对象**`CrystalBattle_Client`
- **文档语言**:中文
- **适用项目**GiftGameX、OnlineGame 等使用 StrayFog 框架的项目
- **适用范围**:使用 StrayFog 框架的 Unity 项目
---
CrystalBattle_Client 用于验证目录、接口、生成流程和平台行为,但项目专有实现不应被写成所有项目都必须遵守的框架规则。
## 重要目录
## 目录
| 目录 | 用途 |
|------|------|
| `modules/frameworks/` | 核心框架模块文档实体、UI、事件、资源、对象池等。 |
| `rules/` | 项目长期规则:工作方式、扫描范围、任务流程、归档规则 |
| `modules/` | 项目知识总入口:框架模块、核心功能、编辑器工具。 |
| `hooks/` | 共享 hook归档检查、提交检查、文档关联同步。 |
| `specs/` | 大需求 / 新模块的 OpenSpec 归档。 |
| `modules/frameworks/编辑器工具/UI工具/` | UI 预制体 JSON 转换工具脚本。 |
| `business-reference/` | 外部实际项目业务参考,与框架核心分离。 |
|---|---|
| `ai/` | AI 工具统一接入入口 |
| `rules/` | 架构、开发、工作流、文档同步和扫描规则 |
| `modules/frameworks/` | StrayFog 模块架构与使用文档 |
| `business-reference/` | 外部项目业务案例,只作参考 |
| `hooks/` | 只读工作流检查和提交检查 |
| `Tools/` | MCP Server 等可执行工具 |
---
## 核心子系统
StrayFog 框架主要包含以下子系统:
| 子系统 | 主要职责 | 对应文档 |
|--------|---------|---------|
| **模拟行为系统** | 解耦 MonoBehaviour模拟完整生命周期 | [SimulateBehaviour](../modules/frameworks/通用框架/README.md) |
| **对象池系统** | GameObject 对象池、脚本对象池、类实例池 | [对象池](../modules/frameworks/内存对象管理/README.md) |
| **事件系统** | CTC/STC 事件派发、监听与释放 | [事件系统](../modules/frameworks/事件系统/README.md) |
| **资源管理系统** | AssetBundle 加载、依赖管理、语言包、图集 | [资源管理](../modules/frameworks/资源管理/README.md) |
| **相机管理系统** | 多相机管理、相机堆栈、渲染顺序 | [相机系统](../modules/frameworks/相机系统/README.md) |
| **实体系统** | 实体创建、System-Model 架构、实体池 | [实体系统](../modules/frameworks/实体系统/README.md) |
| **UI 窗口系统** | UI 窗口管理、层级、生命周期、事件释放 | [UI 窗口系统](../modules/frameworks/UI窗口系统/README.md) |
| **技能系统** | 技能触发器、释放器、配置联动 | [技能系统](../modules/frameworks/技能系统/README.md) |
| **场景系统** | 场景加载、资源配置、生命周期 | [场景系统](../modules/frameworks/场景系统.md) |
| **网络系统** | WebSocket、protobuf、消息映射如项目使用 | [网络系统](../modules/frameworks/网络系统.md) |
| **配置生成系统** | XLSX / protobuf 配置生成(如项目使用) | [配置生成](../modules/frameworks/数据配置框架.md) |
---
## 文档分层
```text
AI 接入层
AGENTS.md / CLAUDE.md / RULES.md / ai/AI接入入口.md
|
v
项目规则层
rules/项目规则入口.md + 00~70 规则文件
|
v
项目知识层
modules/模块索引.md
|
├── 框架模块frameworks/
├── 编辑器工具editor-tools/
└── 使用示例examples/
|
v
业务参考层
business-reference/playballoons/
|
v
Hook / Spec / Plan
hooks/ / specs/ / plans/
```
---
本仓库不建立独立的需求方案、阶段进度或任务清单目录。重大变更在对话中确认,稳定结论直接进入模块文档。
## 使用方式
### AI 工具
1. 读取 `ai/AI接入入口.md`
2. 读取 `rules/项目规则入口.md`
3. 按任务范围读取 `modules/` 下具体模块文档
4. 如需了解外部项目业务参考,读取 `business-reference/playballoons/业务框架映射接口.md`
1. 读取 [AI 接入入口](../ai/AI接入入口.md)。
2. 读取 [项目规则入口](项目规则入口.md) 和 [工作流清单](40-工作流清单.md)。
3. 从 [模块索引](../modules/模块索引.md) 读取任务相关模块。
4. 读取目标 Unity 项目的真实代码、Prefab、配置和日志后再得出结论。
### 人工开发者
1. 新成员从 [开发规则](20-开发规则.md) 开始
2. 了解架构看 [架构说明](10-架构说明.md) 和 [通用框架/框架架构](../modules/frameworks/通用框架/框架架构.md)
3. 使用具体系统看 `modules/frameworks/` 下对应框架模块
4. 遇到问题看 [开发规则/常见问题](20-开发规则.md#常见问题)。
1. 从 [架构说明](10-架构说明.md) 了解源码和生成代码边界
2. 从 [开发规则](20-开发规则.md) 了解编码、资源和生成约束
3. 从具体模块 README 和使用指南查找接口与数据流
---
## 文档原则
## 关键约束
- `ai/rules/modules/` 是 StrayFog 框架知识的唯一事实来源;旧中文教程内容已迁移合并至此
- 修改 `modules/` 中模块文档后,必须同步更新 `modules/模块索引.md` 和相关规则文件
- `business-reference/` 中的内容来自外部实际项目,仅作参考,不作为 StrayFog 框架规范
- 不要把项目规则复制进系统提示或 AI 工具私有目录。
---
- `rules/` 保存跨模块标准。
- `modules/` 保存模块长期知识。
- 一个事实只在一处完整定义,其他文档通过链接引用
- `business-reference/` 不能反向定义框架规范
- 当前工程的临时排错过程不进入长期文档
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [rules/项目规则入口.md](项目规则入口.md) | 项目概览是项目规则入口引用的核心信息 |
| [rules/10-架构说明.md](10-架构说明.md) | 重要顶层目录与总体分层、程序集边界相关 |
| [rules/20-开发规则.md](20-开发规则.md) | 生成内容约束与开发规则互相引用 |
| [rules/70-项目设置.md](70-项目设置.md) | 扫描范围、跳过目录与项目概况中的目录说明相关 |
| [modules/模块索引.md](../modules/模块索引.md) | 模块索引是项目知识总入口 |
| [项目规则入口](项目规则入口.md) | 项目规则入口 |
| [架构说明](10-架构说明.md) | 代码分层和项目边界 |
| [模块索引](../modules/模块索引.md) | 框架知识总入口 |
| [项目设置](70-项目设置.md) | 扫描范围和忽略目录 |

View File

@@ -1,144 +1,67 @@
# 架构说明
> 本文档是 UnityAI / StrayFog 框架知识库的长期规则,为 Codex (AGENTS.md) 和 Claude Code (CLAUDE.md) 共同引用
项目文档按三类组织:**核心运行时**、**编辑器工具**、**业务参考**。详细模块索引见 [模块索引](../modules/模块索引.md)。
---
## 分类口径
- **核心运行时**支撑游戏运行的内部系统例如实体、UI、网络、资源、事件、场景、对象池。
- **编辑器工具**Unity Editor 内使用的生成器、Inspector、菜单、配置工具。
- **业务参考**:外部实际项目的业务实现示例,仅作参考,不作为框架规范。
---
本文档定义 StrayFog 项目的代码分层、真实源码位置与生成边界。具体项目路径不同的时候,以项目配置和生成器实现为准
## 总体分层
```text
┌─────────────────────────────────────────────────────────────┐
StrayFog Framework │
├─────────────────────────────────────────────────────────────┤
│ ┌──────────────────┐ ┌──────────────────┐ ┌───────────┐ │
GameGeneral GameHF │ │ Core │ │
通用工具层 业务核心层 │ │ 基础框架 │ │
│ └────────┬─────────┘ └────────┬─────────┘ └─────┬─────┘ │
├───────────┼─────────────────────┼──────────────────┼─────────┤
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Unity Engine │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
游戏业务GameFunction / GameMono / GameHFResource
|
v
生成后的运行时框架CopyToHF / GameGeneral
|
v
StrayFog Core / Unity Engine / 第三方插件
StrayFog Editor / CopyToX --------------> 生成运行时代码与资源
```
### 各层职责
## 源码与生成结果
| 层级 | 路径 | 职责描述 |
|------|------|---------|
| **Core 层** | `Assets/StrayFog/Core` | 核心基础框架,模拟行为系统、全局数据管理 |
| **HF 层** | `Assets/Game/GameHFScripte/CopyToHF` | 游戏核心业务框架资源管理、事件系统、相机管理、UI 管理等 |
| **General 层** | `Assets/Game/GameGeneralScripte` | 通用工具层,状态机、辅助工具、扩展方法等 |
---
## 核心运行时层
核心运行时按技术职责命名,可以对应内部目录。
- **Hotfix 启动/目录**`GameFunction/GameRoot/`
- **实体系统**`GameFunction/GameEntity/``Logic/EntityData/`
- **UI 系统**`CopyToHF/UIWindowMgr/``GameFunction/UIWindows/``GameMono/Scripts/UIMono/`
- **场景系统**`GameFunction/SceneManager/``GameMono/Scenes/`
- **网络系统**`CopyToHF/Network/``GameFunction/WebSocket/``GoogleProtobufNetwork/`(如项目使用)
- **资源系统**`CopyToHF/AssetBundle/``CopyToHF/SpriteAtlas/``GameHFResource/`
- **事件系统**`CopyToHF/Event/``GameFunction/Event/`
- **对象池**`CopyToHF/MemoryManager/``GameFunction/GameEntity/EntityPool/`
- **通用框架**`GameGeneralScripte/``StrayFog/Core/`
---
## 编辑器工具层
- `Assets/StrayFog/Editor/`StrayFog 编辑器扩展。
- `Assets/Editor/`:项目编辑器资源与脚本。
- `Assets/AutoBuildScript/Editor/`:编辑器生成工具(如项目使用)。
---
## 程序集边界
已识别 asmdef
| 程序集 | 主要路径 | 分类 |
| 路径 | 类型 | 修改规则 |
|---|---|---|
| `StrayFogCore` | `Assets/StrayFog/Core/` | 核心运行时 |
| `SFEditor` | `Assets/StrayFog/Editor/` | 编辑器工具 |
| `GameHF` | `Assets/Game/GameHFScripte/` | 核心运行时 / hotfix 承载 |
| `GameGeneral` | `Assets/Game/GameGeneralScripte/` | 核心运行时 / general |
| `SFMono` | `Assets/Game/GameMono/` | 核心运行时 / Mono 桥接 |
| `Assets/StrayFog/Core` | 直接维护的框架运行时源码 | 按框架需求直接修改并构建 `StrayFogCore` |
| `Assets/StrayFog/Editor` | Editor 工具与生成器源码 | 直接修改;运行时代码不得反向依赖 |
| `Assets/StrayFog/Editor/CopyToX/CopyToHF` | `ESF_` 热更框架源码 | 框架修改的主要源头,修改后执行 CopyToX |
| `Assets/StrayFog/Editor/CopyToX/CopyToGeneral` | General 层生成源码 | 修改后执行 CopyToX |
| `Assets/Game/GameHFScripte/CopyToHF` | CopyToX 生成的 `HF_` 运行时代码 | 禁止手改,只检查生成差异 |
| `Assets/Game/GameGeneralScripte/CopyToGeneral` | CopyToX 生成的 General 代码 | 禁止手改,只检查生成差异 |
| `Assets/Game/GameHFScripte/GameFunction` | 项目热更业务代码 | 业务需求直接修改 |
| `Assets/Game/GameMono` | 项目 Mono 桥接和组件代码 | 直接修改并构建 `SFMono` |
| `Assets/Game/GameHFResource` | Prefab、图集、配置和 AssetBundle 资源 | 通过 Unity 或已有工具修改并验证序列化结果 |
| `Assets/Editor/Command` | 项目 Editor/MCP 工具 | 直接修改,在 Unity 主线程验证 Editor API |
---
CopyToX 会删除并重新生成目标子目录,同时把 `ESF_` 标记转换为 `HF_`。因此目标目录中的手工改动会丢失,也不能作为下一次生成的源码。
## 程序集
| 程序集 | 主要职责 |
|---|---|
| `StrayFogCore` | StrayFog 基础运行时 |
| `SFEditor` | StrayFog Editor 工具 |
| `GameGeneral` | 生成后的通用运行时层 |
| `GameHF` | 热更框架与业务代码 |
| `SFMono` | MonoBehaviour、UIMono 和桥接层 |
项目接入第三方插件时,以 asmdef 依赖为准,不把第三方源码复制为框架规则。
## 依赖方向
```text
业务功能PVE、PVP、背包、商店等放在外部业务参考层
|
v
核心运行时GameRoot、GameEntity、UIWindowMgr、SceneManager、Network、AssetBundle、Event 等)
|
v
通用框架 / 第三方插件StrayFog Core、Spine、DOTween 等)
|
v
Unity Engine / .NET Runtime
- 业务代码可以依赖公开框架入口和通用层。
- 业务调用优先通过 `HFFunCatalogue` 等项目统一入口,不绕过封装直接依赖内部单例。
- Core 和生成后的框架运行时不能依赖具体业务模块。
- 运行时代码不能依赖 Editor 程序集。
- Editor 工具可以读取或生成运行时代码和资源,但生成结果不能成为生成器的反向依赖。
编辑器工具 -----------------> 读取/生成 核心运行时代码
```
## 当前验证基线
### 依赖规则
| 上层 | 可以依赖 | 禁止依赖 |
|---|---|---|
| 业务功能 | 核心运行时、通用框架 | 编辑器工具、第三方插件源码 |
| 核心运行时 | 通用框架、第三方插件 | 具体业务功能、编辑器工具 |
| 编辑器工具 | 核心运行时源码、生成模板 | 被运行时代码反向引用 |
### 说明
- 运行时代码不要依赖 Editor 程序集。
- 第三方插件不应反向依赖具体玩法模块。
- 业务功能代码按业务命名,代码目录可以仍然按框架/实现组织。
---
## 文档与代码分层对应
| 文档层 | 对应代码层 | 示例 |
|--------|-----------|------|
| `modules/frameworks/` | 核心运行时 | 实体系统、UI 系统、事件系统等 |
| `modules/frameworks/通用框架/框架架构.md` | 核心运行时 + 规范 | StrayFog 整体架构分析 |
| `rules/` | 规范与流程 | 开发规则、工作流清单、架构说明 |
---
## 待确认
- 各项目实际使用的 StrayFog 版本与目录可能略有差异,文档中路径以当前仓库描述的 `Assets/StrayFog/``Assets/Game/` 结构为准。
- 网络系统、配置生成系统是否为所有使用 StrayFog 的项目标配,需按具体项目确认。
---
AI 每次都应读取目标项目的 `ProjectSettings/ProjectVersion.txt`,不把文档中的版本当作永久值。当前 CrystalBattle_Client 的验证基线为 Unity `2022.3.29f1`
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/frameworks/通用框架/框架架构.md](../modules/frameworks/通用框架/框架架构.md) | 实现视角的框架架构分析 |
| [modules/模块索引.md](../modules/模块索引.md) | 模块索引是架构说明的落地入口,分类口径需一致 |
| [rules/30-模块登记规则.md](30-模块登记规则.md) | 模块登记口径、状态定义与架构分层互相影响 |
| [rules/20-开发规则.md](20-开发规则.md) | 程序集边界、依赖方向与开发规则互相引用 |
| [rules/项目规则入口.md](项目规则入口.md) | 架构说明是项目规则入口索引的关键规则 |
| [开发规则](20-开发规则.md) | 源码边界对应具体修改规则 |
| [工作流清单](40-工作流清单.md) | 生成和验证步骤引用本架构 |
| [模块索引](../modules/模块索引.md) | 模块职责和入口 |
| [通用框架](../modules/frameworks/通用框架/框架架构.md) | StrayFog 实现视角的架构说明 |

View File

@@ -1,269 +1,87 @@
# 开发规则
> 本文档是 UnityAI / StrayFog 框架知识库的长期规则,为 Codex (AGENTS.md) 和 Claude Code (CLAUDE.md) 共同引用。
## 基本原则
- 优先遵循项目已有模式。
- 修改范围保持在当前功能或模块内
- 不手动修改生成代码,除非生成器不可用且已记录原因
-直接修改 `Assets/ThirdPlugins/` 或 embedded package除非必须且记录原因
- 正常分析项目时忽略 Unity/IDE 本地状态目录
- 运行时代码不引用 Editor 程序集。
- **一处定义,多处引用**:如果一个事实出现在多个文档中,修改时必须同步所有地方。
---
- 先读取附近代码和模块文档,再决定实现方式。
- 修改范围保持聚焦,优先复用项目已有模式和公开入口
- 真实源码、生成代码、业务代码和资源必须按 [架构说明](10-架构说明.md) 区分
-回退任务开始前已有的修改,不顺带格式化或重序列化无关文件
- 一个长期事实只在一处完整定义,其他文档使用链接
## C# 规则
### 命名
### 命名与结构
-名、结构体、枚举、属性方法使用 PascalCase。
- 私有字段使用 `m_` + PascalCase,例如 `m_UserName``m_BagPage`
- 方法参数使用 `_` + camelCase,例如 `_username``_maxpage`
- 局部变量使用 camelCase
- 常量:同一文件内保持一致,优先使用全大写 + 下划线(如 `C_FRAME_ROOT_PATH`)或 PascalCase
- 抽象类优先加 `Abs` 前缀,例如 `AbsHFSingleMonoBehaviour<T>``AbsHFAssetRequest`
- 单例管理器优先继承 `AbsHFSingleMonoBehaviour<T>``AbsHFSingleRunTime<T>`
- Hotfix 类名可优先加 `HF` 前缀,例如 `HFLoginManager``HFBagManager`
- 业务类型枚举可按用途加前缀,例如 `enItemType``enCurrencySubItemType`
- 事件枚举可按用途加前缀,例如 `enHFSTCEvent``enHFCTCEvent`
-、属性方法使用 PascalCase。
- 私有字段沿用项目 `m_` 前缀,例如 `m_UserName`
- 方法参数沿用项目 `_` 前缀,例如 `_userName`
- 一个文件原则上只放一个主要类型partial 和小型辅助类型遵循附近实现
- public API 添加简洁中文 XML 注释,说明用途、参数和返回值
- 日志包含方法、资源路径、PackageId 或平台等关键上下文,不只输出“失败”
### 代码组织
### 语言兼容性
- 一个 `.cs` 文件原则上只放一个主要类型;小枚举可多个共存
- 类型可见性排序按项目已有风格;如无风格,按 public → protected → internal → private
- 公共 API 添加中文 XML 文档注释或行内注释,说明用途、参数、返回值
- 只在解释非显然意图时添加注释,不写无意义注释
- 先读取 Unity 版本、asmdef 和附近代码风格,不默认使用最新 C# 语法
- 优先使用项目已有的显式类型、普通属性、传统 `switch` 和清晰的回调/协程写法
- 非必要时不引入 record、init-only、范围索引、复杂模式匹配、目标类型推断等较新语法
- 新语法只有在目标 Unity/编译器已验证且确实降低复杂度时使用
- 避免超长方法、深层嵌套和为了封装而封装的辅助层。
### 日志与异常
## 框架与业务边界
- 异常只用于异常情况,不用于正常流程控制
- 错误日志包含方法名和关键上下文,不包含敏感信息
- 写日志时保持附近文件风格;优先使用项目统一的日志封装(如有)
- 业务代码通过 `HFFunCatalogue` 等统一入口访问资源、UI、场景和其他框架能力
- 业务功能直接修改 `GameFunction``GameMono` 和对应资源
- 框架维护任务可以修改 `Assets/StrayFog/Core``Assets/StrayFog/Editor` 和 CopyToX 源码,不套用“业务不得改框架”的限制
- CopyToX 生成目录禁止手改;生成结果有问题时修正 ESF/General 源码或生成器。
- 修改 ESF 后必须执行 CopyToX并检查生成后的 HF 差异。
### 其他
## Unity 与异步规则
- 优先遵循附近文件风格。同一文件/同一目录下风格应保持一致
- 保持 public API 小而明确
- 避免超长方法和深层嵌套
- Unity API 只在 Unity 主线程调用;后台线程只做不触碰 UnityEngine 对象的工作
- StreamingAssets、UnityWebRequest、CDN 和 WebGL 下载使用协程或回调
- 禁止在主线程用阻塞式 `while`、忙等或同步等待网络与文件结果
- 下载进度只在单文件完整写入并通过 MD5/完整性校验后计数。
- 平台路径、只读 StreamingAssets、PersistentData 和外部存储回调分别验证。
---
## UI 与 Prefab
## 核心库访问规则
- 修改前同时读取窗口脚本、UIMono、Prefab 层级和相关资源。
- 单列虚拟列表使用 `SetLoopListViewItemPrefabs``InitListView``RefreshDataTotalCount`
- 多列虚拟列表使用 `SetLoopGridViewItemPrefabs``InitGridView``RefreshGridDataTotalCount`
- 模板节点保持禁用,由列表运行时创建实例;不要依赖旧 LayoutGroup 或 ContentSizeFitter 驱动虚拟列表。
- Prefab 修改检查锚点、pivot、sizeDelta、组件启用状态、序列化引用和实际显示。
- 避免文本方式大范围重写 Prefab优先使用 Unity Editor、MCP 或结构化序列化工具。
### 核心库范围
详细规则见 [UI 窗口系统](../modules/frameworks/UI窗口系统/README.md)。
以下目录属于核心库,业务代码应只读访问,修改需走框架团队 Issue 流程:
## 资源与生成规则
| 路径 | 说明 |
|------|------|
| `Assets/StrayFog/Core` | 基础框架核心 |
| `Assets/Game/GameHFScripte/CopyToHF` | 游戏核心业务框架 |
| `Assets/Game/GameGeneralScripte` | 通用工具层 |
- Prefab、图集、配置、AssetBundle 和代码生成优先修改源资源或生成配置。
- SpriteAtlas 按模块组织;打图集设置以项目生成器为准。
- 网络消息以 proto/schema 为源头,生成输出只用于检查。
- 资源加载、分包、MD5、下载和卸载规则见 [资源管理](../modules/frameworks/资源管理/README.md)。
- 生成操作后检查 Git 差异,确认没有删除或覆盖用户维护的非生成文件。
### 访问方式
## 验证与 Git
业务代码必须通过 `HFFunCatalogue` 统一入口访问核心库:
- 验证范围随改动范围扩大,最低要求见 [工作流清单](40-工作流清单.md)。
- 编译通过后仍需检查 Unity ConsoleUI 和平台逻辑继续做实际运行验证。
- UnityAI 与目标 Unity 项目分别检查 Git 状态。
- 只有用户明确要求时才提交或推送,提交格式统一为 `[模块][类型]: 描述`
```csharp
// ✅ 正确:通过统一入口访问
HFFunCatalogue.UIManager.OpenWindow<LoginWindow>();
HFFunCatalogue.Asset.LoadAsset<GameObject>("path");
HFFunCatalogue.Scene.LoadScene("MainScene");
## 文档规则
// ❌ 禁止:直接访问核心库
HFUIWindowMgr.Instance.OpenWindow<LoginWindow>(); // 禁止
HFABMgr.Instance.LoadAsset<GameObject>("path"); // 禁止
```
### 禁止操作
| 禁止行为 | 后果 |
|---------|------|
| 修改核心库源代码 | 破坏框架稳定性 |
| 删除/重命名核心库文件 | 编译错误 |
| 直接实例化核心库内部类 | 违反封装原则 |
| 绕过 `HFFunCatalogue` 直接调用核心库单例 | 难以维护和测试 |
### 修改流程
1. 业务人员发现问题/需求 → 提交 Issue 到框架团队
2. 框架团队评估需求
3. 合理:开发修复 → 代码审查 → 发布新版本 → 业务团队更新引用
4. 不合理:反馈拒绝
---
## 业务代码结构
### 目录组织
业务代码主要位于 `Assets/Game/GameHFScripte/GameFunction/`,按职责分目录:
```
Assets/Game/GameHFScripte/GameFunction/
├── Event/ # 消息处理
│ ├── ServerToClient/ # 服务器到客户端消息
│ ├── ClientToClient/ # 客户端内部消息
│ └── Handle/ # 消息处理器
├── GameRoot/ # 框架调用入口
├── Logic/ # 游戏主要业务逻辑(存放各功能 Manager
├── UIWindows/ # UI 窗口业务逻辑
├── GameEntity/ # 游戏实体
├── SoundManagerPro/ # 音效管理
├── SceneManager/ # 场景管理
└── SDK/ # SDK 集成
```
### 文件命名
| 文件类型 | 命名规则 | 示例 |
|---------|---------|-----|
| 窗口类 | `XXXWindow.cs` | `LoginWindow.cs` |
| 组件类 | `XXXComponent.cs` | `ItemCellComponent.cs` |
| 管理器类 | `XXXManager.cs` | `UIManager.cs` |
| 数据类 | `XXXData.cs` | `PlayerData.cs` |
| 配置类 | `XXXConfig.cs` | `SkillConfig.cs` |
---
## 最佳实践
### 性能优化
- 频繁创建销毁的对象必须使用对象池。
- 合理设置对象池初始容量。
- 及时释放不再使用的资源。
- 使用异步加载避免卡顿。
- 合理使用资源缓存。
- 将图标打包到 SpriteAtlas。
- 滚动列表优先使用 `SFUI_ScrollRect.BindEvent`
### 代码组织
- 每个类只负责一个功能,避免庞大的 God 类。
- 通过管理器获取依赖,而非硬编码。
- 使用接口解耦依赖关系。
- 保持 public API 小而明确。
- 避免超长方法和深层嵌套。
### 错误处理
- 异常只用于异常情况,不用于正常流程控制。
- 在关键路径添加异常处理。
- 记录错误日志便于排查,但不包含敏感信息。
- 检查参数合法性,处理空引用情况。
---
## 常见问题
### 如何扩展核心库功能?
通过继承抽象类或实现接口来扩展,不要直接修改核心库代码。
### 核心库有 Bug 怎么办?
提交 Issue 到框架团队,由框架团队修复并发布新版本。
### 如何调试核心库代码?
可以设置断点调试,但不允许修改代码。
### 常用核心库 API
| 类别 | 管理器 | 常用方法 |
|-----|-------|---------|
| 对象池 | `HFObjectMemPool` | `GetItem()`, `GiveBack()` |
| 事件 | `IHFEventHandle` | `AddListener()`, `Dispatch()` |
| 资源 | `HFABMgr` | `OnStartLoad()` |
| 相机 | `HFCameraManager` | `AddCamera()`, `GetCamera()` |
---
## Unity 资源规则
### Prefab
- Prefab 修改保持聚焦,避免无意义的大范围 reserialize。
- UI Prefab 命名与窗口/面板对应,例如 `Login.prefab``ItemWindow.prefab`
- 通用 UI 组件/Item 使用 `Item` 后缀,例如 `AvatarItem.prefab``ResourceItem.prefab`
- 记录无法从代码复现的手动 Unity Editor 操作。
### Scene
- 场景按用途命名,例如 `LoginScene``BattleScene`
- 不要直接在场景里堆放运行时动态加载的资源。
### Sprite Atlas
- 按业务或 UI 模块划分图集,例如 `SpriteAtlasAssets/Shop``SpriteAtlasAssets/Login`
- 图集内图片按功能子目录组织,避免单张图集过大。
### Material / Shader
- 优先使用项目已有 Shader不随意引入新 Shader。
- Material 实例在运行时通过代码创建,避免直接修改共享 Material。
### Audio
- 音效、背景音乐分目录存放。
- 短音效优先使用 AudioClip + AudioSource 池,长音乐注意卸载。
### 本地化资源
- 资源按语言分目录,例如 `GameHFResource/CN/``GameHFResource/EN/`
- 多语言共用资源放在不带语言后缀的公共目录。
---
## 配置和数据规则
- 优先修改源表、proto 或 schema而不是生成输出。
- 数据/schema 修改后,按项目流程重新生成。
- 缺失的生成步骤记录到 [工作流清单](40-工作流清单.md) 或对应模块文档。
- 详细规则见 [数据/配置框架](../modules/frameworks/数据配置框架.md)。
---
## 网络和 Protobuf 规则
- `.proto` 是消息结构源头。
- schema 修改后重新生成 C# 输出。
- route/message-id 映射变化要和 schema 变化一起记录。
- 详细规则见 [网络框架](../modules/frameworks/网络系统.md)。
---
## UI 框架规则
UI 窗口、Prefab、层级、生命周期、事件释放和 UIMono 绑定规则见 [UI 框架](../modules/frameworks/UI窗口系统/README.md)。
---
## 文档编写规则
- 每个 `.md` 文件末尾必须包含 **文档关联** 表。
- 模块文档变更必须同步更新 [模块索引](../modules/模块索引.md)。
- 不要在多个文档中重复维护同一份事实表。
- 使用相对路径链接,确保在 GitHub / IDE / AI 工具中均可点击。
- 代码示例必须可编译(或明确标注为伪代码)。
---
- 长期文档只记录稳定架构、公共 API、生成流程和使用规则。
- 重大变更的讨论和文档落盘方式遵循 [工作流清单](40-工作流清单.md)。
- 文档使用相对链接;代码示例必须可编译或明确标注伪代码。
- 模块入口或状态变化时更新 [模块索引](../modules/模块索引.md)。
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [rules/项目规则入口.md](项目规则入口.md) | 开发规则是项目规则入口索引的关键规则 |
| [rules/10-架构说明.md](10-架构说明.md) | 程序集边界、依赖方向与 C# / Unity 规则互相引用 |
| [modules/模块索引.md](../modules/模块索引.md) | 模块索引中的框架模块文档是开发规则的具体落地 |
| [UI 框架](../modules/frameworks/UI窗口系统/README.md) | UI 命名、Prefab、层级等规则的具体说明 |
| [网络框架](../modules/frameworks/网络系统.md) | 网络、Protobuf 规则的具体说明 |
| [数据/配置框架](../modules/frameworks/数据配置框架.md) | 配置表、生成输出规则的具体说明 |
| [实体系统](../modules/frameworks/实体系统/README.md) | Entity 编码规范与核心库访问规则落地 |
| [通用框架](../modules/frameworks/通用框架/README.md) | 核心库范围与通用层规则落地 |
| [对象池与运行时对象管理](../modules/frameworks/内存对象管理/README.md) | 对象池最佳实践落地 |
| [架构说明](10-架构说明.md) | 源码与生成目录边界 |
| [工作流清单](40-工作流清单.md) | 实施与验证流程 |
| [文档同步规则](50-文档同步规则.md) | 长期文档更新条件 |
| [UI 窗口系统](../modules/frameworks/UI窗口系统/README.md) | UI 专项规范 |
| [资源管理](../modules/frameworks/资源管理/README.md) | 资源专项规范 |

View File

@@ -1,81 +1,43 @@
# 模块登记规则
> 本文档是 UnityAI / StrayFog 框架知识库的长期规则,为 Codex (AGENTS.md) 和 Claude Code (CLAUDE.md) 共同引用
模块入口、分类和状态统一维护在 [模块索引](../modules/模块索引.md),其他文档不复制模块清单
这里定义模块登记口径、状态含义和拆分规则。项目模块总入口统一维护在 [模块索引](../modules/模块索引.md),本文件不重复维护模块清单。
## 分类
---
- **核心框架**:运行时系统,例如 UI、资源、事件、实体、对象池和场景。
- **编辑器工具**Unity Editor 内的生成器、窗口、菜单和 MCP Bridge。
- **业务参考**:外部项目案例,仅用于说明框架如何被使用。
## 口径
## 状态
- **核心框架**支撑游戏运行的内部系统例如实体系统、UI 窗口系统、事件系统、资源管理、对象池、场景管理、网络、配置生成
- **编辑器工具**Unity Editor 内使用的生成器、Inspector、菜单、配置工具
- **业务参考**:外部实际项目的业务实现示例,仅作参考,不作为框架规范
- **未识别**:尚无稳定实现线索
- **已识别**:已发现路径或入口,未完成系统梳理
- **初步梳理**:职责、路径或基本数据流已记录,仍有待确认项
- **待验证**:文档已有结论,尚未通过 Unity、运行时、生成器或日志闭环。
- **已完成**:当前文档与实现已经验证,后续随代码演进维护。
- **未发现实现**:按目录和关键词检查后暂未发现实现。
---
## 文档结构
## 唯一事实来源
模块清单、模块状态和模块入口统一维护在:
```text
modules/模块索引.md
```
其他规则文档只链接到模块索引或具体模块文档,不再复制模块表。
---
## 状态定义
- **未识别**:还没有稳定线索。
- **已识别**:已发现目录、类、资源或协议线索,但未完成梳理。
- **初步梳理**:已写入核心路径、职责、数据流或使用规则,仍可能有待确认项。
- **待验证**:文档已有结论,但需要通过 Unity、运行时、生成器或日志验证。
- **已完成**:文档和验证都闭环,后续只需随代码演进维护。
- **未发现实现**:按关键词和目录扫描后暂未发现实现,可作为规划占位。
避免使用“已开始”作为状态;它不能说明到底是只建了文件,还是已经完成了一轮梳理。
---
- 轻量模块使用一个 `README.md`
- API 和业务调用较多时增加 `使用指南.md``开发指南.md`
- 特殊限制写入 `注意事项.md`,不创建计划、进度或任务文件。
- 模块发生大改时,讨论保留在对话中;实现后的稳定结论直接更新现有文档。
- 模块新增、删除、重命名或状态变化时更新模块索引。
## 登记规则
- 新增核心框架模块时,登记到 [模块索引](../modules/模块索引.md) 的“核心框架”
- 新增编辑器工具时,登记到 [模块索引](../modules/模块索引.md) 的“编辑器工具
- 新增业务参考时,登记到 `business-reference/` 下对应项目目录,不进入主模块索引
- 框架内部规则优先写进对应框架模块文档,例如 UI 写入 [UI 框架](../modules/frameworks/UI窗口系统/README.md)
- 模块开始有独立设计、进度、任务时,再拆 `modules/<module>/`
- 新增或拆分模块后,同步检查 [架构说明](10-架构说明.md) 和相关模块文档。
---
## 文档关系
- `modules/模块索引.md`:项目模块总入口和事实表。
- `modules/frameworks/*`:核心框架职责和框架使用规则。复杂模块拆分为文件夹,入口为 `README.md`
- `modules/<module>/业务参考总入口.md`:复杂模块稳定说明。
- `modules/<module>/progress.md`:复杂模块阶段进度。
- `modules/<module>/tasks.md`:复杂模块待办事项。
- `rules/`:跨模块的通用规则、工作流、扫描范围和归档规则。
- `business-reference/`:外部项目业务参考,不进入主索引。
---
## 相关文档
- [模块索引](../modules/模块索引.md):按分类整理的模块详细索引。
- [架构说明](10-架构说明.md):总体分层、程序集边界和依赖方向。
---
- 框架模块放入 `modules/frameworks/`
- 编辑器工具放入 `modules/frameworks/编辑器工具/`
- 外部业务案例放入 `business-reference/<project>/`,不作为框架规范
- 文档路径反映模块职责,不按一次性任务名称建立目录
- 一个模块的职责和入口由 README 完整定义,索引只保存摘要和链接
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../modules/模块索引.md) | 模块索引是模块登记规则的唯一事实来源 |
| [rules/10-架构说明.md](10-架构说明.md) | 分类口径、依赖方向与架构说明互相影响 |
| [rules/项目规则入口.md](项目规则入口.md) | 模块登记规则是项目规则入口索引的关键规则 |
| [modules/frameworks/*.md](../modules/frameworks/) | 框架模块的状态、拆分规则需与登记规则一致 |
| [模块索引](../modules/模块索引.md) | 模块清单和状态的唯一事实来源 |
| [架构说明](10-架构说明.md) | 模块分类与代码分层 |
| [文档同步规则](50-文档同步规则.md) | 模块变化的同步条件 |
| [模块文档模板](../modules/模块文档模板.md) | 新模块文档结构 |

View File

@@ -1,206 +1,135 @@
# 工作流清单
> 本文档是 UnityAI / StrayFog 框架知识库的长期规则,为 Codex (AGENTS.md) 和 Claude Code (CLAUDE.md) 共同引用
本文档是 UnityAI 唯一的任务执行流程。其他入口只链接本文件,不复制完整步骤
---
## 一、任务分类
## 任务分类
| 类型 | 特征 | 处理方式 |
| 类型 | 判断标准 | 处理方式 |
|---|---|---|
| **简单修复** | 查询、解释、小范围修改、影响面低、用户已给出明确方式 | 直接处理,完成后验证并归档检查 |
| **旧模块修复** | 已有框架模块的 bug 修复、逻辑补全、局部优化 | 读取模块文档,分析影响范围后修复,验证并归档 |
| **Spec 任务** | 新增框架模块、调整架构/目录/依赖、批量修改迁移 | 走 OpenSpec 流程 |
| 分析检查 | 查询、解释、定位、评审,不要求修改 | 读取真实实现和证据,输出结论与风险 |
| 局部修复 | 目标明确、影响集中、公开行为基本不变 | 检查上下文后直接修改并验证 |
| 重大变更 | 公共接口、框架架构、生成流程、跨模块数据、批量资源或平台流程变化 | 先在对话中确认目标、边界、方案和验收条件,再实施 |
---
重大变更不创建 Spec、Plan、progress 或 tasks 文件。需要长期保存的内容,在实现确认后写入对应模块 README、架构说明或使用指南。
## 简单修复流程
## 二、标准执行流程
1. 确认任务范围。
2. 读取相关模块文档。
3. 直接修改。
4. 验证(如可运行测试、检查链接、预览 Markdown
5. 执行归档检查。
6. 简短说明结果。
### 1. 准备与定位
---
1. 读取 [项目规则入口](项目规则入口.md) 和相关模块文档。
2. 检查 UnityAI 与目标 Unity 项目的分支、工作树和未跟踪文件。
3. 读取 `ProjectSettings/ProjectVersion.txt`,确认 Unity 版本。
4. 搜索调用入口、数据流、Prefab、配置、生成器和测试方式。
5. 按 [架构说明](10-架构说明.md) 确认修改对象是真实源码还是生成结果。
## 旧模块修复流程
无法从仓库发现的信息才向用户确认。已有修改默认属于用户,必须保留并在其基础上工作。
1. 定位已有模块文档和相关内容。
2. 简短分析影响范围和风险。
3. 直接修复。
4. 验证。
5. 按 [50-归档规则.md](50-归档规则.md) 同步相关模块文档。
### 2. 分析与确认
---
- 分析检查:继续读取直到结论有代码、资源、配置或日志支撑。
- 局部修复:说明准备修改的位置和行为,然后直接实施。
- 重大变更:在对话中确认目标、范围、兼容性、数据流、失败行为和验证标准。
- 存在多个真实取舍时提供备选方案;没有实际取舍时不制造方案数量。
## Spec 任务流程
### 3. 实施
大需求 / 新模块统一走 OpenSpec
- 遵循附近文件的结构、命名和语言特性。
- 修改保持在需求涉及的模块内,不顺带重构无关代码。
- 使用结构化 API 处理 JSON、XML、Prefab 和配置,不使用脆弱的字符串替换。
- 修改资源时优先通过 Unity Editor、已有生成器或 MCP 工具,避免无意义的 Prefab 重序列化。
- 不修改任务无关文件,不回退已有工作树变更。
1. 分析目标、影响范围、风险和依赖。
2. 给出 2-3 个方案。
3. 用户确认后创建 OpenSpec change。
4. 生成 `proposal.md``design.md``tasks.md`
5. 实现。
6. 验证。
7. 归档并同步文档。
### 4. 生成代码
详见 [60-新特性工作流.md](60-新特性工作流.md)。
修改 `Assets/StrayFog/Editor/CopyToX` 下的 `ESF_` 源码后:
---
1. 通过 Unity 菜单或 MCP 白名单命令执行 CopyToX。
2. 检查 `Assets/Game/GameHFScripte/CopyToHF` 或对应目标目录的生成差异。
3. 确认 `ESF_``HF_` 的类型名、文件名和调用关系转换正确。
4. 编译目标程序集并读取 Unity Console。
## 开发流程
出现“HF 已修改但 ESF 未修改”时先判断是否误改生成文件出现“ESF 已修改但 HF 未变化”时,必须补执行 CopyToX。
### 1. 文档学习
### 5. 分层验证
根据任务类型阅读对应模块文档:
| 改动范围 | 最低验证 |
|---|---|
| `Assets/StrayFog/Core` | 构建 `StrayFogCore.csproj`,再检查 Unity Console |
| `Assets/StrayFog/Editor``Assets/Editor` | 构建适用 Editor 程序集或在 Unity 中触发编译,检查 Console 和菜单行为 |
| `GameGeneralScripte` | 构建 `GameGeneral.csproj` |
| `GameMono` | 构建 `SFMono.csproj` |
| `GameHFScripte` | 构建 `GameHF.csproj` |
| Prefab/UI | 刷新资源检查层级、RectTransform、组件启用状态、引用和实际显示 |
| AssetBundle/分包/下载 | 除 Editor 外验证目标平台路径、协程、MD5、失败回退和日志 |
| 任务类型 | 必读文档 |
|---------|---------|
| UI 开发 | [UI 窗口系统](../modules/frameworks/UI窗口系统/README.md) |
| UI 界面设计 | [UI 界面拼接设计指南](../modules/frameworks/UI窗口系统/界面拼接.md) |
| 相机控制 | [相机系统](../modules/frameworks/相机系统/README.md) |
| 实体开发 | [实体系统](../modules/frameworks/实体系统/README.md) |
| 事件使用 | [事件系统](../modules/frameworks/事件系统/README.md) |
| 资源加载 | [资源管理](../modules/frameworks/资源管理/README.md) |
常用命令:
### 2. 需求分析
1. 明确需求:理解业务需求,确定需要实现的功能。
2. 评估复杂度:判断是否需要核心库支持或扩展。
3. 设计方案:规划代码结构和调用关系。
### 3. 代码编写
1.`GameFunction/` 下按职责创建模块目录。
2. 遵循编码规范,使用核心库提供的 API。
3. 在 Unity 中测试功能正确性。
### 4. 代码审查
1. 自我检查:检查是否符合规范,是否有潜在问题。
2. 团队审查:提交代码进行团队审查。
3. 修复问题:根据审查意见修改代码。
### 5. 集成测试
1. 模块测试:测试单个模块功能。
2. 集成测试:测试模块间交互。
3. 回归测试:确保不影响现有功能。
---
## 文档变更联动检查
修改任何文档前,确认它是否被其他文档引用:
- 模块文档变更 → 同步检查模块索引、模块登记规则、架构说明。
- 架构/目录变更 → 同步检查模块索引、相关模块 README、架构说明。
- 规则文件变更 → 同步检查 `rules/项目规则入口.md``ai/AI接入入口.md``AGENTS.md``CLAUDE.md``RULES.md`
- spec/change 完成 → 同步更新相关模块 progress.md并建立 `specs/openspec/` 归档。
---
## 提交前检查
### 提交信息格式
推荐格式:
```text
[模块][类型]: 简短描述
- 详细说明 1
- 详细说明 2
```powershell
dotnet build SFMono.csproj --no-restore -v:minimal
dotnet build GameHF.csproj --no-restore -v:minimal
```
类型:`feat``fix``docs``refactor``test``chore`
其他程序集按实际改动替换项目文件。构建结果需区分既有警告和本次新增错误
模块名示例:`framework``entity``ui``event``resource``docs``rules``hooks`
### 6. Unity 与 MCP 验证
示例
需要 Unity 自动化时
```text
[entity][docs]: 补充实体池索引机制说明
1. 调用 `unity_health`,确认项目路径和 Unity 版本。
2. 执行白名单菜单、资源刷新、Prefab 生成或导出。
3. 调用 `unity_get_console_logs` 检查新增 Error 和关键 Warning。
4. Prefab/UI 任务继续做结构与实际显示验证,不能以“文件已生成”作为完成条件。
- 添加 onlyid -> entity 映射说明
- 补充 HFBasicPool/HFEnemyPool/HFHumanityPool 边界
```
连接失败时停止后续 Unity 命令并报告原因,不假设命令已经执行。
> **历史格式说明**:早期文档使用 `【操作原因】具体操作内容`(如 `【修复】xxx`)。该格式已弃用,新提交统一使用 `[module][type]: description`。
### 7. 文档同步
### 分支管理
按 [文档同步规则](50-文档同步规则.md) 判断是否存在长期知识变化:
| 分支名称 | 用途 |
|----------|------|
| `master` | 主分支,稳定版本 |
| `develop` | 开发分支,日常开发 |
| `feature/*` | 功能分支,开发新功能 |
| `bugfix/*` | Bug 修复分支 |
- 公共接口、架构边界、生成流程、平台行为或稳定使用规则变化时,同步模块文档。
- 局部 Bug 修复、临时日志和实现细节不自动写入长期文档。
- 同一事实只在一个文档完整定义,其他位置链接引用。
### Git 操作原则
### 8. Git
- 执行 Git 操作前确认当前操作路径是否正确
- 遵循项目既定的分支策略
- 使用语义化提交信息,清晰描述更改内容
- 重要功能提交前建议先创建分支进行测试
- 开发完成后及时推送到远程仓库
- 只有用户明确要求“提交”“commit”“推送”时才执行 Git 写操作
- UnityAI 和目标 Unity 项目是独立仓库,分别检查、暂存、提交和推送
- 提交前运行工作流检查、目标测试、`git diff --check` 和提交信息检查
- 提交格式:`[模块][类型]: 描述`,类型为 `feat``fix``docs``refactor``test``chore`
- 不把两个仓库的改动描述成一个提交,也不夹带任务开始前的无关修改
### 提交前检查项
## 三、UI 专项流程
- [ ] 文档链接可点击且指向正确
- [ ] 文档关联表已更新
- [ ] 模块索引已同步(如新增/拆分了模块)
- [ ] 无拼写或明显格式错误
- [ ] 未引入外部项目专属内容
- [ ] 业务参考内容只出现在 `business-reference/` 下。
1. 先读 [UI 开发指南](../modules/frameworks/UI窗口系统/开发指南.md) 和 [界面拼接](../modules/frameworks/UI窗口系统/界面拼接.md)
2. 同时检查 Prefab 层级、脚本绑定、模板节点、布局组件和数据刷新时机
3. 单列列表使用 LoopListView2 接口,多列布局使用 LoopGridView 接口
4. 列表初始化放在一次性生命周期,数据数量刷新放在窗口打开或数据变化时
5. 完成后验证空数据、少量数据、超出视口、重复打开和回收重用
---
## 四、异步与平台规则
## 新增内容流程
- UnityWebRequest、StreamingAssets、CDN 和 WebGL 等待流程使用协程或回调。
- 不在主线程使用阻塞式 `while`、忙等或同步等待网络结果。
- Editor 可用不代表 Android、iOS、WebGL 或小游戏路径可用;平台路径和存储回调必须分别验证。
- 下载完成计数只在单个文件完整写入并通过校验后推进。
### 新增框架模块
## 五、完成检查
1. 在 [模块索引](../modules/模块索引.md) 中登记
2. 创建 `modules/frameworks/<module>.md`;复杂模块可拆分为 `modules/frameworks/<module>/README.md` 加子文档
3. 在 [架构说明](10-架构说明.md) 中补充依赖方向
4. 更新相关模块的文档关联表
### 新增编辑器工具
1. 在 [模块索引](../modules/模块索引.md) 中登记
2. 创建 `modules/editor-tools/<tool>.md` 或补充汇总文档。
3. 说明使用入口、生成输出、依赖项。
### 新增业务参考
1. 放入 `business-reference/<project>/`
2. 不进入 `modules/模块索引.md` 主索引。
3.`business-reference/<project>/业务框架映射接口.md` 中补充映射关系。
---
## 生成/构建流程
若项目使用 StrayFog 框架配套的生成工具:
- 优先修改源表、proto 或 schema而不是生成输出。
- 生成后检查输出目录,确认变更符合预期。
- 缺失的生成步骤记录到对应模块文档。
---
- [ ] 修改了真实源码,没有误改生成文件
- [ ] 必要生成步骤已经执行并检查差异
- [ ] 相关程序集编译通过
- [ ] Unity Console 无本次新增错误
- [ ] UI/Prefab 或平台行为完成实际验证。
- [ ] 长期文档按影响同步,没有写入流水账。
- [ ] 两个仓库状态分别检查,未触碰用户无关修改。
- [ ] 未经明确要求没有提交或推送
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [rules/项目规则入口.md](项目规则入口.md) | 工作流清单是项目规则入口索引的关键规则 |
| [rules/50-归档规则.md](50-归档规则.md) | 归档规则与工作流清单互相引用 |
| [rules/60-新特性工作流.md](60-新特性工作流.md) | 新特性工作流是工作流清单的延伸 |
| [rules/20-开发规则.md](20-开发规则.md) | 提交格式与开发规则互相引用 |
| [modules/模块索引.md](../modules/模块索引.md) | 新增模块登记需同步模块索引 |
| [hooks/commit-check.ps1](../hooks/commit-check.ps1) | 提交检查脚本需与本文件格式一致 |
| [项目规则入口](项目规则入口.md) | 指向本工作流的快速入口 |
| [架构说明](10-架构说明.md) | 源码与生成代码边界 |
| [开发规则](20-开发规则.md) | 实施阶段的代码与 Unity 规范 |
| [文档同步规则](50-文档同步规则.md) | 完成后的长期知识同步 |
| [共享检查](../hooks/Hook共享层.md) | 工作流和提交检查脚本 |

View File

@@ -1,79 +0,0 @@
# 归档规则
> 本文档是 UnityAI / StrayFog 框架知识库的长期规则,为 Codex (AGENTS.md) 和 Claude Code (CLAUDE.md) 共同引用。
“归档”是指任务完成后,通过归档 hook / 归档检查判断是否需要把本次任务产生的长期信息同步回项目文档。
归档 hook / 归档检查每个任务都要执行;归档不是写流水账,也不是每次都改所有文档。只更新和本次任务相关的文档。
## 什么时候必须更新文档
- 新增模块、拆分模块、调整模块职责。
- 修改架构、目录结构、依赖方向。
- 新增或改变生成流程。
- 完成一个 spec、计划、功能阶段或里程碑。
- 修改提交、构建、验证、发布规则。
- 用户明确要求记录进度。
- 旧模块修复改变了模块状态、关键流程、后续任务或长期约定。
## 简单修复如何归档
简单修复也必须执行归档 hook / 归档检查。
如果只是查询、解释、无长期影响的小修,可以不改文档,但最终回复中应说明没有长期文档需要更新。
如果简单修复改变了长期规则、模块状态或后续任务,需要补充对应文档。
## 归档内容
按需更新,并保持文档之间的关联关系一致:
| 文档 | 更新时机 | 必须同步更新的关联文档 |
|---|---|---|
| `specs/``specs/openspec/<change>/` | 每个 Spec 任务完成后 | 相关模块 README/progress/tasks、架构说明、模块索引 |
| `modules/<module>/业务参考总入口.md` | 模块职责/入口/结构变化时 | 模块索引、模块登记规则、架构说明、相关功能/框架模块文档 |
| `modules/<module>/progress.md` | 阶段状态变化时 | 同一模块的 README、tasks、相关 spec |
| `modules/<module>/tasks.md` | 下一步任务变化时 | 同一模块的 progress、plans如涉及跨模块计划 |
| `rules/10-架构说明.md` | 架构/目录/依赖方向变化时 | 模块索引、相关模块 README |
| `rules/30-模块登记规则.md` | 新增/拆分/调整模块时 | 架构说明、模块索引、相关模块 README |
| `rules/项目规则入口.md` | 项目规则入口内容变化时 | `ai/AI接入入口.md`、相关规则文件、`AGENTS.md`/`CLAUDE.md`/`RULES.md` |
| `RULES.md` | 仅当重定向目标变化时 | 无需单独维护内容,保持指向 `rules/项目规则入口.md` |
### 关联更新原则
- **一处定义,多处引用**:如果一个事实出现在多个文档中,修改时必须同步所有地方。
- **新增模块必须登记**:新增或拆分模块后,必须更新 `modules/模块索引.md`;如分类、状态定义或登记口径变化,再更新 `rules/30-模块登记规则.md`
- **已有模块变更必须留痕**:修改某个模块的入口、数据流或关键类时,同步更新该模块的 progress.md并在相关 spec/change 中记录。
- **避免孤儿文档**:不要创建没有从索引/注册表链接过去的模块或 spec 文档。
## 归档格式
建议每次记录:
- 日期。
- 本次目标。
- 影响范围。
- 已完成。
- 未完成/TODO。
- 验证情况。
- 相关代码或文档链接。
## 归档前检查
- 不把临时讨论写成长久事实。
- 不把所有模块细节写进总入口。
- 不重复记录已经能从代码直接看出的内容。
- 不记录密钥、账号、机器私有路径。
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [rules/项目规则入口.md](项目规则入口.md) | 归档规则是项目规则入口索引的关键规则 |
| [rules/40-工作流清单.md](40-工作流清单.md) | 任务分类、提交前检查与归档触发互相引用 |
| [rules/60-新特性工作流.md](60-新特性工作流.md) | OpenSpec 归档流程与本文件归档规则互相引用 |
| [specs/openspec/OpenSpec归档.md](../specs/openspec/OpenSpec归档.md) | OpenSpec 归档说明与本文件归档规则互相引用 |

View File

@@ -0,0 +1,59 @@
# 文档同步规则
文档同步的目标是保存长期有效的框架知识,不是记录每次任务过程。
## 必须同步的变化
- 公共 API、参数、返回值或调用顺序变化。
- 框架分层、源码位置、生成目录或依赖方向变化。
- Prefab、资源、配置或构建产物的稳定结构变化。
- Editor 工具、CopyToX、AssetBundle、分包、热更新等生成流程变化。
- 平台路径、下载策略、失败回退或兼容性规则变化。
- 模块新增、删除、重命名、状态或入口变化。
## 通常不写入长期文档
- 不改变公开行为的局部 Bug 修复。
- 临时断点、调试日志、截图和排错过程。
- 一次性测试数据或仅服务当前任务的文件列表。
- 可以直接从代码读出的私有实现细节。
- 尚未确认的讨论结论和未来计划。
## 同步位置
| 变化 | 写入位置 |
|---|---|
| 跨模块规则和工作方式 | `rules/` 中对应唯一规则 |
| 模块职责、架构和数据流 | 模块 `README.md` |
| 业务调用和公共 API | 模块 `使用指南.md` 或开发指南 |
| 模块入口或状态 | `modules/模块索引.md` |
| 外部项目案例 | `business-reference/` 对应项目 |
重大变更的讨论和任务过程文档规则以 [工作流清单](40-工作流清单.md) 为准;实现后的稳定结论直接进入上述长期文档。
## 单一事实来源
- 一个事实只在一个文档完整定义。
- 其他文档只写任务所需的最短摘要并链接到事实来源。
- 修改事实来源后,检查所有引用是否仍然成立;不要复制整段内容同步维护。
- 模块索引只保存入口、职责摘要和状态,不承载完整使用说明。
## 检查方式
任务完成后运行:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File hooks/workflow-check.ps1 `
-UnityProjectRoot <Unity工程路径>
```
脚本只输出工作树、源码/生成代码关系、建议验证和可能需要同步的文档,不自动修改文件。最终是否更新文档按本规则判断。
## 文档关联
| 文档 | 关联原因 |
|---|---|
| [项目规则入口](项目规则入口.md) | 引用文档同步原则 |
| [工作流清单](40-工作流清单.md) | 定义同步发生的任务阶段 |
| [模块登记规则](30-模块登记规则.md) | 模块文档结构和状态口径 |
| [共享检查](../hooks/Hook共享层.md) | 提供只读检查脚本 |

View File

@@ -1,138 +0,0 @@
# 大需求 / 新模块工作流
> 本文档是 UnityAI / StrayFog 框架知识库的长期规则,为 Codex (AGENTS.md) 和 Claude Code (CLAUDE.md) 共同引用。
---
## 总体流程
```text
判断任务类型
|
├── 简单修复 ────────────────→ 直接处理 → 归档检查
|
├── 旧模块修复 ──────────────→ 分析后修复 → 归档检查
|
└── Spec 任务 ───────────────→ OpenSpec 流程
|
v
新模块 / 已有模块大改
|
v
实现 → 验证 → archive → 同步文档
```
---
## 三种任务类型判定
| 类型 | 判定标准 |
|---|---|
| **简单修复** | 查询、解释、小范围修改、用户已给出明确实现方式 |
| **旧模块修复** | 已有框架模块的局部修复,不新增模块、不调整架构 |
| **Spec 任务** | 新增框架模块、调整架构/目录/依赖、批量迁移、用户未明确方案 |
---
## 创建 OpenSpec Change
当任务被判定为 Spec 任务时:
1. 分析目标、影响范围、风险和依赖。
2. 给出 2-3 个方案,让用户选择。
3. 用户确认后创建 OpenSpec change
- Claude Code`/opsx:propose "需求描述"`
- CLI 通用:`openspec new change "change-name"`
4. 生成 `proposal.md``design.md``tasks.md`
---
## 新模块操作清单
适用于在 StrayFog 框架知识库中新增一个框架模块,例如:
- 新增“相机系统”模块文档
- 新增“配置生成”模块文档
- 新增“网络系统”模块文档
操作步骤:
1. 在 [模块索引](../modules/模块索引.md) 中登记模块。
2. 创建模块文档 `modules/frameworks/<module>.md`(简单模块)或 `modules/frameworks/<module>/业务参考总入口.md`(复杂模块)。
3. 如为复杂模块,创建 `progress.md``tasks.md`
4. 在 [架构说明](../rules/10-架构说明.md) 中补充该模块的分层位置和依赖方向。
5. 更新相关模块的文档关联表。
6. 如新增模块影响开发规则,更新 [开发规则](../rules/20-开发规则.md)。
7. 按 design/tasks 完成内容编写。
8. 验证文档链接、格式、关联表完整性。
9. 归档 OpenSpec change。
10. 同步更新模块索引、架构说明、相关模块文档。
---
## 已有模块大改操作清单
适用于对已有框架模块进行较大调整,例如:
- 重构实体系统文档结构
- 调整 UI 窗口系统的生命周期描述
- 合并/拆分对象池相关文档
操作步骤:
1. 读取现有模块文档和模块索引。
2. 在 OpenSpec `design.md` 中说明变更原因、影响范围、新结构。
3. 更新模块 `模块索引.md`
4. 更新模块 `progress.md`,记录本次变更。
5. 更新模块 `tasks.md`,记录后续待办。
6. 同步更新 [模块索引](../modules/模块索引.md)。
7. 如影响架构或依赖方向,更新 [架构说明](../rules/10-架构说明.md)。
8. 验证文档关联表一致性。
9. 归档 OpenSpec change。
---
## 实现与验证
-`tasks.md` 逐项完成。
- 每完成一项,在 `tasks.md` 中标记。
- 验证方式包括:
- Markdown 链接检查
- 文档关联表完整性检查
- 人工阅读关键章节
- 运行 `hooks/archive-check.ps1` 检查关联文档
---
## 归档与联动更新
归档 OpenSpec change 后,必须同步更新:
- `specs/openspec/<change-name>/`
- 相关模块 `模块索引.md``progress.md``tasks.md`
- `rules/10-架构说明.md`(如涉及架构变化)
- `modules/模块索引.md`(如新增模块)
- `rules/30-模块登记规则.md`(如改变模块分类、状态定义或登记规则)
---
## 禁止事项
- 不要在没有 OpenSpec 的情况下进行大模块新增或架构调整。
- 不要把临时讨论写进长期文档。
- 不要创建没有从模块索引链接过去的孤儿文档。
- 不要把外部项目专属内容混入框架核心文档。
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [rules/项目规则入口.md](项目规则入口.md) | 新特性工作流是项目规则入口索引的关键规则 |
| [rules/40-工作流清单.md](40-工作流清单.md) | 工作流清单与新特性工作流互相引用 |
| [rules/50-归档规则.md](50-归档规则.md) | 归档规则与新特性工作流互相引用 |
| [specs/openspec/OpenSpec归档.md](../specs/openspec/OpenSpec归档.md) | OpenSpec 归档说明与新特性工作流互相引用 |
| [modules/模块索引.md](../modules/模块索引.md) | 新增/拆分模块需同步模块索引 |

View File

@@ -1,252 +1,62 @@
# UnityAI - 项目规则入口
# UnityAI 项目规则入口
> 本文件是 UnityAI / StrayFog 框架知识库的 **项目规则入口**
>
> 所有 AI / IDE 工具先通过 [ai/AI接入入口.md](../ai/AI接入入口.md) 接入项目,再按任务范围读取本文件和下方链接的具体文档。
>
> 不要把项目规则重复写在你的系统提示里;直接引用本文件和 `rules/` 下的规则文件。
UnityAI StrayFog 框架的通用工作流与知识标准。具体游戏项目负责提供真实代码、资源和验证环境;当前主要验证对象为 `CrystalBattle_Client`
---
## 快速启动
## AI 快速启动
处理任务时按顺序执行:
1. 确认已读取 [AI 工具接入入口](../ai/AI接入入口.md)。
2. **读取本文件**`rules/项目规则入口.md`
3. 根据任务范围,从下方的 **规则索引**、**项目知识入口**、**Spec 索引** 中选择相关文档读取
4. **简单修复**:直接开始处理,完成后简短说明结果
5. **旧模块修复**:定位已有模块文档和相关内容,简短分析后直接修复
6. **大需求 / 新模块**:新增框架模块、架构调整、文档重组等,先给出 2-3 个方案,用户确认后走 **OpenSpec 流程**
7. **所有任务结束都执行归档 hook / 归档检查**,按影响范围决定是否更新长期文档。
1. 读取本文件和 [工作流清单](40-工作流清单.md)。
2. 从 [模块索引](../modules/模块索引.md) 读取相关模块文档
3. 检查 UnityAI 与目标 Unity 项目的 Git 状态,保留已有修改
4. 根据 [架构说明](10-架构说明.md) 找到真实源码,区分源码、生成代码、业务代码和资源
5. 按任务类型分析、修改、生成和验证
6. 按 [文档同步规则](50-文档同步规则.md) 判断是否需要更新长期文档
---
## 工作方式
## 项目概况
- **分析检查**读取代码、Prefab、配置或日志给出有证据的结论不修改文件。
- **局部修复**:影响范围明确时直接修复并完成针对性验证。
- **重大变更**:涉及公共接口、框架、生成流程、跨模块数据或批量迁移时,先在对话中确认目标、影响和实现方式,再修改。
- 重大变更的讨论和长期结论保存方式以 [工作流清单](40-工作流清单.md) 为准。
- **项目名**UnityAI
- **副标题**StrayFog 框架文档中心
- **目标读者**:使用 StrayFog 框架的 AI 工具、业务开发人员、框架维护人员
- **核心内容**StrayFog 框架架构说明、核心功能使用指南、开发规范、开发流程、项目管理规范
- **仓库结构**
- `ai/` / `rules/` / `modules/` / `hooks/`AI 规则与项目知识库AI 优先读取)
- `modules/frameworks/`:核心框架模块文档(按模块拆分为文件夹)
- `business-reference/`:外部实际项目业务参考(与框架核心分离)
- **技术栈**Unity、C#、StrayFog 框架
- **文档语言**:中文
---
## 语言与工作方式
- **默认使用中文回答用户**。
- **代码、类名、方法名、路径、命令、API 名称保持英文原文**。
- 简单修复直接处理,简短说明结果。
- 旧模块修复先定位已有模块文档和相关内容,简短分析风险后直接修复。
- 大需求 / 新模块先分析目标、影响范围、风险和依赖,给出 2-3 个方案,用户确认后走 OpenSpec。
- 如果用户明确说“直接实现”“按你判断来”,采用保守方案继续执行。
- 所有任务结束都执行归档 hook / 归档检查;不把无长期价值的流水账写入文档。
---
## 规则索引
`rules/` 下的文件是项目长期规则,按以下顺序按需读取:
| 文件 | 用途 |
|---|---|
| [00-项目概览.md](00-项目概览.md) | 项目用途、文档结构、核心子系统 |
| [10-架构说明.md](10-架构说明.md) | 框架结构、目录职责、依赖方向、程序集边界 |
| [20-开发规则.md](20-开发规则.md) | C# 规范、Unity 资源规则、文档编写规则 |
| [30-模块登记规则.md](30-模块登记规则.md) | 模块登记口径、状态定义、文档拆分规则 |
| [40-工作流清单.md](40-工作流清单.md) | 任务分类、修复流程、Spec 流程、提交前检查 |
| [50-归档规则.md](50-归档规则.md) | 归档时机、归档内容、关联更新原则 |
| [60-新特性工作流.md](60-新特性工作流.md) | 大需求 / 新模块如何通过 OpenSpec 流程推进 |
| [70-项目设置.md](70-项目设置.md) | AI 工具扫描范围、跳过目录、例外情况 |
---
## 项目知识入口
项目框架、核心功能、编辑器工具的总入口统一维护在 [modules/模块索引.md](../modules/模块索引.md)。
规则文件不重复维护模块清单;新增、拆分、改名模块时,以 `modules/模块索引.md` 为唯一事实来源,并按 [30-模块登记规则.md](30-模块登记规则.md) 的口径登记。
常用框架规则入口:
- [实体系统](../modules/frameworks/实体系统/README.md)实体基类、System-Model 架构、实体池、生命周期。
- [UI 窗口系统](../modules/frameworks/UI窗口系统/README.md)UIWindow、Prefab、层级、生命周期和事件释放。
- [事件系统](../modules/frameworks/事件系统/README.md)CTC/STC 事件、派发链路、监听规范。
- [资源管理](../modules/frameworks/资源管理/README.md)AssetBundle、语言资源、图集、加载规则。
- [对象池](../modules/frameworks/内存对象管理/README.md)类实例池、行为对象池、实体池、RT 展示对象。
---
## Spec / Plan 索引
| 类型 | 文档 |
|---|---|
| Spec 索引 | [specs/Spec索引.md](../specs/Spec索引.md) |
| OpenSpec 归档 | [specs/openspec/OpenSpec归档.md](../specs/openspec/OpenSpec归档.md) |
| 计划进度 | [plans/](../plans/) |
---
## 任务流转
### 简单修复
- 查询、解释、定位问题。
- 小范围、明确、影响面低的修改。
- 用户已给出明确实现方式。
处理:直接开始修复或回答,完成后验证,并执行归档 hook / 归档检查。
### 旧模块修复
- 项目中已有模块的 bug 修复、逻辑补全、局部优化。
- 影响范围主要集中在一个已有功能或框架模块。
- 不新增大模块,不调整总体架构。
处理:读取相关模块文档和代码/内容,简短分析影响范围后直接修复;完成后验证,并按 [50-归档规则.md](50-归档规则.md) 执行归档 hook / 归档检查。
### Spec 任务
- 新增大功能或新模块。
- 调整架构、目录职责、依赖方向。
- 调整文档结构、生成流程。
- 涉及多个系统的批量修改、删除、迁移。
- 用户没有明确方案,需要先权衡。
处理:按 [40-工作流清单.md](40-工作流清单.md) 和 [60-新特性工作流.md](60-新特性工作流.md) 走 OpenSpec。
---
## OpenSpec 工作流(大需求 / 新模块)
1. 分析目标、影响范围、风险和依赖。
2. 给出 2-3 个方案,让用户选择。
3. 用户确认后创建 OpenSpec change
- Claude Code`/opsx:propose "需求描述"`
- CLI 通用:`openspec new change "change-name"`
4. 生成 `proposal.md``design.md``tasks.md`
5. 判断是 **新模块** 还是 **已有模块的大改**
- 新模块:创建模块文档、登记模块、补充架构。
- 已有模块的大改:更新模块 progress.md、tasks.md记录变更。
6. 按 design/tasks 实现代码、资源或配置变更。
7. 验证。
8. 归档 OpenSpec change
- Claude Code`/opsx:archive`
- CLI 通用:`openspec archive "change-name"`
9. 按 [50-归档规则.md](50-归档规则.md) 同步到项目文档:
- `specs/openspec/<change-name>/`
- 相关模块 `模块索引.md``progress.md``tasks.md`
- `10-架构说明.md`(如涉及架构变化)
- `modules/模块索引.md`(如新增模块)
- `30-模块登记规则.md`(如改变模块分类、状态定义或登记规则)
---
## 扫描范围
正常分析项目时,**默认跳过**以下目录和文件以节省 token
- `Library/`
- `Temp/`
- `Logs/`
- `UserSettings/`
- `.vs/`
- `*.csproj`
- `*.sln`
- `.codex/`(本地 hooks 状态)
- `.claude/`(本地 commands/skills 状态)
- `Library/PackageCache/`
- `obj/`
- `bin/`
- 图片、音频、模型等二进制资源
详见 [70-项目设置.md](70-项目设置.md)。
---
完整步骤与验收条件统一维护在 [工作流清单](40-工作流清单.md),本文件不重复展开。
## 关键约束
- **一处定义,多处引用**:如果一个事实出现在多个文档中,修改时必须同步所有地方
- 优先修改源头文档,不要手动同步复制多份内容
- 运行时代码不引用 Editor 程序集
- UI 变更遵循 [UI 框架](../modules/frameworks/UI窗口系统/README.md)
- 事件变更遵循 [事件框架](../modules/frameworks/事件系统/README.md)
- 资源变更遵循 [资源管理](../modules/frameworks/资源管理/README.md)
- 先定位源码再修改,禁止把生成目录当作源码
- `ESF_` 框架修改发生在 `Assets/StrayFog/Editor/CopyToX`,随后通过 CopyToX 生成 `HF_` 代码
- 不直接手改 `Assets/Game/GameHFScripte/CopyToHF`
- 业务代码通过项目公开入口使用框架;维护框架时修改真实框架源码
- Prefab/UI 修改必须同时核对脚本、层级、RectTransform、组件状态和实际显示
- Unity、平台和网络相关逻辑不能只凭 Editor 编译结果判断正确
- 只在用户明确要求提交或推送时执行 Git 写操作UnityAI 与游戏项目分别提交。
- 不覆盖、不回退任务开始前已有的修改或未跟踪文件。
---
## 规则索引
## 归档规则摘要
| 文档 | 用途 |
|---|---|
| [项目概览](00-项目概览.md) | UnityAI 定位、目录与读者 |
| [架构说明](10-架构说明.md) | 代码分层、源码边界和依赖方向 |
| [开发规则](20-开发规则.md) | C#、Unity、资源和生成代码规范 |
| [模块登记规则](30-模块登记规则.md) | 模块分类、状态与文档结构 |
| [工作流清单](40-工作流清单.md) | 唯一的任务执行流程 |
| [文档同步规则](50-文档同步规则.md) | 长期文档更新条件和方法 |
| [项目设置](70-项目设置.md) | 扫描范围和忽略目录 |
所有任务完成后都执行归档 hook / 归档检查。归档不是写流水账,只更新和本次任务相关的长期文档。
## 知识入口
简单修复如果没有改变长期规则、模块状态、架构或后续任务,可以只在最终回复中说明无需文档更新;旧模块修复和 Spec 任务按影响范围同步相关模块文档
核心原则:**一处定义,多处引用**
详见 [50-归档规则.md](50-归档规则.md)。
---
## 文档变更联动原则
修改任何文档前,先确认它是否被其他文档引用:
- 模块文档变更 → 同步检查模块索引、模块登记规则、架构说明。
- 架构/目录变更 → 同步检查模块索引、相关模块 README、架构说明。
- 规则文件变更 → 同步检查本文件、`ai/AI接入入口.md``AGENTS.md``CLAUDE.md``RULES.md`
- spec/change 完成 → 同步更新相关模块 progress.md并建立 `specs/openspec/` 归档。
## 归档触发
文档关联和归档触发逻辑统一放在 `hooks/`
| 触发方式 | 命令/路径 | 说明 |
|---|---|---|
| Codex Stop hook | `.codex/hooks/archive-reminder.ps1` | 每次任务结束自动调用 `hooks/archive-check.ps1` 输出建议 |
| Claude Code 手动 | `/opsx:archive-check` | 手动触发归档检查 |
| OpenSpec 自动 | `/opsx:archive` | Spec 流程归档完成后自动调用 `hooks/archive-check.ps1` |
| 手动脚本 | `hooks/archive-check.ps1` | 直接运行 PowerShell 脚本 |
**重要**:以上触发只输出建议,不会自动修改任何文档。最终是否更新关联文档,需要用户确认。
详见 [hooks/Hook共享层.md](../hooks/Hook共享层.md)。
---
## 接入新 AI 工具时
如果你是新接入的 AI 工具,只需要:
1. 读取 [ai/AI接入入口.md](../ai/AI接入入口.md)。
2. 再读取本文件(`rules/项目规则入口.md`)。
3. 按任务范围读取本文件索引中的具体规则/模块/spec 文档。
4. 遵循本文件的工作方式、扫描范围、关键约束和归档规则。
不同 AI / IDE 的默认入口、hook 适配方式统一维护在 `ai/AI接入入口.md`。项目根目录的 `AGENTS.md``CLAUDE.md``RULES.md` 都是指向 `ai/AI接入入口.md` 的薄入口。
---
- [模块索引](../modules/模块索引.md):框架、编辑器工具和使用文档入口
- [共享检查](../hooks/Hook共享层.md):工作流和提交检查脚本。
- `business-reference/`:外部业务项目案例,只作参考,不作为框架规范
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [ai/AI接入入口.md](../ai/AI接入入口.md) | AI 接入入口引用本项目规则入口 |
| [AGENTS.md](../AGENTS.md) | Codex 入口通过 ai/AI接入入口.md 引用本文件 |
| [CLAUDE.md](../CLAUDE.md) | Claude Code 入口通过 ai/AI接入入口.md 引用本文件 |
| [RULES.md](../RULES.md) | 兼容重定向入口通过 ai/AI接入入口.md 引用本文件 |
| [rules/00-项目概览.md](00-项目概览.md) | 项目概况在本文档中被引用 |
| [rules/10-架构说明.md](10-架构说明.md) | 架构说明在本文档中被引用 |
| [rules/20-开发规则.md](20-开发规则.md) | 开发规则在本文档中被引用 |
| [rules/30-模块登记规则.md](30-模块登记规则.md) | 模块登记规则在本文档中被引用 |
| [rules/40-工作流清单.md](40-工作流清单.md) | 工作流清单在本文档中被引用 |
| [rules/50-归档规则.md](50-归档规则.md) | 归档规则在本文档中被引用 |
| [rules/60-新特性工作流.md](60-新特性工作流.md) | 大需求/新模块工作流在本文档中被引用 |
| [rules/70-项目设置.md](70-项目设置.md) | 扫描范围规则在本文档中被引用 |
| [modules/模块索引.md](../modules/模块索引.md) | 模块索引是本文档引用的项目知识入口 |
| [specs/Spec索引.md](../specs/Spec索引.md) | Spec 索引在本文档中被引用 |
| [hooks/Hook共享层.md](../hooks/Hook共享层.md) | 归档触发机制在本文档中被引用 |
| [AI 接入入口](../ai/AI接入入口.md) | AI 工具通过该入口进入本规则 |
| [工作流清单](40-工作流清单.md) | 任务执行流程的唯一事实来源 |
| [文档同步规则](50-文档同步规则.md) | 任务完成后的长期知识同步规则 |
| [模块索引](../modules/模块索引.md) | 项目知识入口 |
| [共享检查](../hooks/Hook共享层.md) | 规则的脚本化检查入口 |

View File

@@ -1,75 +0,0 @@
# Spec 标题
## 文档关联
- **相关规则**
- [归档规则](../rules/50-归档规则.md)
- [大需求 / 新模块工作流](../rules/60-新特性工作流.md)
- **相关模块**
- `[<功能模块>]`(示例路径:`../modules/features/<feature-name>.md`
- `[<框架模块>]`(示例路径:`../modules/frameworks/<framework-name>.md`
> 当本 spec 发生变更时,必须检查并同步更新相关模块文档和规则。
## 背景
TODO为什么做这件事当前有什么问题
## 目标
- TODO
## 非目标
- TODO
## 影响范围
- 代码:
- 资源:
- 数据/配置:
- 文档:
- 关联模块:
- `[<功能模块>]`(示例路径:`../modules/features/<feature-name>.md`
- `[<框架模块>]`(示例路径:`../modules/frameworks/<framework-name>.md`
## 方案
### 方案 A
- 优点:
- 缺点:
### 方案 B
- 优点:
- 缺点:
## 选定方案
TODO
## 任务
- [ ] TODO
## 验证
- [ ] TODO
## 变更记录
| 日期 | 变更内容 | 变更人 |
|---|---|---|
| YYYY-MM-DD | 初始版本 | - |
## 归档
完成后必须同步更新(详见 [归档规则](../rules/50-归档规则.md)、[大需求 / 新模块工作流](../rules/60-新特性工作流.md)
- [ ] `specs/openspec/<change-name>/`
- [ ] 相关模块 `模块索引.md`
- [ ] 相关模块 `progress.md`
- [ ] 相关模块 `tasks.md`
- [ ] `rules/10-架构说明.md`(如涉及架构变化)
- [ ] `rules/30-模块登记规则.md`(如新增模块)

View File

@@ -1,57 +0,0 @@
# Spec 索引
大需求、新模块、架构调整、生成流程调整等 Spec 任务在这里沉淀方案和结果。
## 使用规则
- 简单修复和旧模块修复不需要创建 spec按规则直接处理并执行归档检查。
- 大需求 / 新模块使用 OpenSpec 流程:
- Claude Code 命令 `/opsx:propose "需求描述"` 创建 change。
- 生成 `proposal.md``design.md``tasks.md`
- `/opsx:apply` 执行实现。
- `/opsx:archive` 归档到 `openspec/changes/archive/`
- OpenSpec 归档后,再按 [归档规则](../rules/50-归档规则.md) 同步到项目既有模块文档:
- `specs/openspec/<change-name>/`
- `modules/<module>/业务参考总入口.md``progress.md``tasks.md`
- `rules/10-架构说明.md``rules/30-模块登记规则.md`
- 如果不用 OpenSpec也可复制 [Spec模板.md](Spec模板.md) 创建普通 spec。
## OpenSpec 归档
大需求 / 新模块经 OpenSpec 流程后,长期归档优先放在:
```text
specs/openspec/
```
详见 [specs/openspec/OpenSpec归档.md](openspec/OpenSpec归档.md)。
## 命名建议
普通 spec 文件:
```text
YYYY-MM-DD-feature-name.md
```
OpenSpec change
```text
kebab-case-change-name
```
## 文档关联
- [工作流清单](../rules/40-工作流清单.md)任务分类、修复流程、Spec 流程、提交前检查、生成/构建流程。
- [大需求 / 新模块工作流](../rules/60-新特性工作流.md):新增/已有功能判断、模块登记、联动更新检查。
- [归档规则](../rules/50-归档规则.md):归档时机、内容、格式和关联更新原则。
- [模块索引](../modules/模块索引.md):项目功能、框架、编辑器工具模块总览。
## 同步要求
OpenSpec change 完成后,必须将结论同步到项目模块文档:
- `specs/openspec/<change-name>/` — OpenSpec 归档目录
- `modules/<module>/业务参考总入口.md``progress.md``tasks.md` — 相关模块文档
- `rules/10-架构说明.md`(如涉及架构变化)
- `rules/30-模块登记规则.md`(如新增模块)

View File

@@ -1,100 +0,0 @@
# OpenSpec 归档目录
本目录用于存放大需求、新模块、架构调整、生成流程调整等 Spec 任务经 OpenSpec 流程沉淀后的长期文档。
## 目录结构
```text
specs/openspec/
├── 模块索引.md # 本文件
├── <change-name>/ # 单个 Spec 任务的归档
│ ├── proposal.md # 需求背景与目标
│ ├── design.md # 设计方案
│ └── tasks.md # 实现任务与验证
└── ...
```
## OpenSpec 工作目录
OpenSpec CLI 的活跃工作目录位于项目根目录:
```text
openspec/
├── changes/ # 进行中的 change
├── changes/archive/ # OpenSpec 自身归档的 change
└── specs/ # OpenSpec 管理的 spec
```
## 工作流
### 简单修复 / 旧模块修复
- 直接修改代码/资源。
- 简短说明结果。
- 执行归档检查;没有长期信息变化时,不归档到本目录。
### 大需求 / 新模块
1. 使用 Claude Code 命令 `/opsx:propose "需求描述"` 创建 change。
2. 按 OpenSpec 流程生成 `proposal.md``design.md``tasks.md`
3. 使用 `/opsx:apply` 执行实现。
4. 实现完成后使用 `/opsx:archive` 归档。
5. **同时**把结论同步到本目录和项目既有模块文档:
- `specs/openspec/<change-name>/`
- `modules/<module>/业务参考总入口.md`
- `modules/<module>/progress.md`
- `modules/<module>/tasks.md`
- `rules/10-架构说明.md`(如涉及架构变化)
- `modules/模块索引.md`(如新增模块)
- `rules/30-模块登记规则.md`(如改变模块分类、状态定义或登记规则)
## 文档关联
- [工作流清单](../../rules/40-工作流清单.md)任务分类、修复流程、Spec 流程、提交前检查、生成/构建流程。
- [大需求 / 新模块工作流](../../rules/60-新特性工作流.md):新增/已有功能判断、模块登记、联动更新检查。
- [归档规则](../../rules/50-归档规则.md):归档时机、内容、格式和关联更新原则。
- [模块索引](../../modules/模块索引.md):项目功能、框架、编辑器工具模块总览。
## 同步要求
OpenSpec change 完成后,必须将结论同步到项目模块文档:
- `specs/openspec/<change-name>/` — OpenSpec 归档目录
- `modules/<module>/业务参考总入口.md``progress.md``tasks.md` — 相关模块文档
- `rules/10-架构说明.md`(如涉及架构变化)
- `modules/模块索引.md`(如新增模块)
- `rules/30-模块登记规则.md`(如改变模块分类、状态定义或登记规则)
## 与项目既有规则的关系
- 项目既有规则入口:`RULES.md``AGENTS.md``rules/`
- OpenSpec 负责大需求 / 新模块的 spec-driven 流程。
- 项目既有文档负责长期模块知识、架构说明和任务跟踪。
- 大需求 / 新模块完成后OpenSpec 归档和项目归档都需要做。
## 命令速查
```bash
# 查看进行中的 changes
openspec list
# 查看 specs
openspec list --specs
# 创建新 change
openspec new change "change-name"
# 查看 change 状态
openspec status --change "change-name"
# 归档 change
openspec archive "change-name"
```
Claude Code 命令:
- `/opsx:propose "需求描述"` — 创建并生成 change 文档
- `/opsx:apply` — 按 design/tasks 实现
- `/opsx:archive` — 归档完成
- `/opsx:explore` — 探索性分析
- `/opsx:sync` — 同步 specs