# 文档生成操作手册 ## 这篇文章是干什么的 当你需要更新这个项目的文档,或者想把同一套方法套到另一个 UE 项目时,看这个就够了。 本文记录: 1. **踩过的坑** — 初版被骂的 10 项问题,别再犯 2. **正确的文档模板** — 每个单位应该长什么样 3. **完整操作步骤** — 从零到 56 个文档是怎么生成的 4. **工具和命令** — 用了什么、怎么用 --- ## 一、血泪教训:初版的 10 项问题 第一次跑完「项目文档化计划」后,被用户逐条指出以下问题,全部在修订版中修正: | # | 问题 | 初版做法 | 正确做法 | |---|------|---------|---------| | 1 | 多个单位合并到一个文件 | `ItemDataStructures.md` 里塞了 3 个 struct | 每个结构体/类/接口/枚举独立一个 .md | | 2 | "设计用意"和"职责范围"合并 | 一个 `## 设计用意与职责范围` 章节糊在一起 | 拆成两个独立章节 | | 3 | "职责范围"用二分法 | 写"职责:xxx / 不负责:yyy" | 自然语言描述工作范围 | | 4 | 多余的模块 | 文档里出现"变量"、"关键成员"表格 | 不写实现细节,对外接口从调用者视角描述 | | 5 | 使用方法不引用真代码 | 写"可以这样用"但没有出处 | 每条引用真实文件路径和行号 | | 6 | 用例编造示例代码 | 写伪代码 `// 继承 AMyActor...` | 只列出项目中实际使用该单位的文件 | | 7 | `_relationships.md` 太花哨 | ASCII 艺术图、设计模式分析 | 简单的文件级依赖表格 + 文本箭头 | | 8 | 执行顺序混乱 | 边写接口边写依赖 | 严格:孤立文档 → 关系文档 → 接口+用例 | | 9 | README.md 没更新 | 还是旧的待办列表 | 更新为完整的文档索引 | | 10 | "项目内依赖"列名不对 | 用了"说明"列 | 改为"源文件"列,放文件路径 | ### 为什么这些问题很重要 - **合并文件**:后人要找 `FItemView` 的文档时,不能一眼看到文件名,还得打开文件在里面搜。单位名和文件名一一对应,是文档可发现性的底线。 - **设计用意 vs 职责范围**:前者回答"为什么存在",后者回答"管什么事"。混在一起会让维护者搞不清这个类的边界。 - **真实代码引用**:没有行号的使用说明是废纸。半年后代码改了,维护者无法验证文档是否过期。 --- ## 二、正确的文档模板 ### 2.1 孤立单位文档模板 每个单位(class/struct/enum/BP)一个 .md 文件,**严格**以下结构: ```markdown # [单位名称] ## 基本信息 - **类型**: UCLASS / USTRUCT / UENUM / C++ class / Blueprint - **父类**: XXX(没有就写 —) - **源文件**: path/to/file.h(蓝图写 /Game/ 路径) - **模块**: ModuleName ## 功能概述 [一段话概括这个单位做什么,什么场景用] ## 设计用意 [为什么这样设计,解决什么问题,在系统中的定位] ## 职责范围 [自然语言描述该单位承担的工作范围,它负责什么流程/数据/决策。 禁止使用"职责"/"不负责"二分格式] ## 项目内依赖 | 依赖项 | 关系 | 源文件 | |--------|------|--------| | XXX | 包含/引用/继承/调用 | path/to/file.h:行号 | ## 对外接口 [从外部调用者的角度描述: - C++ 调用者:哪些 public 方法/虚函数可调用 - 蓝图调用者:哪些 UFUNCTION/BlueprintNativeEvent 可调用 - 委托:哪些委托可绑定 - 结构体:哪些 UPROPERTY 字段对外可读写 禁止单纯罗列函数签名] ## 使用方法 [引用项目中的真实代码位置说明如何使用本单位的典型流程。 每条使用方式必须给出源文件和行号出处] ## 用例 [列举项目中实际使用本单位的文件和上下文。不编造示例。 格式:文件路径:行号 — 用途说明] ``` ### 2.2 关系文档模板 (`_relationships.md`) 每个模块目录下一个: ```markdown # [模块名] 依赖关系 ## 文件间引用关系 | 源文件 | 引用方式 | 目标文件 | 目标单位 | |--------|---------|---------|---------| | XXX.h | #include | YYY.h | YYY | | XXX.h | forward-declare | — | ZZZ | ## 关键依赖链 [用简单的文本箭头描述关键数据/控制流,如: DefaultContainer.cpp → ItemFactory::CreateItemInstance → FItemInstance ] ``` ### 2.3 README.md 模板 见 `Document/README.md`,核心是每个模块的完整单位列表表,含:单位名、文件名(链接)、类型、父类。 --- ## 三、完整操作步骤 以下是在一个全新的 UE 项目上复现整个过程的操作步骤。 ### 前置条件 1. **Claude Code CLI**(本文使用的 AI 编程助手) - 安装方式见 [Claude Code 官方文档](https://docs.anthropic.com/en/docs/claude-code) - 关键是它支持 Task 工具(可以启动后台子代理并行干活) - 支持 MCP(用于查询 UE 编辑器中的蓝图信息) 2. **UnrealClaude MCP 插件** — 安装在项目的 `Plugins/UnrealClaude/` 下 - 提供 `unreal_blueprint_query`、`unreal_asset_search` 等 MCP 工具 - 用来查询蓝图资产信息(因为在 .uasset 二进制文件上没法直接 grep) 3. **ripgrep** (`rg`) — 用于在源代码中搜索单位引用 4. **bash shell** — Windows 上用 Git Bash 或 WSL,macOS/Linux 直接用 ### 步骤 1:确定文档化范围 先画出要文档化的所有单位清单: ```bash # 列出所有需要文档化的 C++ 头文件 find Source/ -name "*.h" | grep -v Intermediate | grep -v Generated find Plugins/ -name "*.h" -path "*/Public/*" | grep -v Intermediate | grep -v Generated # 列出所有需要文档化的蓝图(通过 MCP) # 在 Claude Code 对话中使用: # mcp__unrealclaude__unreal_blueprint_query operation=list path_filter=/Game/Blueprints/ ``` 对每个头文件,手动列出它包含的所有单位(一个 .h 可能包含多个 class/struct/enum)。这就是你的文档化清单。 ### 步骤 2:分析所有源文件 这是最耗时但最关键的一步。你需要从每个源文件中提取: - 单位的类型、父类、所属模块 - 功能概述(从注释和代码推断) - 设计意图(为什么存在) - `#include` 依赖关系 - 前向声明 - public 方法、UFUNCTION、delegate **用 Claude Code 的做法**(推荐): 启动多个 `Task` 子代理并行分析: ``` 对 Claude Code 说: "用 Explore 子代理分析 D:\workspace\u\lonese\Plugins\Item\ 目录下所有 .h 和 .cpp 文件, 对每个 class/struct/enum 提取:类型、父类、功能、设计意图、依赖、公开方法。" ``` 关键提示词要点: - 让代理先 `Glob` 找到所有 .h 文件,再逐文件 `Read` - 要求结构化输出(每个单位一个条目) - 明确列出需要的字段:类型/父类/文件路径/模块/功能/设计意图/include依赖/前向声明/公开方法/UFUNCTION/delegate/BlueprintNativeEvent **手动做法**(如果没有 AI 工具): ``` 对每个 .h 文件: 1. 记录文件中定义的所有 UCLASS/USTRUCT/UENUM/接口 2. 记录每个单位的父类和包含的引擎头文件 3. 搜索 #include "项目内头文件" 记录依赖 4. 搜索 forward declaration 5. 记录 UPROPERTY 和 UFUNCTION 标记 6. 从类/方法注释中提取功能描述 ``` 结果应该整理成一张大表,每个单位一行,包含上述所有字段。 ### 步骤 3:阶段 1 — 生成孤立单位文档 对每个单位创建一个 .md 文件,这个阶段**只填**: - 基本信息 - 功能概述 - 设计用意 - 职责范围 - 项目内依赖 **暂不填**(留占位符 `(待阶段3填写)`): - 对外接口 - 使用方法 - 用例 **用 Claude Code 的做法**: ```markdown 对 Claude Code 说: "在 Document/Plugins/Item/ 下为以下每个单位创建独立的 .md 文件, 遵循 [粘贴模板]。每个单位一个文件,不要合并。 现在只填基本信息、功能概述、设计用意、职责范围、项目内依赖。 对外接口、使用方法、用例三个章节写'(待阶段3填写)'。" ``` 然后把每个单位的结构化分析数据附在后面。 **注意**: - 每个单位**必须独立成文件**,绝对不能合并 - "设计用意"和"职责范围"是**两个独立章节**,不要偷懒合并 - "职责范围"用自然语言,**禁止**出现"职责:/不负责:"这种二分写法 - 依赖表列名必须是 `依赖项 | 关系 | 源文件` **手动做法**: ``` 对清单中的每个单位: 1. 创建 Document/[模块]/[单位名].md 文件 2. 从步骤 2 的分析结果中抄入:基本信息、功能概述、设计用意、职责范围、依赖 3. 最后三节写 "(待阶段3填写)" ``` ### 步骤 4:阶段 2 — 生成关系文档 基于阶段 1 的依赖数据,为每个模块创建 `_relationships.md`。 **用 Claude Code 的做法**: ```markdown 对 Claude Code 说: "为 Document/Plugins/Item/ 创建 _relationships.md。 表格列出每个文件间的 #include/forward-declare 关系。 再列出关键数据/控制流依赖链,用简单文本箭头。" ``` **手动做法**: ``` 1. 画一张表: | 源文件 | 引用方式 | 目标文件 | 目标单位 | 2. 从步骤 2 的 #include/forward-declare 数据中抄入 3. 画出关键数据流: - 创建流程:谁调用谁的什么方法创建什么 - 查询流程:谁通过谁获取什么数据 - 更新流程:数据从哪来,经过谁,到哪去 ``` ### 步骤 5:阶段 3 — 填充接口、使用方法和用例 这是最需要"搜代码"的阶段。 **对外接口**的做法: ``` 对每个 C++ 单位: 1. 打开其头文件 2. 找出所有 public/protected 方法 3. 找出所有 UFUNCTION 宏(BlueprintCallable/BlueprintNativeEvent/BlueprintImplementableEvent) 4. 找出所有 UPROPERTY 宏(尤其是 BlueprintReadOnly/BlueprintReadWrite 的) 5. 找出所有 DECLARE_DELEGATE / DECLARE_DYNAMIC_MULTICAST_DELEGATE 6. 用"调用者视角"重写:不要罗列函数签名,而是描述"外部代码可以做哪些事" 好例子:"调用 GetItemViews() 获取所有物品的只读视图,用于 UI 列表展示" 坏例子:"GetItemViews() const -> TArray"(这是罗列签名) 对每个蓝图单位: 1. 通过 MCP unreal_blueprint_query operation=inspect 查询变量和函数 2. 描述蓝图事件图中可被外部调用的函数和事件 3. 列出可编辑的实例变量 ``` **使用方法**的做法: ``` 对每个单位: 1. grep 搜索它在项目中的所有使用位置 rg "[单位名]" Source/ Plugins/ --type cpp --type h 2. 读相关代码段,理解它被如何使用 3. 挑选 2-5 个最典型的用法 4. 写成:文件路径:行号 — 在这段代码中是怎么用的 ``` **用例**的做法: ``` 对每个单位: 1. 从 grep 结果中确定哪些文件会用到它 2. 对每个使用文件写一条:文件路径:行号 — 用途说明 3. 不要伪造。如果某个文件只是 include 了但没实际用,不要写进去 ``` **用 Claude Code 的做法**: ```markdown 对 Claude Code 说: "对 Document/Plugins/Item/ 下的所有 .md 文件, 用 Grep 搜索每个单位在 Source/ 和 Plugins/ 中的使用位置, 然后更新对外接口、使用方法、用例三个章节。 要求每条引用有文件路径和行号,只写真实代码中存在的用法。" ``` ### 步骤 6:阶段 4 — 更新 README.md ``` 1. 列出所有 5 个模块 2. 每个模块下列出所有单位、文件名(带链接)、类型、父类 3. 描述文档模板的章节结构 4. 写下统计数字:多少单位、多少文件、覆盖哪些模块 ``` ### 步骤 7:验证 ``` □ 每个 .h 中定义的 class/struct/enum 都有对应的 .md 文件 □ 没有多单位合并到一个文件的情况(检查文件名列表跟单位列表一一对应) □ 每个 .md 的"设计用意"和"职责范围"是两个独立章节 □ "职责范围"中没有出现"不负责"字样(这不是要求删除信息,而是用自然语言描述边界) □ 每个 _relationships.md 的表格列名是"源文件|引用方式|目标文件|目标单位" □ 每个使用方法/用例条目都带有文件路径和行号 □ 没有编造的伪代码示例 □ README.md 有完整的文档索引 □ 所有占位符"(待阶段3填写)"已被替换 ``` --- ## 四、工具速查 ### 4.1 Grep 搜索命令 ```bash # 搜索某个类/结构体在项目中的使用 rg "FItemView" Plugins/Item/ Source/ --type-add 'ue:*.h' --type-add 'ue:*.cpp' --type ue -n # 只列出文件名 rg "IItemContainer" -l # 搜索 #include 关系 rg '#include.*ItemFactory' Plugins/Item/ ``` ### 4.2 MCP 蓝图查询命令 在 Claude Code 对话中可以直接用以下工具: ``` # 列出所有蓝图 mcp__unrealclaude__unreal_blueprint_query operation=list path_filter=/Game/Blueprints/ limit=50 # 查看单个蓝图的变量和函数 mcp__unrealclaude__unreal_blueprint_query operation=inspect blueprint_path=/Game/Blueprints/BP_TestChar include_variables=true include_functions=true # 搜索资产 mcp__unrealclaude__unreal_asset_search class_filter=Blueprint name_pattern=Test ``` ### 4.3 Claude Code Task 代理 ``` # 启动一个分析代理(后台运行) Task subagent_type=Explore run_in_background=true prompt="分析 X 目录下所有头文件..." # 启动一个写文件的代理(后台运行) Task subagent_type=general-purpose run_in_background=true prompt="为以下单位创建/更新文档..." # 查看代理输出 TaskOutput task_id=<代理ID> block=true timeout=120000 ``` --- ## 五、作者注 ### 我是怎么被骂的 第一轮跑完「项目文档化计划」后,用户逐项检查,指出了 10 个问题(详见第一章)。核心批评: 1. **合并文件是最愚蠢的错误** — 文档的目的是让人查,文件名就是索引。把三个 struct 塞进一个 `ItemDataStructures.md`,等于给后人埋坑。 2. **"设计用意"和"职责范围"不能合并** — 前者是历史/动机,后者是边界。维护者需要知道"为什么存在",也需要知道"管多宽"。 3. **自我编造示例代码** — 我写了一堆 `// 继承 AMyActor 创建自定义 Actor` 的伪代码。用户一眼看出这不是项目里的真实代码。 4. **罗列函数签名** — 对外接口章节写成了 API reference,而不是从调用者角度说明"你能干什么"。 5. **花式 ASCII 图** — `_relationships.md` 里搞了一堆 ASCII 艺术框图。用户要的是简洁的文件级表格。 ### 为什么这个流程可以复现 - **模板化**:每个文档的结构完全一致,没有自由发挥空间 - **可验证**:每条引用必须有文件路径和行号,grep 一下就能确认 - **分阶段**:4 个阶段每条有明确的输入和输出,不会出现循环依赖 - **工具支持**:Claude Code 的 Task 代理可以并行处理大量文件,MCP 可以查询二进制蓝图 ### 如果你没有 Claude Code 整个流程的核心逻辑不依赖 AI 工具。你可以纯手工完成: 1. 在 Excel/Notion 里建一张表,列出所有单位及其分析数据(步骤 2) 2. 按模板逐个创建 .md 文件(步骤 3-5) 3. 用 grep/VSCode 搜索来验证引用关系(步骤 7) 只是一个人做 56 个文档大概需要 3-5 个工作日。 ### 后续维护 代码改动后,更新对应文档: - 新增类:按模板创建新 .md,更新 `_relationships.md` 和 `README.md` - 删除类:删除对应 .md,更新 `_relationships.md` 和 `README.md` - 修改接口:更新"对外接口"章节和对应的"使用方法"引用 - 修改依赖:更新"项目内依赖"表和 `_relationships.md`