Files
loneseDocument/HOWTO_REGENERATE_DOCS.md
meishibiezb 29a3f77908 init
2026-06-04 21:44:13 +08:00

15 KiB
Raw Blame History

文档生成操作手册

这篇文章是干什么的

当你需要更新这个项目的文档,或者想把同一套方法套到另一个 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 文件,严格以下结构:

# [单位名称]

## 基本信息
- **类型**: 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 项目上复现整个过程的操作步骤。

前置条件

  1. Claude Code CLI(本文使用的 AI 编程助手)
    • 安装方式见 Claude Code 官方文档
    • 关键是它支持 Task 工具(可以启动后台子代理并行干活)
    • 支持 MCP用于查询 UE 编辑器中的蓝图信息)
  2. UnrealClaude MCP 插件 — 安装在项目的 Plugins/UnrealClaude/
    • 提供 unreal_blueprint_queryunreal_asset_search 等 MCP 工具
    • 用来查询蓝图资产信息(因为在 .uasset 二进制文件上没法直接 grep
  3. ripgrep (rg) — 用于在源代码中搜索单位引用
  4. bash shell — Windows 上用 Git Bash 或 WSLmacOS/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 个问题(详见第一章)。核心批评:

  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.mdREADME.md
  • 删除类:删除对应 .md更新 _relationships.mdREADME.md
  • 修改接口:更新"对外接口"章节和对应的"使用方法"引用
  • 修改依赖:更新"项目内依赖"表和 _relationships.md