15 KiB
文档生成操作手册
这篇文章是干什么的
当你需要更新这个项目的文档,或者想把同一套方法套到另一个 UE 项目时,看这个就够了。
本文记录:
- 踩过的坑 — 初版被骂的 10 项问题,别再犯
- 正确的文档模板 — 每个单位应该长什么样
- 完整操作步骤 — 从零到 56 个文档是怎么生成的
- 工具和命令 — 用了什么、怎么用
一、血泪教训:初版的 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 文件,严格以下结构:
# [单位名称]
## 基本信息
- **类型**: 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)
每个模块目录下一个:
# [模块名] 依赖关系
## 文件间引用关系
| 源文件 | 引用方式 | 目标文件 | 目标单位 |
|--------|---------|---------|---------|
| XXX.h | #include | YYY.h | YYY |
| XXX.h | forward-declare | — | ZZZ |
## 关键依赖链
[用简单的文本箭头描述关键数据/控制流,如:
DefaultContainer.cpp → ItemFactory::CreateItemInstance → FItemInstance
]
2.3 README.md 模板
见 Document/README.md,核心是每个模块的完整单位列表表,含:单位名、文件名(链接)、类型、父类。
三、完整操作步骤
以下是在一个全新的 UE 项目上复现整个过程的操作步骤。
前置条件
- Claude Code CLI(本文使用的 AI 编程助手)
- 安装方式见 Claude Code 官方文档
- 关键是它支持 Task 工具(可以启动后台子代理并行干活)
- 支持 MCP(用于查询 UE 编辑器中的蓝图信息)
- UnrealClaude MCP 插件 — 安装在项目的
Plugins/UnrealClaude/下- 提供
unreal_blueprint_query、unreal_asset_search等 MCP 工具 - 用来查询蓝图资产信息(因为在 .uasset 二进制文件上没法直接 grep)
- 提供
- ripgrep (
rg) — 用于在源代码中搜索单位引用 - bash shell — Windows 上用 Git Bash 或 WSL,macOS/Linux 直接用
步骤 1:确定文档化范围
先画出要文档化的所有单位清单:
# 列出所有需要文档化的 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 的做法:
对 Claude Code 说:
"在 Document/Plugins/Item/ 下为以下每个单位创建独立的 .md 文件,
遵循 [粘贴模板]。每个单位一个文件,不要合并。
现在只填基本信息、功能概述、设计用意、职责范围、项目内依赖。
对外接口、使用方法、用例三个章节写'(待阶段3填写)'。"
然后把每个单位的结构化分析数据附在后面。
注意:
- 每个单位必须独立成文件,绝对不能合并
- "设计用意"和"职责范围"是两个独立章节,不要偷懒合并
- "职责范围"用自然语言,禁止出现"职责:/不负责:"这种二分写法
- 依赖表列名必须是
依赖项 | 关系 | 源文件
手动做法:
对清单中的每个单位:
1. 创建 Document/[模块]/[单位名].md 文件
2. 从步骤 2 的分析结果中抄入:基本信息、功能概述、设计用意、职责范围、依赖
3. 最后三节写 "(待阶段3填写)"
步骤 4:阶段 2 — 生成关系文档
基于阶段 1 的依赖数据,为每个模块创建 _relationships.md。
用 Claude Code 的做法:
对 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<FItemView>"(这是罗列签名)
对每个蓝图单位:
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 的做法:
对 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 搜索命令
# 搜索某个类/结构体在项目中的使用
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 个问题(详见第一章)。核心批评:
- 合并文件是最愚蠢的错误 — 文档的目的是让人查,文件名就是索引。把三个 struct 塞进一个
ItemDataStructures.md,等于给后人埋坑。 - "设计用意"和"职责范围"不能合并 — 前者是历史/动机,后者是边界。维护者需要知道"为什么存在",也需要知道"管多宽"。
- 自我编造示例代码 — 我写了一堆
// 继承 AMyActor 创建自定义 Actor的伪代码。用户一眼看出这不是项目里的真实代码。 - 罗列函数签名 — 对外接口章节写成了 API reference,而不是从调用者角度说明"你能干什么"。
- 花式 ASCII 图 —
_relationships.md里搞了一堆 ASCII 艺术框图。用户要的是简洁的文件级表格。
为什么这个流程可以复现
- 模板化:每个文档的结构完全一致,没有自由发挥空间
- 可验证:每条引用必须有文件路径和行号,grep 一下就能确认
- 分阶段:4 个阶段每条有明确的输入和输出,不会出现循环依赖
- 工具支持:Claude Code 的 Task 代理可以并行处理大量文件,MCP 可以查询二进制蓝图
如果你没有 Claude Code
整个流程的核心逻辑不依赖 AI 工具。你可以纯手工完成:
- 在 Excel/Notion 里建一张表,列出所有单位及其分析数据(步骤 2)
- 按模板逐个创建 .md 文件(步骤 3-5)
- 用 grep/VSCode 搜索来验证引用关系(步骤 7)
只是一个人做 56 个文档大概需要 3-5 个工作日。
后续维护
代码改动后,更新对应文档:
- 新增类:按模板创建新 .md,更新
_relationships.md和README.md - 删除类:删除对应 .md,更新
_relationships.md和README.md - 修改接口:更新"对外接口"章节和对应的"使用方法"引用
- 修改依赖:更新"项目内依赖"表和
_relationships.md