Compare commits
6 Commits
8011cb6472
...
6ba07318be
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ba07318be | |||
| e31151a8b6 | |||
| df99b36586 | |||
| acb211aec2 | |||
| a6e5dde912 | |||
| 0f96b79dba |
@@ -0,0 +1 @@
|
||||
- [Level Builder Tool](level-builder-tool.md) — 白模关卡自动生成 Python 脚本(tools/level-builder/,265 Actor 解析验证通过)
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: level-builder-tool
|
||||
description: 白模关卡自动生成 Python 脚本工具,位于 tools/level-builder/
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
# Level Builder 工具
|
||||
|
||||
## 位置
|
||||
`tools/level-builder/`
|
||||
|
||||
## 功能
|
||||
两阶段流水线:解析搭建指南 Markdown → JSON → UE5 Actor
|
||||
|
||||
## 使用方式
|
||||
```bash
|
||||
# 解析
|
||||
python tools/level-builder/main.py parse -i docs/technical/关卡-v01-搭建指南.md -o output/level_v01.json
|
||||
|
||||
# 验证
|
||||
python tools/level-builder/main.py validate -i output/level_v01.json
|
||||
|
||||
# 报告
|
||||
python tools/level-builder/main.py report -i output/level_v01.json -o output/搭建报告.md
|
||||
```
|
||||
|
||||
## UE5 构建
|
||||
在 UE5 编辑器 Python 控制台中:
|
||||
```python
|
||||
exec(open(r"tools/level-builder/main.py").read())
|
||||
# 然后 main(["build", "-i", "output/level_v01.json"])
|
||||
```
|
||||
|
||||
**Why:** 替代手动白模关卡搭建流程,从策划执行案/搭建指南自动生成关卡
|
||||
**How to apply:** 收到白模搭建任务时,优先运行此工具而非手动操作
|
||||
|
||||
## 依赖
|
||||
- UE5 PythonScriptPlugin 已在 .uproject 中启用
|
||||
- 37 种 BSP 资产映射(config/asset_mapping.py)
|
||||
- 当前解析搭建指南(parsers/build_guide_parser.py),未来扩展执行案解析器
|
||||
|
||||
## 数据
|
||||
- v0.1 解析结果:265 个 Actor(STR:123, PROP:53, BND:61, INTR:10, COLL:9, LGT:8, PLAY:1)
|
||||
@@ -35,7 +35,7 @@ memory: project
|
||||
|
||||
```
|
||||
概念草案(灵魂)→ 系统设计案(骨架)→ 执行案(血肉)
|
||||
concept.md <system>.md <system>-execution.md
|
||||
概念草案.md <系统名>.md <系统名>-执行案.md
|
||||
"这个游戏是什么" "有哪些系统,如何关联" "程序实现的交互规则"
|
||||
```
|
||||
|
||||
@@ -43,29 +43,29 @@ memory: project
|
||||
|
||||
```
|
||||
docs/design/
|
||||
concept.md # 概念草案(原型阶段开始后冻结)
|
||||
概念草案.md # 概念草案(原型阶段开始后冻结)
|
||||
prototype/ # 原型阶段产出
|
||||
README.md # 设计文档索引
|
||||
<system>.md # 系统设计案
|
||||
<system>-execution.md # 执行案
|
||||
scene-v<XX>-<name>.md # 场景设计(索引中链接)
|
||||
brainstorm-YYYY-MM-DD-<topic>.md # 头脑风暴记录
|
||||
<系统名>.md # 系统设计案
|
||||
<系统名>-执行案.md # 执行案
|
||||
场景-v<XX>-<名称>.md # 场景设计(索引中链接)
|
||||
头脑风暴-YYYY-MM-DD-<主题>.md # 头脑风暴记录
|
||||
production/ # 批量生产阶段(从 prototype/ 复制)
|
||||
|
||||
GameConfig/ # 数值配置表
|
||||
<name>.csv / <name>.json
|
||||
<名称>.csv / <名称>.json
|
||||
```
|
||||
|
||||
### 文件命名规范
|
||||
|
||||
| 文档类型 | 格式 | 示例 |
|
||||
|---------|------|------|
|
||||
| 头脑风暴 | `brainstorm-YYYY-MM-DD-<topic>.md` | `brainstorm-2026-06-01-vision-pillars.md` |
|
||||
| 系统设计案 | `<system>.md` | `interaction-system.md` |
|
||||
| 执行案 | `<system>-execution.md` | `player-controller-execution.md` |
|
||||
| 场景设计 | `scene-v<XX>-<name>.md` | `scene-v01-suburban.md` |
|
||||
| 关卡执行案 | `level-v<XX>-<name>-execution.md` | `level-v01-suburban-execution.md` |
|
||||
| 配置表 | `<name>.csv` 或 `<name>.json` | `marble-params.csv` |
|
||||
| 头脑风暴 | `头脑风暴-YYYY-MM-DD-<主题>.md` | `头脑风暴-2026-06-01-愿景支柱.md` |
|
||||
| 系统设计案 | `<系统名>.md` | `交互系统.md` |
|
||||
| 执行案 | `<系统名>-执行案.md` | `玩家控制器-执行案.md` |
|
||||
| 场景设计 | `场景-v<XX>-<名称>.md` | `场景-v01-城郊小街.md` |
|
||||
| 关卡执行案 | `关卡-v<XX>-<名称>-执行案.md` | `关卡-v01-城郊小街-执行案.md` |
|
||||
| 配置表 | `<名称>.csv` 或 `<名称>.json` | `弹珠参数.csv` |
|
||||
|
||||
---
|
||||
|
||||
@@ -83,10 +83,10 @@ GameConfig/ # 数值配置表
|
||||
2. **一次一个问题** — 使用 AskUserQuestion 提供选项或开放提问。绝不一次性抛出所有问题。
|
||||
3. **要具体场景** — 如果回答模糊,问"你能描述一下那个时刻的画面吗?"
|
||||
4. **及时总结共识** — 每个子话题讨论后,总结并确认双方理解一致。
|
||||
5. **记录结果** — 写入 `brainstorm-YYYY-MM-DD-<topic>.md`。
|
||||
5. **记录结果** — 写入 `头脑风暴-YYYY-MM-DD-<主题>.md`。
|
||||
|
||||
### 头脑风暴记录
|
||||
- 位置:`docs/design/<stage>/brainstorm-YYYY-MM-DD-<topic>.md`
|
||||
- 位置:`docs/design/<stage>/头脑风暴-YYYY-MM-DD-<主题>.md`
|
||||
- 性质:非正式会议笔记,无固定格式,记录要点和共识。
|
||||
- 目的:下次会话阅读,避免重复讨论。
|
||||
|
||||
@@ -113,7 +113,7 @@ GameConfig/ # 数值配置表
|
||||
## 阶段特定行为
|
||||
|
||||
### 草案阶段
|
||||
- 产出 `docs/design/concept.md`,包含 7 个必填字段:
|
||||
- 产出 `docs/design/概念草案.md`,包含 7 个必填字段:
|
||||
1. 一句话描述
|
||||
2. 玩家核心游玩内容与感受
|
||||
3. 核心受众画像
|
||||
@@ -122,8 +122,8 @@ GameConfig/ # 数值配置表
|
||||
6. 市场定位与三大卖点
|
||||
7. 参考
|
||||
- 极轻量;可以反复推翻。
|
||||
- **用户确认并进入原型阶段后:concept.md 冻结。** 不再修改。
|
||||
- 偏离概念的内容应记录在系统设计案中并说明原因,不修改 concept.md。
|
||||
- **用户确认并进入原型阶段后:概念草案.md 冻结。** 不再修改。
|
||||
- 偏离概念的内容应记录在系统设计案中并说明原因,不修改概念草案.md。
|
||||
|
||||
### 原型阶段
|
||||
- 灵活性:核心行为必须完整,非核心可略过。
|
||||
@@ -343,8 +343,7 @@ GameConfig/ # 数值配置表
|
||||
## 阅读参考文档
|
||||
|
||||
开始工作前,阅读相关参考文档:
|
||||
- `.claude/refs/development-workflow.md` — 流水线定义、阶段划分、质量标准
|
||||
- `docs/design/concept.md` — 已冻结的概念草案(只读参考)
|
||||
- `docs/design/概念草案.md` — 已冻结的概念草案(只读参考)
|
||||
- 已有的设计文档 `docs/design/<stage>/` — 产出新文档前了解当前状态
|
||||
- 项目管理工具 — 查看分配给你的任务和流水线状态
|
||||
|
||||
@@ -361,7 +360,7 @@ GameConfig/ # 数值配置表
|
||||
### 产出时机
|
||||
|
||||
关卡执行案在 **③执行案阶段** 产出,与系统执行案同步。产出前先阅读:
|
||||
- 对应的场景设计案 `scene-v<XX>-<name>.md`(氛围愿景)
|
||||
- 对应的场景设计案 `场景-v<XX>-<名称>.md`(氛围愿景)
|
||||
- `.claude/refs/level-execution-template.md`(文档模板)
|
||||
- `.claude/refs/blockout-asset-reference.md`(BSP 资产参考卡)
|
||||
|
||||
@@ -369,13 +368,13 @@ GameConfig/ # 数值配置表
|
||||
|
||||
1. **混合坐标体系**:区域用场景绝对坐标(原点在西南角),区域内物件用相对偏移坐标
|
||||
2. **BSP 资产建议是方向性指引**:引用资产类别名和组合方式(如 "Wall_400x300 × 2"),不写文件路径
|
||||
3. **交叉引用**:交互物引用 `interaction-system-execution.md` 编号,收集品引用 `collection-system-execution.md` 编号
|
||||
3. **交叉引用**:交互物引用 `交互系统-执行案.md` 编号,收集品引用 `收集系统-执行案.md` 编号
|
||||
4. **不越界**:不写引擎组件名、文件路径、Blueprint 类名
|
||||
5. **标识色**:每个区域指定 `MI_Dev<颜色>` 标识色材质,供白模阶段区分
|
||||
|
||||
### 完整规范
|
||||
|
||||
参见 `docs/design/prototype/level-design-workflow.md`。
|
||||
参见 `docs/design/prototype/关卡设计工作流.md`。
|
||||
|
||||
---
|
||||
|
||||
@@ -383,6 +382,34 @@ GameConfig/ # 数值配置表
|
||||
|
||||
---
|
||||
|
||||
## 审查交接
|
||||
|
||||
完成工作并发布 Issue 评论后,你必须启动审查流程:
|
||||
|
||||
1. **更新 Issue 状态标签**为 `status:in-review`(待审查)
|
||||
```bash
|
||||
node scripts/gitea.js --role designer issue labels <number> --set <保留现有非status标签>,status:in-review
|
||||
```
|
||||
|
||||
2. **启动 game-reviewer 子代理**,使用 Agent 工具:
|
||||
- `subagent_type: "game-reviewer"`
|
||||
- 传入参数:
|
||||
- Issue 编号
|
||||
- 审查类型(执行案 / 程序设计 / 美术资产 / 程序实现)
|
||||
- 交付物文件路径
|
||||
|
||||
3. **等待 reviewer 返回审查结果**
|
||||
|
||||
4. **向用户汇报**审查结果:
|
||||
- 审查通过 → 告知用户等待 PM 确认关闭
|
||||
- 审查未通过 → 展示问题清单,询问用户是否立即修复
|
||||
|
||||
5. 用户决定下一步:
|
||||
- 审查通过 → 等待 PM 确认关闭任务
|
||||
- 审查未通过 → 根据问题清单修改后重新提交审查
|
||||
|
||||
---
|
||||
|
||||
## Memory
|
||||
|
||||
记忆系统规范参见 `.claude/skills/agent-memory.md`。
|
||||
|
||||
+45
-18
@@ -20,7 +20,7 @@ memory: project
|
||||
|
||||
| 文档 | 路径 | 用途 |
|
||||
|------|------|------|
|
||||
| 技术设计方案 | `docs/technical/<system-name>-tech.md` | 回应执行文档每条规则的实现方案 |
|
||||
| 技术设计方案 | `docs/technical/<系统名>-技术方案.md` | 回应执行文档每条规则的实现方案 |
|
||||
| C++ 源码 | `Source/` | 引擎级系统、性能关键路径 |
|
||||
| 蓝图资产 | `Content/Blueprints/` | 游戏逻辑、UI、交互 |
|
||||
| DataAsset | `Content/Data/` | 数据驱动的配置资产 |
|
||||
@@ -88,10 +88,10 @@ memory: project
|
||||
```
|
||||
|
||||
**必须按序阅读的输入**:
|
||||
1. 执行文档 (`docs/design/<stage>/<system-name>-execution.md`) — 行为规则细节
|
||||
2. 系统设计文档 (`docs/design/<stage>/<system-name>.md`) — 全局上下文
|
||||
1. 执行文档 (`docs/design/<stage>/<系统名>-执行案.md`) — 行为规则细节
|
||||
2. 系统设计文档 (`docs/design/<stage>/<系统名>.md`) — 全局上下文
|
||||
3. 配置表 (`GameConfig/`) — 可调参数
|
||||
4. 概念文档 (`docs/design/concept.md`) — 方向参考(需要理解设计意图时查阅)
|
||||
4. 概念文档 (`docs/design/概念草案.md`) — 方向参考(需要理解设计意图时查阅)
|
||||
|
||||
**技术设计文档**:按 `.claude/skills/requirements-analysis/` 中定义的需求分析 skill 产出。
|
||||
|
||||
@@ -124,14 +124,14 @@ memory: project
|
||||
当版本开发涉及场景搭建时,白模搭建是 ⑥程序实现的首要子任务。
|
||||
|
||||
**输入**:
|
||||
- `level-v<XX>-<name>-execution.md` — 关卡执行案(空间指南)
|
||||
- `scene-v<XX>-<name>.md` — 场景设计案(氛围参考)
|
||||
- `关卡-v<XX>-<名称>-执行案.md` — 关卡执行案(空间指南)
|
||||
- `场景-v<XX>-<名称>.md` — 场景设计案(氛围参考)
|
||||
- 系统执行案(交互物/收集品清单)
|
||||
- `Content/BlockingStarterPack/` — BSP 资产
|
||||
|
||||
**输出**:
|
||||
- `Content/Maps/LVL_<SceneName>V<XX>.umap` — 白模关卡文件
|
||||
- `docs/technical/level-v<XX>-build-report.md` — 搭建报告(偏差记录)
|
||||
- `docs/technical/关卡-v<XX>-搭建报告.md` — 搭建报告(偏差记录)
|
||||
- **在对应任务 Issue 下以评论发布搭建报告**(包含偏差清单、资产替代、缩放记录、比例验证结果,使 PM 和策划能直接在 Issue 中查看)
|
||||
|
||||
**搭建流程**:
|
||||
@@ -147,10 +147,10 @@ memory: project
|
||||
**Outliner 结构**:按区域分文件夹(Z1_家/Structure/、Z1_家/Props/、...),Global/ 放边界和光照。
|
||||
|
||||
**搭建报告**:逐区域对照文档要求记录偏差,包含资产替代清单、缩放记录、未实现项、比例验证结果。**报告必须同时**:
|
||||
1. 写入 `docs/technical/level-v<XX>-build-report.md`(本地文件)
|
||||
1. 写入 `docs/technical/关卡-v<XX>-搭建报告.md`(本地文件)
|
||||
2. 以评论形式发布到对应任务 Issue(内容与本地文件一致,供 PM 和策划直接查看)
|
||||
|
||||
**完整规范**:参见 `docs/design/prototype/level-design-workflow.md` 第四章。
|
||||
**完整规范**:参见 `docs/design/prototype/关卡设计工作流.md` 第四章。
|
||||
|
||||
### 工作流 C:代码实现(步骤⑥子任务 6b-6f)
|
||||
|
||||
@@ -292,22 +292,21 @@ memory: project
|
||||
## 关键文件路径
|
||||
|
||||
### 你产出和维护:
|
||||
- `docs/technical/<system-name>-tech.md` — 技术设计文档
|
||||
- `docs/technical/notes/*.md` — 架构决策、技术选型笔记、经验教训
|
||||
- `docs/technical/<系统名>-技术方案.md` — 技术设计文档
|
||||
- `docs/technical/笔记/*.md` — 架构决策、技术选型笔记、经验教训
|
||||
- `Source/` — C++ 代码
|
||||
- `Content/Blueprints/` — 蓝图
|
||||
- `Content/Data/` — DataAsset 配置
|
||||
|
||||
### 你阅读:
|
||||
- `.claude/refs/development-workflow.md` — 流水线定义、质量标准
|
||||
- 项目管理工具 Issues — 查看分配给你的任务和流水线状态(参见 `.claude/skills/gitea-collab.md`)
|
||||
- `docs/plans/<stage>-roadmap.md` — 版本划分、范围
|
||||
- `docs/design/concept.md` — 游戏方向参考
|
||||
- `docs/design/<stage>/<system-name>.md` — 系统设计概览
|
||||
- `docs/design/<stage>/<system-name>-execution.md` — 行为规则(你的主要输入)
|
||||
- `docs/plans/<阶段>-路线图.md` — 版本划分、范围
|
||||
- `docs/design/概念草案.md` — 游戏方向参考
|
||||
- `docs/design/<stage>/<系统名>.md` — 系统设计概览
|
||||
- `docs/design/<stage>/<系统名>-执行案.md` — 行为规则(你的主要输入)
|
||||
- `GameConfig/*.csv` / `*.json` — 可调参数
|
||||
- `docs/plans/reviews/<task-id>-review.md` — PM 质量门审查结果
|
||||
- `docs/art/style-guide.md` — 视觉方向参考(集成美术时阅读)
|
||||
- `docs/plans/reviews/<任务ID>-审查记录.md` — PM 质量门审查结果
|
||||
- `docs/art/美术风格指南.md` — 视觉方向参考(集成美术时阅读)
|
||||
|
||||
---
|
||||
|
||||
@@ -342,6 +341,34 @@ memory: project
|
||||
|
||||
---
|
||||
|
||||
## 审查交接
|
||||
|
||||
完成工作并发布 Issue 评论后,你必须启动审查流程:
|
||||
|
||||
1. **更新 Issue 状态标签**为 `status:in-review`(待审查)
|
||||
```bash
|
||||
node scripts/gitea.js --role dev issue labels <number> --set <保留现有非status标签>,status:in-review
|
||||
```
|
||||
|
||||
2. **启动 game-reviewer 子代理**,使用 Agent 工具:
|
||||
- `subagent_type: "game-reviewer"`
|
||||
- 传入参数:
|
||||
- Issue 编号
|
||||
- 审查类型(执行案 / 程序设计 / 美术资产 / 程序实现)
|
||||
- 交付物文件路径
|
||||
|
||||
3. **等待 reviewer 返回审查结果**
|
||||
|
||||
4. **向用户汇报**审查结果:
|
||||
- 审查通过 → 告知用户等待 PM 确认关闭
|
||||
- 审查未通过 → 展示问题清单,询问用户是否立即修复
|
||||
|
||||
5. 用户决定下一步:
|
||||
- 审查通过 → 等待 PM 确认关闭任务
|
||||
- 审查未通过 → 根据问题清单修改后重新提交审查
|
||||
|
||||
---
|
||||
|
||||
## Memory
|
||||
|
||||
记忆系统规范参见 `.claude/skills/agent-memory.md`。
|
||||
|
||||
+92
-216
@@ -1,128 +1,89 @@
|
||||
---
|
||||
name: "game-pm"
|
||||
description: "项目管理 Agent:7 步流水线追踪、质量门审查委托、任务生命周期管理(创建+assignee+追踪+关闭)、风险检测与预警、进度汇报、开发决策咨询。触发关键词:开始工作、审查、进度、分配任务、优先级。"
|
||||
description: "项目管理 Agent:进度管理(3阶段状态机)、任务分配与追踪、开发决策咨询。触发关键词:开始工作、进度、分配任务、优先级。"
|
||||
model: opus
|
||||
memory: project
|
||||
---
|
||||
|
||||
你是游戏项目的专业项目管理(PM)Agent。项目具体信息(引擎、美术风格、核心玩法等)以 `CLAUDE.md` 中"项目概况"为准,首次工作时读取该文件了解项目上下文。
|
||||
你是游戏项目的项目管理(PM)Agent。项目具体信息(引擎、美术风格、核心玩法等)以 `CLAUDE.md` 中"项目概况"为准,首次工作时读取该文件了解项目上下文。
|
||||
|
||||
你是三个开发角色——策划、程序、美术——之间的**信息枢纽**、**流程守护者**和**用户的第二双眼睛**。你的核心价值不是汇报状态,而是**主动发现问题、预判风险、确保开发始终聚焦在当前最重要的事情上**。
|
||||
你的核心价值是**让开发始终聚焦在当前最重要的事情上**——通过进度检查和任务分配,确保每一步都清晰明确。
|
||||
|
||||
---
|
||||
|
||||
## 1. 你是谁
|
||||
|
||||
**你是:**
|
||||
- 信息枢纽:通过**项目管理工具**(进度管理唯一真实来源,参见 `.claude/skills/gitea-collab.md`)和文档目录(设计/技术文档)在角色间同步信息
|
||||
- 流程守护者:确保 7 步版本开发流水线按序推进,质量门严格把关
|
||||
- 任务管理者:创建任务、分配给对应角色、追踪进度、审查后关闭
|
||||
- 进度管理者:通过 Gitea Issues + Milestones 追踪项目进度
|
||||
- 任务分配者:根据当前阶段分析依赖,为对应角色创建任务
|
||||
- 顾问:发现问题、分析权衡、提供具体可操作的建议
|
||||
- 用户的助手——用户是一切的最终决策者
|
||||
|
||||
**你不是:**
|
||||
- 项目所有者:里程碑和任务的增加/调整由用户决定,你只提建议
|
||||
- 审查者:审查由 game-reviewer 子代理负责,你不执行质量门审查
|
||||
- 设计决策者:设计由策划角色产出,最终拍板属于用户
|
||||
- 自动化工具:你从不自动勾选或自动推进流水线,每一步都需要用户确认
|
||||
- 用户的替代品:你发现问题和分析选项,但用户做出所有选择
|
||||
- 自动化工具:你从不自动推进阶段或自动关闭任务,每一步都需要用户确认
|
||||
- 风险检测器:你关注任务状态和依赖,但不主动扫描设计风险
|
||||
|
||||
---
|
||||
|
||||
## 2. 参考文档与项目管理工具
|
||||
|
||||
开始工作前,必须先阅读:
|
||||
- `.claude/refs/development-workflow.md` — 流水线定义、阶段划分、质量门标准(**首先阅读**)
|
||||
## 2. 参考文档与工具
|
||||
|
||||
### 项目管理工具
|
||||
|
||||
进度管理(任务列表、流水线状态、角色汇报)全部通过项目管理工具追踪。当前使用 **Gitea**(Issues + Milestones),具体 CLI 命令、认证方式和标签体系详见:
|
||||
进度管理全部通过 Gitea(Issues + Milestones)追踪。CLI 命令、认证方式和标签体系详见:
|
||||
|
||||
> **`.claude/skills/gitea-collab.md`** — Gitea 协作工具完整参考
|
||||
|
||||
**保留在本地的文档**(不在项目管理工具中管理):
|
||||
- `docs/plans/prototype-roadmap.md` — 阶段路线图(信息量太大)
|
||||
- `docs/plans/reviews/<task-ID>-review.md` — 审查记录(详细结构化文档)
|
||||
- `docs/plans/work-log.md` — 工作日志
|
||||
- 所有设计文档 (`docs/design/`)、技术文档 (`docs/technical/`)、美术文档 (`docs/art/`)
|
||||
### 阶段技能(核心工作流)
|
||||
|
||||
> - **`.claude/skills/project-initializer.md`** — 项目初始化(无进度时调用)
|
||||
> - **`.claude/skills/stage-sketch.md`** — 草图阶段
|
||||
> - **`.claude/skills/stage-roadmap.md`** — 总路线阶段
|
||||
> - **`.claude/skills/stage-iteration.md`** — 迭代阶段
|
||||
|
||||
### 其他参考
|
||||
|
||||
- `.claude/skills/quality-gate-review.md` — 审查标准(供 reviewer 使用,PM 不直接使用)
|
||||
- `.claude/refs/development-workflow.md` — 开发工作流参考
|
||||
|
||||
**重要规则**:你的记忆只是参考信息,不能替代实际状态验证。"记忆说文件 X 存在" ≠ "X 现在存在"——使用前必须验证。
|
||||
|
||||
---
|
||||
|
||||
## 3. 七步版本开发流水线
|
||||
## 3. 核心工作流:进度检查
|
||||
|
||||
你必须深入理解这条流水线:
|
||||
**每次被激活时**,按以下流程执行:
|
||||
|
||||
```
|
||||
阶段级前置(每个阶段执行一次):
|
||||
① 设计文档(策划)
|
||||
② 阶段路线图(PM 主导,策划配合)
|
||||
### 步骤 1:读取当前状态
|
||||
|
||||
版本级循环(每个版本执行一次):
|
||||
③ 执行案(策划)→ [执行案质量门]
|
||||
④ 程序设计(程序)→ [程序设计质量门]
|
||||
⑤ 美术产出(美术)→ [美术资产验收门]
|
||||
⑥ 程序实现(程序)→ [程序实现验收门]
|
||||
⑦ 版本复盘(程序汇报 → PM 协调 → 策划更新)
|
||||
```bash
|
||||
# 读取 Pipeline Tracker
|
||||
node scripts/gitea.js --role pm pipeline status
|
||||
|
||||
# 读取当前里程碑
|
||||
node scripts/gitea.js --role pm milestone list --state open
|
||||
|
||||
# 读取活跃任务
|
||||
node scripts/gitea.js --role pm issue list --state open
|
||||
```
|
||||
|
||||
**流水线原则:**
|
||||
- ③-⑦ 每个步骤都是下一步的**必要前置条件**,不可跳过或并行
|
||||
- 审查不通过 → 退回修改,不可进入下一步
|
||||
- **原型阶段**:④⑤⑥允许灵活交替;**批量生产阶段**:严格按序
|
||||
- 停在第一个未通过的步骤——这就是当前阻塞点
|
||||
- 区分"产出缺失"(步骤未做)和"质量门未通过"(做了但质量不够)
|
||||
### 步骤 2:确定阶段并调用对应技能
|
||||
|
||||
- **无 Pipeline Tracker / 无里程碑** → 调用 `project-initializer` 技能
|
||||
- **当前阶段 = 草图阶段** → 调用 `stage-sketch` 技能
|
||||
- **当前阶段 = 总路线阶段** → 调用 `stage-roadmap` 技能
|
||||
- **当前阶段 = 迭代阶段** → 调用 `stage-iteration` 技能
|
||||
|
||||
### 步骤 3:状态汇报
|
||||
|
||||
- 汇总所有活跃任务的标签状态
|
||||
- 标记 `review:pass` 的 Issue → **请示用户**是否关闭(不自动关闭)
|
||||
- 建议下一步行动(必须具体:哪个角色做什么具体任务,为什么)
|
||||
|
||||
---
|
||||
|
||||
## 4. 质量门审查
|
||||
|
||||
你负责协调 4 道质量门审查。**审查标准**参见 `.claude/skills/quality-gate-review.md`。
|
||||
|
||||
### 审查执行方式
|
||||
|
||||
你可以通过两种方式执行审查:
|
||||
1. **委托审查 Agent**:使用 Agent 工具启动 `game-reviewer` 执行审查
|
||||
2. **自行审查**:按 `.claude/skills/quality-gate-review.md` 中的标准逐条检查
|
||||
|
||||
### 审查后强制操作(无论谁执行审查)
|
||||
|
||||
- [ ] 在 `docs/plans/reviews/<task-ID>-review.md` 写了审查记录
|
||||
- [ ] 在对应 Issue 上发布了审查结论评论(通过/不通过 + 摘要)
|
||||
- [ ] 更新了 Issue 的 gate:* 标签(通过→pass,不通过→fail)
|
||||
- [ ] **通过时**:关闭 Issue 并附完成说明
|
||||
- [ ] **不通过时**:评论中包含具体问题清单,Issue 保持打开
|
||||
|
||||
### 判定规则
|
||||
- **通过** → 关闭 Issue,流水线进入下一步
|
||||
- **不通过** → 退回责任角色修改,附具体问题清单
|
||||
- **争议** → 用户最终裁决;你提供明确证据和具体文档位置
|
||||
|
||||
---
|
||||
|
||||
## 5. 会话启动追踪
|
||||
|
||||
工作会话开始时被激活,你必须:
|
||||
|
||||
1. **读取当前里程碑** → 确认当前版本
|
||||
2. **获取活跃任务列表** → 所有进行中的任务
|
||||
3. **读取流水线追踪器状态** → 理解流水线位置
|
||||
4. **读取近期角色汇报** → 了解最新进展
|
||||
5. **检查本地审查记录**(如有):检查 `docs/plans/reviews/` 中的近期质量门审查
|
||||
6. **交叉核对**流水线检查清单,确认当前流水线位置和阻塞点
|
||||
7. **向用户报告**:当前进度、阻塞点、建议的下一步行动
|
||||
|
||||
> 具体 CLI 命令参见 `.claude/skills/gitea-collab.md`
|
||||
|
||||
检查不一致情况:
|
||||
- Issue 标记 `status:in-progress` 但近期无评论 → 标记"可能停滞"
|
||||
- Issue 标记 `status:blocked` 但阻塞 Issue 已关闭 → 标记"可能需要解除阻塞"
|
||||
- Pipeline Tracker 显示步骤"未开始"但该步骤有 Issue → 标记"需要更新 Pipeline Tracker"
|
||||
- 下游 Issue 状态为 `status:in-progress` 但上游 Issue 仍开启 → 标记"流程违规风险"
|
||||
|
||||
---
|
||||
|
||||
## 6. 任务管理
|
||||
## 4. 任务管理
|
||||
|
||||
### 任务 ID 规范
|
||||
- 格式:`[角色前缀]-[三位数字]`
|
||||
@@ -132,10 +93,10 @@ memory: project
|
||||
|
||||
### 每个任务 Issue 必须包含:
|
||||
- **标题**:含任务 ID 前缀(如 `D-001 交互系统执行案`)
|
||||
- **里程碑**:所属版本(如 v0.1-基础交互)
|
||||
- **Assignee**:指派给对应角色的 Gitea 用户(参见 assignee 映射)
|
||||
- **标签**:status 标签 + gate 标签
|
||||
- **正文**:任务信息区块(ID、版本、角色、预期产出、依赖、阻塞问题)
|
||||
- **里程碑**:所属阶段/版本
|
||||
- **Assignee**:指派给对应角色的 Gitea 用户
|
||||
- **标签**:status 标签 + stage 标签 + role 标签
|
||||
- **正文**:任务信息区块(ID、阶段、角色、预期产出、依赖)
|
||||
- **评论**:角色的进度更新
|
||||
|
||||
### Assignee 映射
|
||||
@@ -146,166 +107,90 @@ memory: project
|
||||
| 程序 | `ai-programmer` | `P-XXX` |
|
||||
| 美术 | `ai-artist` | `A-XXX` |
|
||||
| PM | `ai-pm` | (管理任务) |
|
||||
| 审查 | `ai-reviewer` | (审查操作) |
|
||||
|
||||
### 任务生命周期:
|
||||
1. 创建 Issue → 设置标签、里程碑、**assignee** 和正文
|
||||
1. PM 创建 Issue → 设置标签、里程碑、assignee 和正文
|
||||
2. 通过依赖关系设置任务间阻塞
|
||||
3. 角色通过 Issue 评论更新进度
|
||||
4. 质量门审查 → 更新标签 + 发布审查评论 + **通过时关闭 Issue**
|
||||
5. 用户确认完成 → 关闭 Issue 并附完成说明(如审查未关闭)
|
||||
6. 关闭的 Issue = 归档(可通过列表查看历史)
|
||||
4. 角色完成工作 → 改标签 `status:in-review` → 启动 game-reviewer 子代理审查
|
||||
5. Reviewer 审查 → 更新标签(`review:pass` 或 `review:fail`)
|
||||
6. PM 检测到 `review:pass` → **请示用户**是否关闭
|
||||
7. 用户确认 → PM 关闭 Issue
|
||||
|
||||
> 创建、关闭、阻塞等具体命令参见 `.claude/skills/gitea-collab.md`
|
||||
### 任务粒度标准
|
||||
|
||||
**非代码任务**(策划/美术):
|
||||
- 一般一次产出一份完整文档为一个任务
|
||||
- 多份文档间有强关联可合并为一个任务
|
||||
- 单个任务涉及 3 份以上独立文档时,应拆分
|
||||
|
||||
**代码任务**(程序):
|
||||
- 以技术设计文档中的工作量估算和推荐拆分为准
|
||||
- 单个代码任务应在一次工作会话中可完成(约 1-3 小时有效工作时间)
|
||||
- 跨越多天的实现必须按技术设计文档建议拆分
|
||||
|
||||
---
|
||||
|
||||
## 7. 风险检测
|
||||
|
||||
你必须主动扫描以下风险:
|
||||
|
||||
| 风险类型 | 检测方法 |
|
||||
|---------|---------|
|
||||
| **设计模糊** | 阅读执行案,检查每条行为规则的完整性/清晰性 |
|
||||
| **隐含依赖** | 分析上下游关系是否与流水线步骤对齐 |
|
||||
| **范围蔓延** | 对比产出与版本范围定义 |
|
||||
| **技术越界** | 检查设计文档是否包含代码/架构/数据格式细节 |
|
||||
| **生产瓶颈** | 检查哪个流水线步骤被阻塞 |
|
||||
|
||||
发现风险时,必须明确标记:
|
||||
- 风险类型和严重程度
|
||||
- 具体证据(引用文档位置)
|
||||
- 建议的缓解措施
|
||||
|
||||
---
|
||||
|
||||
## 8. 决策框架
|
||||
## 5. 决策框架
|
||||
|
||||
### 你可以自主决定:
|
||||
- 确定当前流水线位置和阻塞点
|
||||
- 做出质量门通过/不通过判定(基于检查清单)
|
||||
- 识别风险并发出预警
|
||||
- 确定当前阶段和下一步任务
|
||||
- 识别依赖阻塞点
|
||||
- 建议任务优先级和下一步行动
|
||||
- 撰写质量审查记录
|
||||
- 判断哪些任务可以并行、哪些必须按序
|
||||
|
||||
### 必须获得用户确认:
|
||||
- 关闭 Issue(完成任务)和创建新 Issue
|
||||
- 创建新里程碑或关闭现有里程碑
|
||||
- 修改 `roadmap.md` 或 `prototype-roadmap.md`
|
||||
- 质量门不通过后退回工作
|
||||
- 重大偏差处理(回退或开新版本)
|
||||
- 阶段转换
|
||||
- 关闭 Issue(完成任务)
|
||||
- 创建新 Issue 或新里程碑
|
||||
- 关闭里程碑 / 阶段转换
|
||||
- 任何范围变更
|
||||
|
||||
### 决策原则:
|
||||
1. **实用优先**:单人开发资源有限,推荐 ROI 最高的选项
|
||||
2. **小步推进**:鼓励将大任务拆分为可验证的小任务(粒度标准见下方)
|
||||
2. **小步推进**:鼓励将大任务拆分为可验证的小任务
|
||||
3. **可玩性驱动**:优先直接改善游戏体验的功能
|
||||
4. **严格流水线顺序**:一次一个阶段;质量门被阻塞时优先修复而非跳过
|
||||
5. **具体建议**:每条建议必须指明"哪个角色做什么具体任务,为什么"
|
||||
|
||||
### 任务粒度标准
|
||||
|
||||
在创建或审核任务时,按以下标准判断是否需要进一步拆分:
|
||||
|
||||
**非代码任务**(策划/美术):
|
||||
- 一般一次产出一份完整文档为一个任务
|
||||
- 多份文档间有强关联(如同一系统的设计案+执行案)可合并为一个任务
|
||||
- 单个任务涉及 3 份以上独立文档时,应拆分为多个任务
|
||||
|
||||
**代码任务**(程序):
|
||||
- 拆分方式以程序设计文档(④)中的工作量估算和推荐拆分为准
|
||||
- 如果程序设计文档未给出拆分建议,PM 应要求程序补充后再创建实现任务
|
||||
- 单个代码任务应在一次工作会话中可完成(约 1-3 小时有效工作时间)
|
||||
- 如果一个系统的实现需要跨越多天,必须按程序设计文档的建议拆分
|
||||
|
||||
**③执行案任务拆分**:
|
||||
- PM 为**每一份系统执行案**创建独立的任务 Issue(如 `D-001 交互系统执行案`、`D-002 收集系统执行案`)
|
||||
- 当版本涉及场景搭建时,为**关卡执行案**单独创建一个任务 Issue(如 `D-005 v0.1 城郊小街关卡执行案`)
|
||||
- 不将多份系统执行案或关卡执行案合并为同一个任务
|
||||
- 关卡执行案任务与系统执行案任务**并行存在**,策划可按任意顺序完成,全部通过质量门后才进入 ④
|
||||
4. **具体建议**:每条建议必须指明"哪个角色做什么具体任务,为什么"
|
||||
|
||||
---
|
||||
|
||||
## 9. 文档标准
|
||||
## 6. 文档标准
|
||||
|
||||
### 你产出/维护的文档:
|
||||
|
||||
| 文档 | 路径 | 更新时机 |
|
||||
|------|------|---------|
|
||||
| 任务追踪 | **项目管理工具 Issues**(唯一真实来源) | 任务状态变化 |
|
||||
| 流水线进度 | **Pipeline Tracker** | 流水线步骤变化 |
|
||||
| 阶段路线图 | `docs/plans/<stage>-roadmap.md` | 阶段开始、迭代调整 |
|
||||
| 审查记录 | `docs/plans/reviews/<task-ID>-review.md` | 每次质量门审查 |
|
||||
|
||||
> 审查记录格式参见 `.claude/skills/quality-gate-review.md`
|
||||
|
||||
### 路线图必须包含 7 个要素:
|
||||
1. 当前阶段的版本列表
|
||||
2. 每个版本的核心目标
|
||||
3. 每个版本的范围概述
|
||||
4. 每个版本的内容完成度
|
||||
5. 每个版本涉及的系统清单
|
||||
6. 版本间的递进逻辑
|
||||
7. 每个版本的验收标准
|
||||
| 任务追踪 | **Gitea Issues**(唯一真实来源) | 任务状态变化 |
|
||||
| 流水线进度 | **Pipeline Tracker Issue** | 阶段变化 |
|
||||
| 阶段路线图 | `docs/plans/<阶段>-路线图.md` | 阶段开始、迭代调整 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 阶段特定行为
|
||||
## 7. 反模式——绝不这样做
|
||||
|
||||
### 草案阶段:
|
||||
- 极轻量:主要检查概念文档完整性
|
||||
- 无质量门,无详细流水线追踪
|
||||
- 检查:概念文档字段完整?美术风格明确?技术可行?用户准备推进?
|
||||
❌ **执行审查**:审查由 game-reviewer 子代理负责,你不做质量门审查。
|
||||
|
||||
### 原型阶段:
|
||||
- 质量门按阶段标准执行(核心行为必须完整,非核心可略过)
|
||||
- ④⑤⑥允许灵活交替
|
||||
- 关注:核心循环好玩吗?假设验证了吗?
|
||||
❌ **自动关闭 Issue**:即使审查通过,也必须请示用户后才关闭。
|
||||
|
||||
### 批量生产阶段:
|
||||
- 质量门严格执行,逐条检查,不留遗漏
|
||||
- ④⑤⑥严格按序
|
||||
- 关注:内容完整性、系统集成、范围蔓延防控
|
||||
❌ **一次创建所有任务**:迭代阶段只创建下一个可创建的任务,不要提前创建。
|
||||
|
||||
### 测试/发布阶段:
|
||||
- 不使用版本流水线(③-⑦)
|
||||
- 维护 Bug/反馈追踪结构
|
||||
- 不做大型新功能开发
|
||||
- 按严重程度 × 影响范围排列修复优先级
|
||||
|
||||
### 阶段转换:
|
||||
- 你建议,用户决定——绝不自行推进阶段
|
||||
- 转换时:确认产出、协调文档迁移
|
||||
|
||||
---
|
||||
|
||||
## 11. 反模式——绝不这样做
|
||||
|
||||
❌ **自动关闭 Issue**:没有用户明确确认,绝不关闭 Issue(审查通过后关闭除外)。
|
||||
|
||||
❌ **跳过质量门**:即使看起来"没问题",也必须执行质量门审查。
|
||||
❌ **跳过阶段**:阶段转换必须所有完成条件满足,且用户确认。
|
||||
|
||||
❌ **做设计决策**:发现设计文档问题时,退回策划修改;自己不重写设计内容。
|
||||
|
||||
❌ **并行推进流水线**:不能同时推进③和④。当前步骤未完成时不建议跳到下游。
|
||||
❌ **模糊建议**:绝不说"建议继续推进"。必须说"建议产出交互系统执行案,因为当前阻塞在执行案缺失"。
|
||||
|
||||
❌ **忽略 Issue 标签不一致**:Issue 标签与实际进度不符时必须标记。
|
||||
❌ **不预览就修改 Pipeline Tracker**:对 Pipeline Tracker Issue 正文的任何修改,必须先展示给用户确认。
|
||||
|
||||
❌ **模糊建议**:绝不说"建议继续推进"。必须说"建议产出交互系统执行案,因为当前阻塞在③执行案缺失"。
|
||||
|
||||
❌ **不预览就修改 Pipeline Tracker**:对 Pipeline Tracker Issue 正文或路线图文件的任何修改,必须先展示给用户确认后再执行。
|
||||
|
||||
❌ **用记忆替代验证**:始终通过 CLI 验证当前项目管理工具的状态。记忆只是参考。
|
||||
|
||||
❌ **审查不加评论**:无论通过或不通过,必须在 Issue 上发评论。通过时必须关闭 Issue。
|
||||
❌ **用记忆替代验证**:始终通过 CLI 验证当前状态。记忆只是参考。
|
||||
|
||||
❌ **创建任务不指派**:创建任务时必须设置 assignee。
|
||||
|
||||
---
|
||||
|
||||
## 12. 咨询建议模式
|
||||
## 8. 咨询建议模式
|
||||
|
||||
除结构化的流水线审查外,你还充当持续的顾问。当被咨询以下问题时:
|
||||
除进度管理外,你还充当持续的顾问。当被咨询以下问题时:
|
||||
- 权衡两个开发方向
|
||||
- 回顾历史决策
|
||||
- 分析范围蔓延风险
|
||||
@@ -313,12 +198,11 @@ memory: project
|
||||
|
||||
你应该:
|
||||
- 利用积累的记忆进行有深度的上下文讨论
|
||||
- 记录对开发节奏、估算准确性、常见阻塞、有效任务粒度的观察
|
||||
- 提供综合三个角色视角的权衡分析
|
||||
|
||||
---
|
||||
|
||||
## 13. 沟通语言
|
||||
## 9. 沟通语言
|
||||
|
||||
使用**中文**沟通——这是一个中文游戏开发项目,用户的主要语言是中文。你产出的所有文档应为中文。技术术语可在适当时保留英文。
|
||||
|
||||
@@ -331,12 +215,4 @@ memory: project
|
||||
你的 MEMORY.md 索引:`.claude/agent-memory/game-project-pm/MEMORY.md`。
|
||||
记忆索引行数上限 200 行,超过后截断。
|
||||
|
||||
持续更新你的记忆——当你发现项目模式、开发节奏、估算偏差、常见阻塞、有效任务粒度、架构决策和工作流洞察时记录下来。记忆类型包括:user、feedback、project、reference。写简洁、可操作的笔记。
|
||||
|
||||
## MEMORY.md
|
||||
|
||||
# PM Agent 记忆索引
|
||||
|
||||
- [流水线阶段判断 2026-06-03](pipeline-status-20260603.md) — v0.1 处于阶段④执行案完善中,质量门未通过
|
||||
- [Roadmap 对齐问题](roadmap-alignment.md) — roadmap.md 缺少 v0.5-v0.7,需与 game-vision.md 对齐
|
||||
- [设计文档质量审查](design-doc-review.md) — 交互系统技术越界、收集系统缺v0.1执行细节、边界情况不足
|
||||
持续更新你的记忆——当你发现项目模式、开发节奏、估算偏差、常见阻塞、有效任务粒度时记录下来。记忆类型包括:user、feedback、project、reference。写简洁、可操作的笔记。
|
||||
|
||||
@@ -1,48 +1,54 @@
|
||||
---
|
||||
name: "game-reviewer"
|
||||
description: "质量门审查 Agent:对执行案、程序设计、美术资产、程序实现进行质量门审查,产出审查记录并更新 Issue。触发关键词:审查、质量门、验收、review。"
|
||||
description: "审查子代理 — 角色完成工作后启动,审查交付物质量,更新 Issue 标签和评论。使用 ai-reviewer 账号操作 Gitea。"
|
||||
model: opus
|
||||
color: purple
|
||||
memory: project
|
||||
---
|
||||
|
||||
你是游戏项目的**质量门审查 Agent**。项目具体信息以 `CLAUDE.md` 中"项目概况"为准,首次工作时读取该文件了解项目上下文。
|
||||
你是游戏项目的**审查子代理**。你由策划、程序等角色在完成工作后启动,负责审查他们的交付物质量。
|
||||
|
||||
你的职责是**严格、公正地执行质量门审查**——对照检查标准逐条验证交付物,产出结构化的审查记录,并通过 Issue 评论和标签更新反映审查结果。
|
||||
项目具体信息以 `CLAUDE.md` 中"项目概况"为准,首次工作时读取该文件了解项目上下文。
|
||||
|
||||
---
|
||||
|
||||
## 1. 你是谁
|
||||
|
||||
**你是:**
|
||||
- 质量守护者:确保每份交付物在进入下一步流水线前达到质量标准
|
||||
- 子代理:由任何角色(策划、程序、PM)在完成工作后启动
|
||||
- 质量守护者:确保交付物达到质量标准后才进入下一步
|
||||
- 客观检查者:基于检查清单逐条验证,不做主观设计判断
|
||||
- 问题发现者:识别交付物中的遗漏、模糊、不一致和越界
|
||||
|
||||
**你不是:**
|
||||
- 设计决策者:发现问题退回修改,不自己重写
|
||||
- 流程推进者:不推进流水线,只判定通过/不通过
|
||||
- 任务管理者:不创建或分配任务
|
||||
- 流程推进者:不推进流水线或阶段
|
||||
- 任务管理者:不创建、分配或关闭任务
|
||||
- 直接汇报者:你将结果返回给启动你的角色,由该角色向用户汇报
|
||||
|
||||
---
|
||||
|
||||
## 2. 参考文档
|
||||
|
||||
开始工作前,必须先阅读:
|
||||
- `.claude/skills/quality-gate-review.md` — **4 道质量门标准、阶段对应标准、审查流程**(核心技能)
|
||||
- `.claude/skills/quality-gate-review.md` — **审查标准和检查清单**
|
||||
- `.claude/skills/gitea-collab.md` — Gitea CLI 命令和操作规范
|
||||
- `.claude/refs/development-workflow.md` — 流水线定义和阶段划分
|
||||
|
||||
---
|
||||
|
||||
## 3. 审查工作流
|
||||
|
||||
当你被要求执行审查时,严格按以下流程:
|
||||
当你被启动时,会收到以下上下文:
|
||||
- **Issue 编号**:需要审查的 Issue
|
||||
- **审查类型**:执行案 / 程序设计 / 美术资产 / 程序实现
|
||||
- **交付物路径**:产出文件的路径
|
||||
|
||||
严格按以下流程执行:
|
||||
|
||||
### 步骤 1:确认审查范围
|
||||
- 确定审查对象(哪个 Issue / 哪份交付物)
|
||||
- 确定质量门类型(执行案 / 程序设计 / 美术资产 / 程序实现)
|
||||
- 确定当前开发阶段(原型 / 批量生产)
|
||||
- 确定 Issue 编号和交付物
|
||||
- 确定审查类型和对应的质量门标准
|
||||
- 确定当前开发阶段(影响严格程度)
|
||||
|
||||
### 步骤 2:读取交付物
|
||||
- 从 Issue 评论或正文中获取产出文件路径
|
||||
@@ -51,45 +57,62 @@ memory: project
|
||||
|
||||
### 步骤 3:逐条检查
|
||||
- 按 `.claude/skills/quality-gate-review.md` 中对应质量门的检查维度逐条验证
|
||||
- 根据阶段对应标准调整严格程度(原型 vs 批量生产)
|
||||
- 根据阶段调整严格程度(原型较宽松,批量生产严格)
|
||||
|
||||
### 步骤 4:执行强制操作
|
||||
|
||||
**必须**完成以下所有操作(缺一不可):
|
||||
|
||||
1. ✅ 在 `docs/plans/reviews/<task-ID>-review.md` 写审查记录
|
||||
2. ✅ 在对应 Issue 上发布审查结论评论(通过/不通过 + 摘要)
|
||||
3. ✅ 更新 Issue 的 gate:* 标签
|
||||
4. ✅ **通过时**:关闭 Issue(附完成说明)
|
||||
5. ✅ **不通过时**:评论中包含具体问题清单,Issue 保持打开
|
||||
1. ✅ 写审查记录:`docs/plans/reviews/<task-ID>-审查记录.md`
|
||||
2. ✅ 用 `--role reviewer` 发 Issue 评论(标注审查身份)
|
||||
3. ✅ 更新 Issue 标签为 `review:pass` 或 `review:fail`
|
||||
4. ❌ **不关闭 Issue**(关闭决策权在用户)
|
||||
|
||||
### 步骤 5:返回结果
|
||||
|
||||
将以下结构化结果返回给启动你的角色:
|
||||
|
||||
```
|
||||
审查结果:
|
||||
- 通过/未通过
|
||||
- Issue 编号
|
||||
- 审查记录路径
|
||||
- 问题清单(未通过时)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 审查评论模板
|
||||
## 4. Gitea 操作规范
|
||||
|
||||
### 通过
|
||||
**必须使用 `--role reviewer`** 操作 Gitea,不要用其他角色的 token。
|
||||
|
||||
### 审查评论模板
|
||||
|
||||
**通过**:
|
||||
```bash
|
||||
node scripts/gitea.js --role pm issue comment <number> --body "## 质量门审查通过 ✅
|
||||
node scripts/gitea.js --role reviewer issue comment <number> --body "## 审查通过 ✅
|
||||
|
||||
**质量门**:[执行案/程序设计/美术资产/程序实现]
|
||||
**审查类型**:[执行案/程序设计/美术资产/程序实现]
|
||||
**审查日期**:YYYY-MM-DD
|
||||
**审查记录**:docs/plans/reviews/<task-ID>-review.md
|
||||
**审查记录**:docs/plans/reviews/<task-ID>-审查记录.md
|
||||
**审查者**:审查 Agent (ai-reviewer)
|
||||
|
||||
### 结论
|
||||
[一句话总结]
|
||||
|
||||
### 检查通过项
|
||||
- ✅ [检查项1]
|
||||
- ✅ [检查项2]
|
||||
..."
|
||||
- ✅ [检查项2]"
|
||||
```
|
||||
|
||||
### 不通过
|
||||
**未通过**:
|
||||
```bash
|
||||
node scripts/gitea.js --role pm issue comment <number> --body "## 质量门审查未通过 ❌
|
||||
node scripts/gitea.js --role reviewer issue comment <number> --body "## 审查未通过 ❌
|
||||
|
||||
**质量门**:[执行案/程序设计/美术资产/程序实现]
|
||||
**审查类型**:[执行案/程序设计/美术资产/程序实现]
|
||||
**审查日期**:YYYY-MM-DD
|
||||
**审查记录**:docs/plans/reviews/<task-ID>-review.md
|
||||
**审查记录**:docs/plans/reviews/<task-ID>-审查记录.md
|
||||
**审查者**:审查 Agent (ai-reviewer)
|
||||
|
||||
### 结论
|
||||
[一句话总结]
|
||||
@@ -99,13 +122,29 @@ node scripts/gitea.js --role pm issue comment <number> --body "## 质量门审
|
||||
2. ..."
|
||||
```
|
||||
|
||||
### 标签更新
|
||||
|
||||
**通过时**:
|
||||
```bash
|
||||
# 移除 status:in-review,添加 review:pass
|
||||
node scripts/gitea.js --role reviewer issue labels <number> --set <status:ready,status:done,stage:...,role:...,review:pass>
|
||||
```
|
||||
|
||||
**未通过时**:
|
||||
```bash
|
||||
# 移除 status:in-review,改为 status:blocked,添加 review:fail
|
||||
node scripts/gitea.js --role reviewer issue labels <number> --set <status:blocked,stage:...,role:...,review:fail>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 反模式——绝不这样做
|
||||
|
||||
❌ **审查不加评论**:无论通过或不通过,必须在 Issue 上发评论。
|
||||
❌ **使用其他角色的 token**:必须使用 `--role reviewer`。
|
||||
|
||||
❌ **通过不关闭 Issue**:审查通过时必须关闭 Issue。
|
||||
❌ **关闭 Issue**:审查完成后不关闭 Issue,关闭决策权在用户。
|
||||
|
||||
❌ **审查不加评论**:无论通过或不通过,必须在 Issue 上发评论。
|
||||
|
||||
❌ **跳过检查项**:即使交付物看起来"没问题",也必须逐条检查。
|
||||
|
||||
@@ -113,11 +152,13 @@ node scripts/gitea.js --role pm issue comment <number> --body "## 质量门审
|
||||
|
||||
❌ **模糊描述**:问题清单必须指明具体文档位置和修改建议。
|
||||
|
||||
❌ **直接向用户汇报**:你将结果返回给启动你的角色,由该角色汇报。
|
||||
|
||||
---
|
||||
|
||||
## 6. 沟通语言
|
||||
|
||||
使用**中文**沟通。技术术语可保留英文。
|
||||
使用**中文**沟通。技术术语可保留英文。评论中使用中文。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
# 游戏开发工作流
|
||||
|
||||
> 本文档定义游戏项目的开发阶段、版本开发流水线、角色职责、产出标准和质量检查规则。
|
||||
> 版本开发流水线(③-⑦)中各步骤在批量生产阶段严格按序推进,不可跳过或并行;原型阶段在④⑤⑥允许灵活交替(详见原型阶段说明)。①②为阶段级前置,仅在阶段开始时执行一次,后续版本迭代中不重复执行。
|
||||
> 本文档定义游戏项目的开发阶段、任务管理、角色职责、产出标准和质量检查规则。
|
||||
> 项目采用 3 阶段开发模型:草图 → 总路线 → 迭代。每个阶段有独立的技能文件管理任务序列。
|
||||
>
|
||||
> 项目管理工具当前使用 Gitea。所有操作命令和标签定义详见 `.claude/skills/gitea-collab.md`。
|
||||
> 阶段技能文件:
|
||||
> - `.claude/skills/project-initializer.md` — 项目初始化
|
||||
> - `.claude/skills/stage-sketch.md` — 草图阶段
|
||||
> - `.claude/skills/stage-roadmap.md` — 总路线阶段
|
||||
> - `.claude/skills/stage-iteration.md` — 迭代阶段
|
||||
|
||||
---
|
||||
|
||||
@@ -14,7 +19,8 @@
|
||||
| **策划** (Design) | 玩法设计、数值平衡、叙事、系统机制 | `docs/design/`、`GameConfig/` | 扫描 `docs/design/` 和 `GameConfig/` 目录中的文件 |
|
||||
| **程序** (Dev) | 系统实现、技术架构、Bug 修复 | `Source/`、`Content/`、`docs/technical/` | 扫描对应目录中的文件 |
|
||||
| **美术** (Art) | 美术需求分析、资产规格、风格指南 | `docs/art/`、`Content/` | 扫描对应目录中的文件 |
|
||||
| **PM** | 进度追踪、优先级建议、风险预警、流水线推进 | **项目管理工具**(当前为 Gitea Issues)+ `docs/plans/reviews/` | 通过项目管理工具读取任务和里程碑 |
|
||||
| **PM** | 进度管理(3阶段状态机)、任务分配与追踪、开发决策咨询 | **Gitea Issues + Milestones** | 通过 Gitea 读取任务和里程碑 |
|
||||
| **审查** (Reviewer) | 审查交付物质量、更新 Issue 标签和评论 | `docs/plans/reviews/` + Issue 评论 | 通过 `review:pass/fail` 标签 |
|
||||
|
||||
### 协作规则
|
||||
|
||||
@@ -22,8 +28,9 @@
|
||||
- **程序读设计**:实现前必须先读取对应设计文档和配置表
|
||||
- **文档即交接**:Agent 之间不直接通信,通过共享文档目录 + 任务 Issue 评论交接(参见 `.claude/skills/gitea-collab.md`)
|
||||
- **冲突时用户说了算**:玩法/数值/叙事的最终决定权在用户
|
||||
- **PM 是信息枢纽**:跨角色的信息同步和阻塞排查由 PM 负责
|
||||
- **会话启动先调 PM**:每次工作会话开始时,用户必须先调出 PM 执行进度追踪,确认当前状态后再进入具体工作
|
||||
- **PM 是进度调度器**:PM 管理进度和任务分配,不做审查
|
||||
- **审查是子代理**:角色完成工作后启动 game-reviewer 子代理审查,审查结果返回给角色,角色向用户汇报
|
||||
- **会话启动先调 PM**:每次工作会话开始时,用户先调 PM 执行进度检查,确认当前状态后再进入具体工作
|
||||
- **用户驱动角色切换**:角色间的工作切换由用户手动触发,PM 不自动指派任务
|
||||
- **角色任务汇报**:各角色通过项目管理工具在任务 Issue 下评论汇报进度,PM 据此追踪
|
||||
|
||||
@@ -31,8 +38,8 @@
|
||||
|
||||
- 进度管理唯一真实来源是 **项目管理工具**(当前为 Gitea Issues + Milestones)。具体操作参见 `.claude/skills/gitea-collab.md`
|
||||
- 各角色完成工作后在对应 Issue 下评论汇报,不更新本地 report 文件
|
||||
- PM 通过项目管理工具读取任务和评论验收各角色工作结果,给出行动建议
|
||||
- 角色完成工作后汇报结果,PM 验收并建议关闭 Issue,用户确认后由 PM 关闭 Issue
|
||||
- PM 通过项目管理工具读取任务和评论,给出行动建议
|
||||
- 角色完成工作后自行启动 game-reviewer 子代理审查,审查通过后 PM 请示用户关闭 Issue
|
||||
- 任务追踪使用唯一任务 ID(格式:`[角色前缀]-[三位数字]`),对应 Issue 标题前缀
|
||||
- 审查记录仍保留在本地 `docs/plans/reviews/`,同时在 Issue 评论中发布摘要和链接
|
||||
|
||||
@@ -40,74 +47,114 @@
|
||||
|
||||
## 开发阶段
|
||||
|
||||
项目开发分为四个宏观阶段,每个阶段有不同的关注重点和流程重量。
|
||||
项目开发分为三个阶段,每个阶段有独立的技能文件管理任务序列。
|
||||
|
||||
```
|
||||
草案阶段 ─── "这个游戏是什么?" ─── 轻量流程,每项目一次
|
||||
草图阶段 ─── "这个游戏是什么?能不能做?" ─── 概念设计 + 技术可行性
|
||||
↓
|
||||
原型阶段 ─── "这个游戏好玩吗?" ─── 版本流水线(轻量),多版本迭代
|
||||
总路线阶段 ─── "全面设计蓝图" ─── 9 份设计文档 + 版本路径图
|
||||
↓
|
||||
批量生产阶段 ─── "把游戏做完整" ─── 版本流水线(完整 + 生产工具),多版本迭代
|
||||
↓
|
||||
测试与发布阶段 ─── "把游戏做稳定" ─── Bug/反馈驱动
|
||||
迭代阶段 ─── "按版本迭代开发" ─── 插件准备 → 执行案 → 开发 → 整合 → 修复
|
||||
↑ ↓
|
||||
└──── 迭代完成,有下一个迭代 ←── 迭代完成 ───────┘
|
||||
```
|
||||
|
||||
**阶段转换由用户主观判断**,无硬性标准。PM 可建议,但最终决定权在用户。
|
||||
**阶段转换由 PM 建议但需用户确认**。阶段技能文件定义了每个阶段的任务序列和转换条件。
|
||||
|
||||
---
|
||||
|
||||
### 草案阶段
|
||||
### 草图阶段
|
||||
|
||||
**目的**:确定游戏是什么——玩法方向、美术风格、技术可行性。
|
||||
|
||||
**范围**:每项目一次,在正式开发之前完成。
|
||||
|
||||
**关注问题**:这个游戏是什么?为什么要做?做不做得了?
|
||||
|
||||
#### 产出
|
||||
#### 任务序列(2 个任务)
|
||||
|
||||
**玩法草案文档**(`docs/design/concept.md`),包含以下 7 个字段:
|
||||
|
||||
1. **一句话描述** — 用一句话说清楚这是什么游戏
|
||||
2. **玩家核心游玩内容与感受** — 玩家在游戏中的核心操作和活动,以及游戏希望带给玩家的情感基调、视觉风格和整体感受
|
||||
3. **核心受众画像** — 目标玩家是谁(年龄段、游戏经历、情感诉求),他们为什么会喜欢这款游戏
|
||||
4. **游戏核心循环** — 玩家在游戏中反复做什么、为什么持续玩
|
||||
5. **游戏元素** — 世界观、角色、剧情、物品、怪物等关键元素概述
|
||||
6. **市场定位与三大卖点** — 游戏在市场中的定位(品类、平台、价位),以及最能吸引玩家的三个核心卖点
|
||||
7. **参考** — 灵感来源(其他游戏、电影、书、某种经历)
|
||||
|
||||
**美术风格指南**(`docs/art/style-guide.md`)— 确定视觉方向。
|
||||
|
||||
**技术可行性评估** — 评估核心玩法在目标引擎/平台上的实现可行性。
|
||||
|
||||
#### 流程特点
|
||||
|
||||
- **极轻量**:不需要详细的系统设计,不需要执行案
|
||||
- **可以反复推翻重来**:草案阶段推翻设计成本最低
|
||||
- **头脑风暴是核心方法**:通过讨论探索方向,确认后才进入文档产出
|
||||
|
||||
#### PM 检查要点
|
||||
|
||||
- 草案文档 7 个字段是否都已回答?
|
||||
- 美术风格是否有明确方向?
|
||||
- 技术上是否可行?
|
||||
- 用户是否明确表示"可以进入下一阶段"?
|
||||
1. **概念设计** (D-XXX) — 策划产出概念草案
|
||||
2. **技术可行性评估** (P-XXX) — 程序评估技术可行性(依赖概念设计完成)
|
||||
|
||||
#### 完成标志
|
||||
|
||||
玩法草案文档产出且用户确认。进入原型阶段后,草案文档不再修改(除非用户决定推翻重来)。
|
||||
两个任务都关闭 + 草图阶段里程碑下无 open issues → 进入总路线阶段。
|
||||
|
||||
#### 任务管理
|
||||
#### 详细任务定义
|
||||
|
||||
草案阶段不使用版本开发流水线。草案阶段的任务管理(如"产出概念草案")遵循 `pm-role-specification.md` 中的草图任务类型。
|
||||
参见 `.claude/skills/stage-sketch.md`。
|
||||
|
||||
#### 设计文档层级关系
|
||||
---
|
||||
|
||||
### 总路线阶段
|
||||
|
||||
**目的**:建立完整的游戏设计蓝图,规划版本迭代路径。
|
||||
|
||||
**关注问题**:游戏长什么样?有哪些系统?分几个版本做?
|
||||
|
||||
#### 任务序列(9 个任务)
|
||||
|
||||
1. **设计总索引** (D-XXX) — 必须完整
|
||||
2. **世界观设计** (D-XXX) — 允许占位
|
||||
3. **剧情设计** (D-XXX) — 允许占位
|
||||
4. **角色设计** (D-XXX) — 允许占位
|
||||
5. **系统设计** (D-XXX) — ⚠️ 必须完整
|
||||
6. **关卡设计** (D-XXX) — ⚠️ 必须完整
|
||||
7. **UI 设计** (D-XXX) — 允许占位
|
||||
8. **音频设计** (D-XXX) — 允许占位
|
||||
9. **版本路径图** (D-XXX) — 必须完整(依赖 2-8)
|
||||
|
||||
**占位规则**:除系统设计和关卡设计外,其他文档允许空文件占位(标题+章节+「待填充」),后续迭代完善。
|
||||
|
||||
#### 完成标志
|
||||
|
||||
所有任务关闭 + 里程碑下无 open issues → 进入迭代阶段。
|
||||
|
||||
#### 详细任务定义
|
||||
|
||||
参见 `.claude/skills/stage-roadmap.md`。
|
||||
|
||||
---
|
||||
|
||||
### 迭代阶段
|
||||
|
||||
**目的**:按版本迭代推进开发,每个迭代完成一个可玩版本。
|
||||
|
||||
**关注问题**:这个版本做什么?做到什么程度?Bug 修了没?
|
||||
|
||||
#### 任务序列(每迭代 6-9 个任务)
|
||||
|
||||
| 编号 | 任务 | 角色 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 1.4.0 | 插件资产检查与准备 | 程序 | 仅首次迭代 |
|
||||
| 1.4.0.5 | 设计案迭代补充 | 策划 | 可选 |
|
||||
| 1.4.1 | 策划执行案 | 策划 | 必选 |
|
||||
| 1.4.2 | 程序需求分析 | 程序 | 必选 |
|
||||
| 1.4.3.1 | 关卡搭建 | 程序 | 有场景时 |
|
||||
| 1.4.3.2 | 系统开发 | 程序 | 必选 |
|
||||
| 1.4.3.3 | 优化意见 | 程序 | 必选 |
|
||||
| 1.4.3.5 | 资产接入整合 | 程序 | 必选 |
|
||||
| 1.4.4 | Bug 修复 | 程序 | 必选 |
|
||||
|
||||
**关键规则**:PM 每次激活时只创建下一个可创建的任务,不要一次全部创建。
|
||||
|
||||
#### 迭代完成
|
||||
|
||||
Bug 修复(1.4.4)关闭 → 询问用户是否有下一个迭代:
|
||||
- **有** → 关闭当前里程碑 → 创建下一个迭代里程碑 → 继续
|
||||
- **没有** → 返回总路线阶段
|
||||
|
||||
#### 详细任务定义
|
||||
|
||||
参见 `.claude/skills/stage-iteration.md`。
|
||||
|
||||
---
|
||||
|
||||
## 设计文档层级关系
|
||||
|
||||
草案、设计案、执行案三者的关系:
|
||||
|
||||
| 层级 | 定位 | 比喻 | 特点 |
|
||||
|------|------|------|------|
|
||||
| **草案** (concept.md) | 给游戏**定调** | 灵魂 | 回答"这个游戏是什么",简略但方向明确 |
|
||||
| **草案** (概念草案.md) | 给游戏**定调** | 灵魂 | 回答"这个游戏是什么",简略但方向明确 |
|
||||
| **设计案** | 给游戏**定骨架** | 骨架 | 基于草案的原则展开系统设计,定义有哪些系统、系统关系、核心行为 |
|
||||
| **执行案** | 给游戏**填血肉** | 血肉 | 细化到程序可直接实现的交互规则和行为细节 |
|
||||
|
||||
@@ -115,276 +162,9 @@
|
||||
|
||||
---
|
||||
|
||||
### 原型阶段
|
||||
## 执行案规范
|
||||
|
||||
**目的**:验证核心玩法可行性——从最核心功能开始,快速迭代。
|
||||
|
||||
**关注问题**:这个游戏好玩吗?核心循环跑得通吗?
|
||||
|
||||
#### 阶段路线图
|
||||
|
||||
原型阶段开始时,产出**阶段路线图**(`docs/plans/prototype-roadmap.md`):
|
||||
|
||||
- 计划分几个版本迭代
|
||||
- 每个版本探索哪个方向(验证什么假设)
|
||||
- 每个版本的验收标准
|
||||
- 版本间的递进关系
|
||||
|
||||
路线图可以在迭代过程中调整。
|
||||
|
||||
#### 多版本迭代
|
||||
|
||||
原型阶段包含**多个版本**,每个版本是版本开发流水线的一次循环。版本之间可以:
|
||||
- 基于上一版本的反馈调整方向
|
||||
- 增加新的系统验证
|
||||
- 修改已有系统的行为规则
|
||||
|
||||
每个版本的流水线循环:**③执行案 → ④程序设计 → ⑤美术产出 → ⑥程序实现 → ⑦版本复盘**
|
||||
|
||||
①设计案在阶段初期集中产出,后续版本可增量补充。②阶段路线图在阶段开始时产出。
|
||||
|
||||
#### 设计文档组织
|
||||
|
||||
原型阶段的所有设计案和执行案统一放在 `docs/design/prototype/` 目录下:
|
||||
|
||||
```
|
||||
docs/design/prototype/
|
||||
player-controller.md # 设计案
|
||||
player-controller-execution.md # 执行案(含迭代版本表头)
|
||||
interaction-system.md
|
||||
interaction-system-execution.md
|
||||
...
|
||||
```
|
||||
|
||||
**不按版本分子目录**——执行案通过文件内的迭代记录表头追踪各版本的变更。
|
||||
|
||||
#### 各流水线步骤的原型阶段要求
|
||||
|
||||
| 步骤 | 原型阶段要求 |
|
||||
|------|-------------|
|
||||
| ① 设计案 | 可以边做边补,不需要一开始就全部完成。新系统先产出简要设计再动手 |
|
||||
| ② 阶段路线图 | 原型阶段开始时产出,可随迭代调整 |
|
||||
| ③ 执行案 | 关注影响玩法体验的核心行为。边界情况、非核心反馈等细节可以先略过 |
|
||||
| ④ 程序设计 | 可以用占位方案,不需要生产级架构。重点回答"怎么实现核心行为" |
|
||||
| ⑤ 美术产出 | 允许占位资源、用户自行处理。能用就行 |
|
||||
| ⑥ 程序实现 | 允许硬编码、允许快速但不优雅的实现。先跑通再说 |
|
||||
| ⑦ 版本复盘 | 程序汇报实现偏差,PM协调同步,策划更新执行案 |
|
||||
| *PM 质量门* | *穿插于③④⑤⑥之后,按阶段对应标准严格检查。用户可决定跳过哪些检查项,PM 记录用户的跳过决策* |
|
||||
|
||||
> **原型阶段灵活性**:④⑤⑥允许更紧密的交替推进,不必严格串行。例如在⑤美术产出或⑥程序实现中发现需要调整美术需求,可以回退到④重新设计后继续推进。质量门在每次推进时仍然触发,用户可决定跳过非核心检查项。批量生产阶段恢复严格串行。
|
||||
|
||||
#### 版本复盘(每个迭代完成后)
|
||||
|
||||
每个版本迭代完成后执行⑦版本复盘,具体要求见下方流水线中的⑦定义。
|
||||
|
||||
> **为什么每个版本都要复盘?** 文档是下一个迭代的起点。如果文档和实现不一致,下一个迭代的执行案会基于错误的假设,导致更多返工。
|
||||
|
||||
#### 阶段退出
|
||||
|
||||
- **退出标准**:用户主观判断核心玩法已验证通过
|
||||
- **退出时应有**:至少一个可玩的核心循环原型,核心系统的执行案已验证可行,文档已同步
|
||||
- **退出后**:将 `prototype/` 最终版复制到 `production/`,`prototype/` 保留作为归档
|
||||
|
||||
---
|
||||
|
||||
### 批量生产阶段
|
||||
|
||||
**目的**:在核心玩法验证通过的基础上,完善外围系统,填充游戏内容与资产。
|
||||
|
||||
**关注问题**:怎么把游戏做完整?内容够不够?系统之间能不能协同?
|
||||
|
||||
#### 阶段路线图
|
||||
|
||||
批量生产阶段开始时,产出**阶段路线图**(`docs/plans/production-roadmap.md`):
|
||||
|
||||
- 计划分几个版本迭代
|
||||
- 每个版本的主要内容范围
|
||||
- 每个版本的验收标准
|
||||
- 生产工具开发计划(如需要)
|
||||
|
||||
#### 多版本迭代
|
||||
|
||||
与原型阶段相同,批量生产阶段包含**多个版本**,每个版本是流水线的一次循环。
|
||||
|
||||
#### 设计文档组织
|
||||
|
||||
批量生产阶段的所有设计案和执行案统一放在 `docs/design/production/` 目录下:
|
||||
|
||||
```
|
||||
docs/design/production/
|
||||
player-controller.md # 在原型基础上更新
|
||||
player-controller-execution.md # 继续迭代,追加新的迭代记录
|
||||
...
|
||||
```
|
||||
|
||||
#### 各流水线步骤的批量生产阶段要求
|
||||
|
||||
| 步骤 | 批量生产阶段要求 |
|
||||
|------|----------------|
|
||||
| ① 设计案 | 应已完整。新增系统必须先产出设计案再进入版本开发 |
|
||||
| ② 阶段路线图 | 批量生产阶段开始时产出,可随迭代调整 |
|
||||
| ③ 执行案 | 所有细节必须补全:边界情况、非核心反馈、边缘体验、异常处理等 |
|
||||
| ④ 程序设计 | 需要生产级架构,考虑扩展性和数据驱动。美术需求必须具体到尺寸和数量 |
|
||||
| ⑤ 美术产出 | 正式资产,符合风格指南。不允许占位 |
|
||||
| ⑥ 程序实现 | 不允许硬编码,数据应配置化。代码质量需达到发布标准 |
|
||||
| ⑦ 版本复盘 | 程序汇报实现偏差,PM协调同步,策划更新执行案。所有差异必须记录 |
|
||||
| *PM 质量门* | *穿插于③④⑤⑥之后,完整执行所有检查项,逐条对照* |
|
||||
|
||||
#### 版本复盘(每个迭代完成后)
|
||||
|
||||
与原型阶段相同:每个版本迭代完成后必须执行⑦版本复盘。批量生产阶段要求所有差异必须记录在案,不可省略。
|
||||
|
||||
#### 生产工具
|
||||
|
||||
批量生产阶段可能需要在正式开发前先开发生产工具:
|
||||
|
||||
| 工具类型 | 目的 | 何时开发 |
|
||||
|----------|------|----------|
|
||||
| 数据配置工具 | 让策划能自行配置交互内容、NPC 对话、物品属性等 | 需要大量配置数据时 |
|
||||
| 关卡编辑工具 | 快速搭建和调整场景布局 | 需要频繁调整场景时 |
|
||||
| 批量资产生成工具 | 批量生成/导入美术资产 | 资产量大时 |
|
||||
| 测试工具 | 自动化验证、数据校验 | 手动测试效率低时 |
|
||||
|
||||
工具开发本身也应遵循版本流水线(有执行案、有程序设计),但允许省略美术产出步骤(⑤)和对应的美术验收门。
|
||||
|
||||
#### 阶段退出
|
||||
|
||||
- **退出标准**:所有计划内容填充完毕,游戏功能完整
|
||||
- **退出后**:进入测试与发布阶段
|
||||
|
||||
---
|
||||
|
||||
### 测试与发布阶段
|
||||
|
||||
**目的**:在现有功能基础上做微调和 Bug 修复,确保游戏稳定可发布。
|
||||
|
||||
**关注问题**:有没有 Bug?体验够不够好?性能行不行?
|
||||
|
||||
#### 流程特点
|
||||
|
||||
- **不使用版本开发流水线**(③-⑦)
|
||||
- **不增加大型新功能**——所有功能应已在上游阶段完成
|
||||
- **以 Bug 和反馈驱动**
|
||||
- **微调允许**:数值调整、文案修改、UI 微调、性能优化
|
||||
|
||||
#### 工作流程
|
||||
|
||||
```
|
||||
收集 Bug/反馈 → 评估优先级 → 修复/微调 → 验证 → 发布
|
||||
↑ │
|
||||
└───────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
1. **收集**:从测试、体验、反馈中收集 Bug 和改进意见
|
||||
2. **评估**:按严重程度和影响范围排优先级
|
||||
3. **修复/微调**:针对性修改,不引入大的架构变动
|
||||
4. **验证**:确认修复有效,未引入新问题
|
||||
5. **发布**:构建发布版本
|
||||
|
||||
---
|
||||
|
||||
## 版本开发流水线
|
||||
|
||||
> 本流水线在**原型阶段**和**批量生产阶段**使用。两个阶段使用相同的流水线结构,但严格度和完整性要求不同(详见上方各阶段说明)。
|
||||
|
||||
**阶段级前置**(每个阶段开始时执行一次):
|
||||
|
||||
```
|
||||
① 设计案 → ② 阶段路线图
|
||||
```
|
||||
|
||||
**版本级循环**(每个版本迭代执行一次,严格按序推进):
|
||||
|
||||
```
|
||||
③ 执行案
|
||||
↓
|
||||
[PM 审查:执行案质量门]
|
||||
↓
|
||||
④ 程序设计
|
||||
↓
|
||||
[PM 审查:程序设计质量门]
|
||||
↓
|
||||
⑤ 美术产出
|
||||
↓
|
||||
[PM 审查:美术资产验收门]
|
||||
↓
|
||||
⑥ 程序实现
|
||||
↓
|
||||
[PM 审查:程序实现验收门]
|
||||
↓
|
||||
⑦ 版本复盘
|
||||
```
|
||||
|
||||
**③-⑦ 每个步骤都是下一步的必要前置条件,不可跳过或并行(批量生产阶段严格执行;原型阶段④⑤⑥允许灵活交替,见原型阶段说明)。PM 审查不通过则退回重做,不可进入下一步(生产工具开发可省略⑤及对应验收门,见生产工具说明)。用户是质量门争议的最终仲裁者。**
|
||||
|
||||
---
|
||||
|
||||
### ① 设计案(策划)
|
||||
|
||||
**目的**:定义**当前阶段目标形态**的全景图——有哪些系统、有哪些元素、它们大体上是什么关系。
|
||||
|
||||
**范围**:面向当前阶段要达成的目标形态,不是某个版本,也不是游戏的最终完成形态。
|
||||
|
||||
**必须包含的内容**:
|
||||
|
||||
1. **系统清单**:当前阶段目标形态包含哪些系统
|
||||
2. **元素清单**:游戏世界中的关键元素(角色、场景区域、收集品分类、NPC等)
|
||||
3. **系统关系**:各系统之间如何关联和互相影响
|
||||
|
||||
**产出形式**:
|
||||
- 可以有多个文档,按系统或主题拆分
|
||||
- 必须有一个**索引文档**把所有设计文档串联起来
|
||||
- 产出位置:`docs/design/<阶段>/`(`prototype/` 或 `production/`)。不同阶段的设计案独立维护:原型阶段描述"原型要验证什么",批量生产阶段描述"完整游戏要做成什么样"
|
||||
|
||||
**PM 检查要点**:
|
||||
- 是否覆盖了当前阶段目标形态的所有主要系统?
|
||||
- 系统关系是否清晰(谁依赖谁、谁独立)?
|
||||
- 索引文档是否存在且能串联所有设计文档?
|
||||
|
||||
---
|
||||
|
||||
### ② 阶段路线图(PM 主导,策划配合)
|
||||
|
||||
**目的**:定义当前阶段(原型或批量生产)分几个版本推进,每个版本探索什么方向。
|
||||
|
||||
**必须包含的内容**:
|
||||
|
||||
1. **版本列表**:当前阶段有哪些版本
|
||||
2. **每个版本的核心目标**:这个版本验证/实现什么
|
||||
3. **每个版本的范围概述**:大致包含哪些系统和内容
|
||||
4. **每个版本的内容完成度**:每种内容做到什么程度(占位/初版/正式)
|
||||
5. **每个版本涉及的系统清单**:哪些系统需要在本版本实现(引用设计案中的系统名)
|
||||
6. **版本间的递进关系**:为什么先做这些后做那些
|
||||
7. **每个版本的验收标准**:怎样算"这个版本做完了"
|
||||
|
||||
**产出位置**:`docs/plans/prototype-roadmap.md` 或 `docs/plans/production-roadmap.md`
|
||||
|
||||
**版本编号规则**:
|
||||
- 原型阶段:`v0.1`、`v0.2`、`v0.3`……
|
||||
- 批量生产阶段:从 `v1.0` 开始,按 `v1.1`、`v1.2` 递增
|
||||
- 版本号反映阶段归属,不跨阶段延续(原型 v0.3 完成后,批量生产从 v1.0 开始)
|
||||
|
||||
**PM 检查要点**:
|
||||
- 每个版本的范围是否与设计案对齐?
|
||||
- 版本间是否有清晰的递进逻辑?
|
||||
- 是否有版本范围过大的风险?
|
||||
|
||||
---
|
||||
|
||||
### ③ 执行案(策划)
|
||||
|
||||
**目的**:针对当前版本中的每个系统/元素,写出完整的**交互规则细节**,细到程序照着这个逻辑实现功能。
|
||||
|
||||
**范围**:仅限当前版本中需要实现的系统。
|
||||
|
||||
**核心原则**:
|
||||
|
||||
- **只描述行为和规则,不涉及技术实现**
|
||||
- **必须覆盖所有玩家可感知的行为**
|
||||
- **不能有模糊地带**——程序拿到执行案后不应有"这没写要怎么做"的疑问
|
||||
- **具体参数数值**不需要策划提前确定,程序先设初始值,后续体验阶段调优。如有建议值标注"建议值,可调"
|
||||
|
||||
**执行案格式**:
|
||||
### 执行案格式
|
||||
|
||||
每个执行案文件顶部必须包含**迭代记录表头**:
|
||||
|
||||
@@ -408,304 +188,70 @@ docs/design/production/
|
||||
(当前最新状态的所有行为规则)
|
||||
```
|
||||
|
||||
**必须包含的内容**:
|
||||
### 执行案必须包含
|
||||
|
||||
1. **系统概述**:这个系统做什么、服务于什么体验目标
|
||||
2. **完整的行为规则清单**:每个操作的触发条件、触发后表现、结束条件
|
||||
3. **状态和状态转换**:系统有哪些状态、什么条件触发状态变化
|
||||
4. **边界情况处理**:异常/极端情况下的系统行为(如多个交互物重叠、交互中移动等)
|
||||
4. **边界情况处理**:异常/极端情况下的系统行为
|
||||
5. **与其他系统的交互**:本系统与其他系统的关联和影响
|
||||
|
||||
**不应包含的内容**:
|
||||
### 执行案不应包含
|
||||
|
||||
- 用什么引擎组件实现
|
||||
- 代码/脚本架构怎么组织
|
||||
- 数据用什么格式存储
|
||||
- 具体的参数精确值(调优参数标注"建议值,可调")
|
||||
|
||||
**阶段差异**:
|
||||
### 关卡执行案
|
||||
|
||||
| 维度 | 原型阶段 | 批量生产阶段 |
|
||||
|------|----------|-------------|
|
||||
| 行为覆盖 | 核心行为必须完整,非核心行为可略过 | 所有行为必须完整覆盖 |
|
||||
| 边界情况 | 影响核心玩法的必须处理,其余可略过 | 全部必须处理 |
|
||||
| 系统间关联 | 核心关联必须说明 | 全部必须说明 |
|
||||
|
||||
**产出形式**:
|
||||
- 每个系统一份执行案,放在 `docs/design/<阶段>/` 目录下
|
||||
- 文件命名:`<系统名>-execution.md`(如 `player-controller-execution.md`)
|
||||
- 不按版本分文件,通过迭代记录表头追踪变更
|
||||
- **关卡执行案**(如有场景搭建需求):文件命名 `level-v<XX>-<name>-execution.md`,产出模板参见 `.claude/refs/level-execution-template.md`
|
||||
|
||||
**关卡执行案**:
|
||||
- 当版本开发涉及场景搭建时,策划额外产出一份关卡执行案
|
||||
- 关卡执行案将场景设计案的氛围描述转化为空间精度搭建指南(坐标、尺寸、BSP 资产映射)
|
||||
- 使用混合坐标体系:区域用场景绝对坐标,区域内物件用相对偏移坐标
|
||||
- 交互物和收集品的位置标注必须与交互系统/收集系统执行案交叉引用
|
||||
- 完整规范参见 `docs/design/prototype/level-design-workflow.md`
|
||||
- 完整规范参见 `.claude/refs/level-execution-template.md`
|
||||
|
||||
---
|
||||
|
||||
### PM 审查:执行案质量门
|
||||
## 审查流程
|
||||
|
||||
**触发时机**:策划完成执行案(③)后,进入程序设计(④)之前。
|
||||
> 审查由 **game-reviewer 子代理**执行,不再由 PM 执行。
|
||||
> 角色完成工作后自行启动 reviewer 子代理。
|
||||
|
||||
**审查人**:PM
|
||||
### 审查触发
|
||||
|
||||
**审查内容**:
|
||||
角色完成工作 → 在 Issue 下评论汇报 → 改标签为 `status:in-review` → 启动 game-reviewer 子代理
|
||||
|
||||
1. **完整性**:是否覆盖了该系统所有玩家可感知的行为?每个操作是否明确了触发条件、触发后表现、结束条件?
|
||||
2. **清晰性**:是否有模糊地带?行为描述是否无歧义?程序拿到后能否不问策划任何问题就实现功能?
|
||||
3. **边界情况**:异常/极端情况下的系统行为是否都处理了?
|
||||
4. **系统间关联**:与其他系统的关联和影响是否说明?
|
||||
5. **技术越界检查**:是否误入了技术细节?精确参数值是否标注为"建议值,可调"?
|
||||
### 审查标准
|
||||
|
||||
**关卡执行案附加检查**(仅当审查对象包含关卡执行案时):
|
||||
- **关卡资产存在验证**:关卡执行案中引用的 BSP 资产是否在 `Content/BlockingStarterPack/` 中实际存在?标识色材质是否在可用范围内?
|
||||
- 关卡执行案的质量门**仅做资产存在验证**。空间精度、比例、跨文档一致性等维度由程序在白模搭建过程中验证——如果程序在 Issue 中汇报搭建异常,PM 汇总异常并汇报给用户确认,不自行判断通过/不通过。
|
||||
审查标准定义在 `.claude/skills/quality-gate-review.md`,包含 4 道质量门:
|
||||
1. 执行案质量门 — 完整性、清晰性、边界情况、系统关联、技术越界
|
||||
2. 程序设计质量门 — 执行案覆盖度、美术需求、架构、数据流、风险、工作量
|
||||
3. 美术资产验收门 — 需求覆盖、资产到位、规格、缺失处理
|
||||
4. 程序实现验收门 — 设计一致性、覆盖度、资产使用、偏差处理
|
||||
|
||||
**判定**:
|
||||
- **通过** → 进入 ④ 程序设计
|
||||
- **不通过** → 退回策划修改,列出具体问题清单
|
||||
- **争议** → PM 与策划对判定有分歧时,由用户最终裁决
|
||||
### 审查结果
|
||||
|
||||
**产出**:PM 审查记录(记录在 `docs/plans/reviews/<任务ID>-review.md`)
|
||||
- **通过** → reviewer 更新标签为 `review:pass` → 角色向用户汇报 → 用户决定是否关闭
|
||||
- **未通过** → reviewer 更新标签为 `review:fail` + `status:blocked` → 角色向用户汇报 → 修改后重新提交
|
||||
|
||||
---
|
||||
|
||||
### ④ 程序设计(程序)
|
||||
## 审查质量标准
|
||||
|
||||
**目的**:程序读完策划的执行案后,设计具体的实现方案。
|
||||
> 以下标准供 game-reviewer 子代理使用。详细标准定义在 `.claude/skills/quality-gate-review.md`。
|
||||
|
||||
**前置条件**:执行案(③)已就绪,且 PM 审查通过。
|
||||
### 执行案质量标准
|
||||
|
||||
**必须包含的内容**:
|
||||
|
||||
1. **实现方案**:针对执行案中的每条行为规则,说明打算如何实现
|
||||
2. **类与组件架构**:需要哪些类/组件?继承关系?挂载方式?
|
||||
3. **数据流**:数据从哪里来?系统之间如何通信?
|
||||
4. **美术资源需求清单**:实现这个功能需要哪些美术资源(模型、动画、UI 等),具体到数量和尺寸
|
||||
5. **技术风险和注意事项**:实现中可能遇到的技术难点
|
||||
6. **工作量估算与实现拆分建议**:估算总体工作量(如预计涉及多少文件/类/组件),给出⑥程序实现的推荐拆分方式——按什么维度拆分为可独立验证的子任务,每个子任务大致包含什么内容。供 PM 审核后据此创建⑥的实现任务
|
||||
|
||||
**阶段差异**:
|
||||
|
||||
| 维度 | 原型阶段 | 批量生产阶段 |
|
||||
|------|----------|-------------|
|
||||
| 架构 | 可以用占位方案,不需要生产级 | 需要生产级架构,考虑扩展性 |
|
||||
| 数据流 | 允许硬编码 | 数据应配置化 |
|
||||
| 美术需求 | 允许简化描述 | 必须具体到尺寸和数量 |
|
||||
|
||||
**产出位置**:`docs/technical/<系统名>-tech.md`
|
||||
|
||||
---
|
||||
|
||||
### PM 审查:程序设计质量门
|
||||
|
||||
**触发时机**:程序完成技术设计(④)后,进入美术产出(⑤)之前。
|
||||
|
||||
**审查内容**:
|
||||
|
||||
1. **执行案覆盖度**:程序设计是否完整回应了执行案中的每条行为规则?逐条对照
|
||||
2. **美术资源需求**:需求是否列得具体?数量、尺寸、触发时机是否明确?
|
||||
3. **类与组件架构**:继承关系和挂载方式是否清晰?
|
||||
4. **数据流**:是否明确?
|
||||
5. **技术风险**:是否识别?
|
||||
6. **工作量估算与拆分建议**:是否给出了工作量估算?拆分方式是否合理(每个子任务可独立验证,粒度适合单人单次会话完成)?拆分后的任务是否能完整覆盖实现方案?
|
||||
|
||||
**判定**:
|
||||
- **通过** → 进入 ⑤ 美术产出
|
||||
- **不通过** → 退回程序修改
|
||||
- **争议** → PM 与程序对判定有分歧时,由用户最终裁决
|
||||
|
||||
---
|
||||
|
||||
### ⑤ 美术产出(美术)
|
||||
|
||||
**目的**:根据程序输出的美术资源需求清单,产出对应资产。
|
||||
|
||||
**前置条件**:程序设计(④)已就绪,且 PM 审查通过。
|
||||
|
||||
**阶段差异**:
|
||||
|
||||
| 维度 | 原型阶段 | 批量生产阶段 |
|
||||
|------|----------|-------------|
|
||||
| 资产质量 | 允许占位资源、用户自行处理 | 正式资产,符合风格指南 |
|
||||
| 完成度 | 能用就行 | 必须满足规格要求 |
|
||||
|
||||
**产出位置**:
|
||||
- 资产放 `Content/` 对应目录
|
||||
- 清单记录在 `docs/art/asset-manifest.md` 或直接在技术方案中更新
|
||||
|
||||
---
|
||||
|
||||
### PM 审查:美术资产验收门
|
||||
|
||||
**触发时机**:美术完成资产产出(⑤)后,进入程序实现(⑥)之前。
|
||||
|
||||
**审查内容**:
|
||||
|
||||
1. **需求覆盖度**:逐项对照美术资源需求清单
|
||||
2. **资产到位确认**:资产文件是否已放置在对应目录?
|
||||
3. **资产规格**:尺寸、格式等是否符合需求?
|
||||
4. **缺失项处理**:未到位资产是否有明确处理方案?
|
||||
|
||||
**判定**:
|
||||
- **通过** → 进入 ⑥ 程序实现
|
||||
- **不通过** → 退回美术补充
|
||||
- **争议** → PM 与美术对判定有分歧时,由用户最终裁决
|
||||
|
||||
---
|
||||
|
||||
### ⑥ 程序实现(程序)
|
||||
|
||||
**目的**:基于程序设计方案和美术资产,实际编写代码。
|
||||
|
||||
**前置条件**:程序设计(④)已就绪且 PM 审查通过,美术资产(⑤)已就位且 PM 验收通过。
|
||||
|
||||
**白模搭建子任务(6a)**:
|
||||
当版本开发涉及场景搭建时,⑥的第一个子任务是白模关卡搭建:
|
||||
- 读取关卡执行案 `level-v<XX>-<name>-execution.md` 和场景设计案
|
||||
- 使用 `Content/BlockingStarterPack/` 资产在 UE5 编辑器中搭建白模关卡
|
||||
- 按区域分文件夹组织 Actor(命名规范:`LVL_<ZoneID>_<Category>_<Description>`)
|
||||
- 完成比例验证(角色可通过门洞、摄像机不穿墙、流线通行)
|
||||
- 产出搭建报告 `docs/technical/level-v<XX>-build-report.md` 记录偏差
|
||||
- **搭建报告必须以评论形式发布到对应任务 Issue**(与本地文件内容一致)
|
||||
- 完整搭建流程参见 `docs/design/prototype/level-design-workflow.md` 第四章
|
||||
|
||||
**阶段差异**:
|
||||
|
||||
| 维度 | 原型阶段 | 批量生产阶段 |
|
||||
|------|----------|-------------|
|
||||
| 代码质量 | 允许硬编码、快速实现 | 不允许硬编码,数据配置化,代码达发布标准 |
|
||||
| 验证 | 核心流程跑通即可 | 每个系统独立验证 + 集成验证 |
|
||||
|
||||
**实现完成后进入 PM 程序实现验收门,验收通过后进入⑦版本复盘。**
|
||||
|
||||
---
|
||||
|
||||
### PM 审查:程序实现验收门
|
||||
|
||||
**触发时机**:程序完成实现(⑥)后,进入版本复盘(⑦)之前。
|
||||
|
||||
**审查人**:PM
|
||||
|
||||
**审查内容**:
|
||||
|
||||
1. **设计一致性**:实现是否与程序设计方案(④)一致?逐条对照
|
||||
2. **执行案覆盖度**:最终实现是否完整覆盖了执行案(③)中的所有行为规则?
|
||||
3. **资产使用确认**:美术资产是否已正确集成?
|
||||
4. **完成度评估**:当前版本的实现是否达到了阶段要求的完成度?
|
||||
5. **偏差梳理**:实现中产生的偏差是否已梳理,偏差等级是否合理?
|
||||
|
||||
**偏差处理规则**:
|
||||
|
||||
| 偏差等级 | 判定标准 | 处理方式 |
|
||||
|----------|----------|----------|
|
||||
| **轻微** | 不影响核心行为,仅实现细节差异 | 记录在偏差清单,在⑦版本复盘时同步文档 |
|
||||
| **重大** | 影响核心行为或与设计方案严重不一致 | PM 建议回退到③重新出执行案或开启新版本修正,由用户决定 |
|
||||
|
||||
**判定**:
|
||||
|
||||
- **通过** → 进入 ⑦ 版本复盘
|
||||
- **不通过(轻微)** → 程序补充修正后重新验收
|
||||
- **不通过(重大)** → PM 提出处理建议(回退步骤或开新版本),由用户最终决定
|
||||
- **争议** → PM 与程序对判定有分歧时,由用户最终裁决
|
||||
|
||||
**产出**:PM 审查记录(记录在 `docs/plans/reviews/<任务ID>-review.md`)
|
||||
|
||||
---
|
||||
|
||||
### ⑦ 版本复盘(程序汇报 → PM 协调 → 策划更新)
|
||||
|
||||
**目的**:确保文档与实现一致,为下一个迭代提供准确的起点。
|
||||
|
||||
**触发时机**:PM 程序实现验收门通过后。
|
||||
|
||||
**流程**:
|
||||
|
||||
1. **程序汇报**:程序对照执行案,列出实现中产生的偏差和调整
|
||||
2. **PM 协调**:PM 汇总程序汇报的偏差清单,协调策划进行文档更新
|
||||
3. **策划更新**:策划根据偏差清单,更新设计案和执行案
|
||||
4. **标记状态**:执行案迭代记录中该版本标记为 ✅ 已同步
|
||||
|
||||
**必须更新的内容**:
|
||||
- 设计案中与实际不符的系统描述
|
||||
- 执行案中与实际实现不一致的行为规则
|
||||
- 执行案迭代记录中新增一行,记录变更
|
||||
|
||||
**阶段差异**:
|
||||
|
||||
| 维度 | 原型阶段 | 批量生产阶段 |
|
||||
|------|----------|-------------|
|
||||
| 偏差记录 | 核心行为的偏差必须记录 | 所有差异必须记录在案 |
|
||||
| 文档同步 | 核心系统必须同步 | 所有系统必须同步 |
|
||||
|
||||
#### 为什么必须复盘
|
||||
|
||||
- 文档是下一个迭代的起点。如果文档和实现不一致,下一个迭代会基于错误假设
|
||||
- PM 的质量门审查基于文档。文档不准确会导致审查无效
|
||||
- 程序在实现时可能发现设计的问题并做了调整,这些调整必须反映到文档中
|
||||
|
||||
---
|
||||
|
||||
## PM 诊断检查清单
|
||||
|
||||
PM 在每次 `/plan` 调用时,先判断宏观阶段,再针对**当前版本**逐项检查流水线状态。
|
||||
|
||||
### 宏观阶段判断
|
||||
|
||||
| 检查项 | 判断方式 | 结果 |
|
||||
|--------|----------|------|
|
||||
| 项目处于哪个开发阶段? | 检查 `concept.md` → 检查核心玩法是否已有可玩原型 → 检查内容是否已完整 | 草案 / 原型 / 批量生产 / 测试发布 |
|
||||
|
||||
### 版本流水线检查
|
||||
|
||||
| 检查项 | 检查方式 | 通过条件 |
|
||||
|--------|----------|----------|
|
||||
| ① 设计案是否存在且完整? | 检查 `docs/design/<阶段>/` 中的设计文档和索引 | 覆盖所有主要系统,有索引串联 |
|
||||
| ② 阶段路线图是否存在? | 检查 `docs/plans/<阶段>-roadmap.md` | 版本列表、每版本目标、递进关系明确 |
|
||||
| ③ 执行案是否齐全? | 检查 `docs/design/<阶段>/` 对应执行案 | 每个涉及系统的核心行为规则都已明确 |
|
||||
| **PM 审查**:执行案质量门 | 对照质量标准逐项检查 | 完整性、清晰性、边界情况通过 |
|
||||
| ④ 程序设计方案是否齐全? | 检查 `docs/technical/` 对应文档 | 每个执行案都有对应技术方案 |
|
||||
| **PM 审查**:程序设计质量门 | 执行案与技术方案逐条对照 | 行为规则全覆盖,美术需求具体,工作量与拆分建议明确 |
|
||||
| ⑤ 美术资产是否就位? | 检查资源需求清单 vs 实际资产 | 所有必需资产已产出或有处理方案 |
|
||||
| **PM 审查**:美术资产验收门 | 需求清单 vs 实际资产逐项对照 | 每项需求都有对应产出 |
|
||||
| ⑥ 程序实现是否完成? | 检查实际文件 | 实现完成 |
|
||||
| **PM 审查**:程序实现验收门 | 实现与设计方案逐条对照 + 偏差分级处理 | 设计一致,偏差已处理或有明确方案 |
|
||||
| ⑦ 版本复盘是否完成? | 检查执行案迭代记录是否 ✅ 已同步 | 策划已根据实现偏差更新文档 |
|
||||
|
||||
**遇到第一个未通过的即为当前应推进的步骤。**
|
||||
|
||||
**注意**:所有质量门按阶段对应标准严格检查。用户可根据实际情况决定跳过特定检查项,PM 记录用户的跳过决策及理由。
|
||||
|
||||
---
|
||||
|
||||
## 文档质量检查标准
|
||||
|
||||
以下标准对应流水线中的三道 PM 质量门。
|
||||
|
||||
### 执行案质量标准(对应「执行案质量门」)
|
||||
|
||||
**完整性检查**:
|
||||
- [ ] 是否覆盖了该系统所有玩家可感知的行为?
|
||||
- [ ] 每个操作是否明确了触发条件、触发后表现、结束条件?
|
||||
- [ ] 边界情况是否都处理了?
|
||||
- [ ] 与其他系统的关联是否说明?
|
||||
|
||||
**清晰性检查**:
|
||||
- [ ] 是否有模糊地带?
|
||||
- [ ] 行为描述是否无歧义?
|
||||
- [ ] 程序拿到后能否不问策划任何问题就实现功能?
|
||||
|
||||
**边界检查**:
|
||||
- [ ] 是否误入了技术细节?
|
||||
- [ ] 精确参数值是否标注为"建议值,可调"而非固定值?
|
||||
|
||||
**迭代记录检查**:
|
||||
- [ ] 是否有模糊地带?行为描述是否无歧义?
|
||||
- [ ] 是否误入了技术细节?参数值是否标注为"建议值,可调"?
|
||||
- [ ] 迭代记录表头是否存在且填写完整?
|
||||
- [ ] 已完成版本的文档是否已同步(✅ 已同步)?
|
||||
|
||||
### 程序设计质量标准(对应「程序设计质量门」)
|
||||
### 程序设计质量标准
|
||||
|
||||
- [ ] 是否完整回应了执行案中的每条行为规则?(逐条对照)
|
||||
- [ ] 美术资源需求是否列得具体(尺寸、数量、触发时机)?
|
||||
@@ -714,29 +260,19 @@ PM 在每次 `/plan` 调用时,先判断宏观阶段,再针对**当前版本
|
||||
- [ ] 技术风险是否识别?
|
||||
- [ ] 工作量估算是否给出?实现拆分建议是否合理且可操作?
|
||||
|
||||
### 美术资产验收标准(对应「美术资产验收门」)
|
||||
### 美术资产验收标准
|
||||
|
||||
- [ ] 资源需求清单中的每一项是否都有对应的美术产出?(逐项对照)
|
||||
- [ ] 资产文件是否已放置在对应目录?
|
||||
- [ ] 资产路径是否记录清晰?
|
||||
- [ ] 资产规格是否符合需求?
|
||||
- [ ] 未到位资产是否有明确的处理方案?
|
||||
|
||||
### 程序实现验收标准(对应「程序实现验收门」)
|
||||
### 程序实现验收标准
|
||||
|
||||
- [ ] 实现是否与程序设计方案一致?(逐条对照)
|
||||
- [ ] 最终实现是否完整覆盖了执行案中的所有行为规则?
|
||||
- [ ] 美术资产是否已正确集成并使用?
|
||||
- [ ] 当前版本的实现是否达到了阶段要求的完成度?
|
||||
- [ ] 偏差清单是否已梳理清楚?
|
||||
- [ ] 重大偏差是否有明确的处理方案(回退步骤或开新版本)?
|
||||
|
||||
### 版本复盘标准(对应「⑦版本复盘」)
|
||||
|
||||
- [ ] 程序是否汇报了实现偏差清单?
|
||||
- [ ] PM 是否审核了偏差清单并给出协调意见?
|
||||
- [ ] 策划是否根据协调结果更新了设计案/执行案?
|
||||
- [ ] 执行案迭代记录是否新增一行并标记 ✅ 已同步?
|
||||
- [ ] 偏差清单是否已梳理清楚?重大偏差是否有处理方案?
|
||||
|
||||
---
|
||||
|
||||
@@ -746,4 +282,4 @@ PM 在每次 `/plan` 调用时,先判断宏观阶段,再针对**当前版本
|
||||
- **精力有限**——避免一次性拆分太多并行任务
|
||||
- **心流重要**——建议按「完整做完一个小系统」而非「同时推进多个系统」的方式工作
|
||||
- **早期验证**——越早能跑起来看到东西,越不容易失去动力
|
||||
- **文档交接**——Agent 之间通过文档交接,PM 是信息枢纽
|
||||
- **文档交接**——Agent 之间通过文档交接,PM 是进度调度器,reviewer 负责质量把关
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> 本模板供策划产出关卡执行案时使用。关卡执行案是场景设计案的"空间精度版本"——将氛围描述转化为带有坐标、尺寸和资产建议的结构化搭建指南,供程序在 UE5 编辑器中搭建白模。
|
||||
>
|
||||
> 文件命名:`level-v<XX>-<name>-execution.md`(如 `level-v01-suburban-execution.md`)
|
||||
> 文件命名:`关卡-v<XX>-<名称>-执行案.md`(如 `关卡-v01-城郊街区-执行案.md`)
|
||||
> 存放位置:`docs/design/<阶段>/`(与其他执行案同目录)
|
||||
|
||||
---
|
||||
@@ -49,7 +49,7 @@
|
||||
| ... | | | | | |
|
||||
| Zn | | | | | |
|
||||
|
||||
> 标识色使用 BSP 材质名(参见 `blockout-asset-reference.md`),程序用对应材质标识区域。
|
||||
> 标识色使用 BSP 材质名(参见 `白模资产参考.md`),程序用对应材质标识区域。
|
||||
|
||||
### 2.2 交通流线
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
- 填写 BSP 资产类别名和组合方式(如 "Wall_400x300 × 2 叠加")
|
||||
- 程序负责映射到引擎中的具体资产路径
|
||||
- 如无精确匹配,写"Primitives 组合"并注明替代意图
|
||||
- 参见 `.claude/refs/blockout-asset-reference.md` 中的替代方案
|
||||
- 参见 `.claude/refs/白模资产参考.md` 中的替代方案
|
||||
|
||||
#### 3.x.3 布景物清单
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
|
||||
| 交互编号 | 物件名称 | 交互类型 | 相对原点偏移 (ΔX,ΔY,ΔZ) | 触发半径 (cm) | 引用 |
|
||||
|---------|---------|---------|----------------------|-------------|------|
|
||||
| Zx-I01 | <!-- 如 旧冰箱 --> | <!-- 如 观察 --> | (200,400,10) | <!-- 如 150 --> | interaction-system-execution #n |
|
||||
| Zx-I01 | <!-- 如 旧冰箱 --> | <!-- 如 观察 --> | (200,400,10) | <!-- 如 150 --> | 交互系统-执行案 #n |
|
||||
| Zx-I02 | | | | | |
|
||||
| ... | | | | | |
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
|
||||
| 收集编号 | 物品名称 | 稀有度 | 相对原点偏移 (ΔX,ΔY,ΔZ) | 引用 |
|
||||
|---------|---------|--------|----------------------|------|
|
||||
| Zx-C01 | <!-- 如 彩色弹珠 --> | <!-- 如 普通/稀有 --> | (500,200,0) | collection-system-execution #n |
|
||||
| Zx-C01 | <!-- 如 彩色弹珠 --> | <!-- 如 普通/稀有 --> | (500,200,0) | 收集系统-执行案 #n |
|
||||
| Zx-C02 | | | | |
|
||||
| ... | | | | | |
|
||||
|
||||
@@ -187,7 +187,7 @@
|
||||
|
||||
| # | 物品名称 | 所在区域 | 绝对坐标 (X,Y,Z) | 稀有度 | 引用 |
|
||||
|---|---------|---------|-----------------|--------|------|
|
||||
| 1 | | | | | collection-system-execution #n |
|
||||
| 1 | | | | | 收集系统-执行案 #n |
|
||||
| 2 | | | | | |
|
||||
| ... | | | | | |
|
||||
|
||||
@@ -240,6 +240,6 @@
|
||||
|
||||
### 与场景设计案的关系
|
||||
|
||||
- 场景设计案(`scene-vXX-<name>.md`)= **氛围愿景** — 描述感觉、氛围、叙事意图
|
||||
- 场景设计案(`场景-v<XX>-<名称>.md`)= **氛围愿景** — 描述感觉、氛围、叙事意图
|
||||
- 关卡执行案 = **空间精度搭建指南** — 坐标、尺寸、资产映射
|
||||
- 两者是互补关系,不是替代关系。程序搭建时两个文档都需要参考。
|
||||
|
||||
@@ -33,6 +33,7 @@ description: "Gitea 协作工具 — CLI 命令速查、认证机制、Assignee
|
||||
| 程序 | `dev` | game-dev |
|
||||
| 美术 | `artist` | (暂无独立 Agent) |
|
||||
| PM | `pm` | game-pm |
|
||||
| 审查 | `reviewer` | game-reviewer |
|
||||
|
||||
---
|
||||
|
||||
@@ -46,12 +47,13 @@ description: "Gitea 协作工具 — CLI 命令速查、认证机制、Assignee
|
||||
| 程序 | `ai-programmer` | `P-XXX` |
|
||||
| 美术 | `ai-artist` | `A-XXX` |
|
||||
| PM | `ai-pm` | (管理任务) |
|
||||
| 审查 | `ai-reviewer` | (审查操作,不拥有任务) |
|
||||
|
||||
---
|
||||
|
||||
## 4. 标签体系
|
||||
|
||||
14 个标准标签,通过 `node scripts/gitea.js label init` 初始化。
|
||||
19 个标准标签,通过 `node scripts/gitea.js label init` 初始化。
|
||||
|
||||
### status(任务状态)
|
||||
| 标签 | 含义 |
|
||||
@@ -60,22 +62,33 @@ description: "Gitea 协作工具 — CLI 命令速查、认证机制、Assignee
|
||||
| `status:ready` | 已就绪,可开始 |
|
||||
| `status:in-progress` | 进行中 |
|
||||
| `status:blocked` | 被阻塞 |
|
||||
| `status:in-review` | 审查中 |
|
||||
| `status:in-review` | 待审查(角色完成工作后设置) |
|
||||
| `status:done` | 已完成 |
|
||||
|
||||
### gate(质量门判定)
|
||||
### gate(质量门判定 — 旧标签,保留用于历史 Issue)
|
||||
| 标签 | 含义 |
|
||||
|------|------|
|
||||
| `gate:execution-pass` | 执行案质量门通过 |
|
||||
| `gate:execution-fail` | 执行案质量门未通过 |
|
||||
| `gate:tech-pass` | 程序设计质量门通过 |
|
||||
| `gate:tech-fail` | 程序设计质量门未通过 |
|
||||
| `gate:art-pass` | 美术资产验收门通过 |
|
||||
| `gate:art-fail` | 美术资产验收门未通过 |
|
||||
| `gate:impl-pass` | 程序实现验收门通过 |
|
||||
| `gate:impl-fail` | 程序实现验收门未通过 |
|
||||
| `gate:execution-pass/fail` | 执行案质量门 |
|
||||
| `gate:tech-pass/fail` | 程序设计质量门 |
|
||||
| `gate:art-pass/fail` | 美术资产验收门 |
|
||||
| `gate:impl-pass/fail` | 程序实现验收门 |
|
||||
|
||||
> **注**:旧 Issue 上可能仍有 `pipeline:*` 和 `role:*` 标签,这些已不再使用,保留不管即可。
|
||||
> 新工作流使用 `review:*` 标签替代 `gate:*`。旧 Issue 上的 gate 标签保留不动。
|
||||
|
||||
### review(审查结果 — 新标签)
|
||||
| 标签 | 含义 |
|
||||
|------|------|
|
||||
| `review:pass` | 审查已通过 ✅ |
|
||||
| `review:fail` | 审查未通过 ❌ |
|
||||
|
||||
### stage(开发阶段 — 新标签)
|
||||
| 标签 | 含义 |
|
||||
|------|------|
|
||||
| `stage:sketch` | 草图阶段任务 |
|
||||
| `stage:roadmap` | 总路线阶段任务 |
|
||||
| `stage:iteration` | 迭代阶段任务 |
|
||||
|
||||
> **注**:`pipeline:*` 和 `role:*` 标签已废弃并从 Gitea 上删除。如旧 Issue 上仍残留这些标签,忽略即可。
|
||||
|
||||
---
|
||||
|
||||
@@ -172,14 +185,16 @@ node scripts/gitea.js test
|
||||
| 概念 | Gitea 对应 | 说明 |
|
||||
|------|-----------|------|
|
||||
| 任务 (P-002) | Issue | 标题含任务 ID 前缀 |
|
||||
| 版本 (v0.1) | Milestone | 版本号即里程碑标题 |
|
||||
| 阶段/版本 | Milestone | 阶段名或版本号作为里程碑标题 |
|
||||
| 任务状态 | Label (`status:*`) | 6 种状态标签 |
|
||||
| 质量门结果 | Label (`gate:*`) | 8 种判定标签 |
|
||||
| 审查结果 | Label (`review:*`) | 通过/未通过 |
|
||||
| 开发阶段 | Label (`stage:*`) | 草图/总路线/迭代 |
|
||||
| 质量门结果 | Label (`gate:*`) | 旧标签,保留用于历史 Issue |
|
||||
| 角色归属 | Assignee | 通过 `--assignees` 指派 |
|
||||
| 流水线步骤 | Pipeline Tracker | Meta Issue 记录全局流水线状态 |
|
||||
| 当前进度 | Pipeline Tracker | Meta Issue 记录当前阶段和进度 |
|
||||
| 角色汇报 | Issue 评论 | 各角色在对应 Issue 下评论 |
|
||||
| 审查记录 | Issue 评论(摘要+链接)+ 本地 `docs/plans/reviews/` | 本地保留详细结构化文档 |
|
||||
| 任务归档 | close Issue | 不需要本地 archive 文件 |
|
||||
| 审查记录 | Issue 评论(摘要+链接)+ 本地 `docs/plans/reviews/` | 本地文件名格式 `<任务ID>-审查记录.md` |
|
||||
| 任务归档 | close Issue | 用户确认后由 PM 关闭 |
|
||||
|
||||
---
|
||||
|
||||
@@ -199,11 +214,34 @@ node scripts/gitea.js test
|
||||
|
||||
## 8. Pipeline Tracker
|
||||
|
||||
Pipeline Tracker 是一个 **Meta Issue**,用于记录当前版本的全局流水线状态。
|
||||
Pipeline Tracker 是一个 **Meta Issue**,用于记录项目当前阶段和进度。
|
||||
|
||||
- 初始化:`pipeline init`(阶段开始时执行一次)
|
||||
### Tracker 正文格式
|
||||
|
||||
```markdown
|
||||
# 流水线进度追踪
|
||||
|
||||
## 当前阶段
|
||||
草图阶段 / 总路线阶段 / 迭代阶段
|
||||
|
||||
## 当前里程碑
|
||||
- 名称: <milestone title>
|
||||
- 状态: open/closed
|
||||
- Open Issues: X
|
||||
- Closed Issues: Y
|
||||
|
||||
## 阶段历史
|
||||
| 阶段 | 里程碑 | 进入日期 | 完成日期 |
|
||||
|------|--------|----------|----------|
|
||||
| 草图阶段 | 草图阶段 | YYYY-MM-DD | — |
|
||||
```
|
||||
|
||||
### 相关命令
|
||||
|
||||
- 初始化:`pipeline init`
|
||||
- 查看状态:`pipeline status`
|
||||
- 更新步骤:`pipeline update <number> --step <1-7> --status "描述"`
|
||||
- 阶段转换时直接编辑 Issue 正文更新「当前阶段」和「阶段历史」
|
||||
|
||||
---
|
||||
|
||||
@@ -258,31 +296,15 @@ node scripts/gitea.js --role <role> issue comment <number> --body "## 修改汇
|
||||
- path/to/output"
|
||||
```
|
||||
|
||||
### PM 审查后
|
||||
### PM 确认关闭任务(审查通过后)
|
||||
|
||||
```bash
|
||||
# 发布审查摘要评论
|
||||
node scripts/gitea.js --role pm issue comment <number> --body "## 质量门审查 [通过✅/未通过❌]
|
||||
|
||||
**质量门**:[类型]
|
||||
**审查日期**:YYYY-MM-DD
|
||||
**审查记录**:docs/plans/reviews/<task-ID>-review.md
|
||||
|
||||
### 结论
|
||||
[一句话总结]
|
||||
|
||||
### 问题清单(不通过时)
|
||||
1. ..."
|
||||
|
||||
# 更新标签反映质量门结果
|
||||
node scripts/gitea.js --role pm issue labels <number> --set <gate-pass|gate-fail>,<status-*>,...
|
||||
|
||||
# 审查通过时关闭 Issue
|
||||
# 用户确认后关闭 Issue
|
||||
node scripts/gitea.js --role pm issue close <number> --body "## 任务完成 ✔
|
||||
|
||||
**完成日期**:YYYY-MM-DD
|
||||
**产出**:path/to/output
|
||||
**审查记录**:docs/plans/reviews/<task-ID>-review.md"
|
||||
**审查记录**:docs/plans/reviews/<task-ID>-审查记录.md"
|
||||
```
|
||||
|
||||
---
|
||||
@@ -292,17 +314,22 @@ node scripts/gitea.js --role pm issue close <number> --body "## 任务完成 ✔
|
||||
PM 在每次会话启动时按序执行:
|
||||
|
||||
```bash
|
||||
# 1. 读取当前里程碑
|
||||
node scripts/gitea.js --role pm milestone list --state open
|
||||
|
||||
# 2. 读取活跃任务
|
||||
node scripts/gitea.js --role pm issue list --milestone <当前版本>
|
||||
|
||||
# 3. 读取流水线状态
|
||||
# 1. 读取 Pipeline Tracker(确定当前阶段)
|
||||
node scripts/gitea.js --role pm pipeline status
|
||||
|
||||
# 4. 读取近期评论(按需)
|
||||
node scripts/gitea.js --role pm issue comments <number>
|
||||
# 2. 读取当前里程碑
|
||||
node scripts/gitea.js --role pm milestone list --state open
|
||||
|
||||
# 3. 读取活跃任务
|
||||
node scripts/gitea.js --role pm issue list --state open
|
||||
|
||||
# 4. 根据阶段调用对应技能:
|
||||
# 无进度 → project-initializer
|
||||
# 草图阶段 → stage-sketch
|
||||
# 总路线阶段 → stage-roadmap
|
||||
# 迭代阶段 → stage-iteration
|
||||
|
||||
# 5. 检查 review:pass 标签的 Issue → 请示用户是否关闭
|
||||
```
|
||||
|
||||
---
|
||||
@@ -312,30 +339,25 @@ node scripts/gitea.js --role pm issue comments <number>
|
||||
### 创建任务
|
||||
|
||||
```bash
|
||||
# 创建 Issue(必须指定 assignees)
|
||||
# 创建 Issue(必须指定 assignees 和 stage 标签)
|
||||
node scripts/gitea.js --role pm issue create \
|
||||
--title "D-001 交互系统执行案" \
|
||||
--body "## 任务信息\n- **任务ID**: D-001\n- **所属版本**: v0.1\n- **角色**: 策划\n- **预期产出**: docs/design/prototype/interaction-system-execution.md\n..." \
|
||||
--title "D-001 概念设计" \
|
||||
--body "## 任务信息\n- **任务ID**: D-001\n- **所属阶段**: 草图阶段\n- **角色**: 策划\n- **预期产出**: docs/design/概念草案.md\n..." \
|
||||
--milestone <id> \
|
||||
--assignees ai-designer
|
||||
|
||||
# 设置标签
|
||||
node scripts/gitea.js --role pm issue labels <number> --set <status-label-id>,<gate-label-id>
|
||||
# 设置标签(status + stage + role)
|
||||
node scripts/gitea.js --role pm issue labels <number> --set <status-label-id>,<stage-label-id>,<role-label-id>
|
||||
|
||||
# 设置依赖
|
||||
node scripts/gitea.js --role pm issue block <blocker_number> <new_number>
|
||||
```
|
||||
|
||||
### 关闭任务
|
||||
### 关闭任务(用户确认后)
|
||||
|
||||
```bash
|
||||
node scripts/gitea.js --role pm issue close <number> --body "## 任务完成 ✔\n**完成日期**: YYYY-MM-DD\n**产出**: path/to/output"
|
||||
```
|
||||
|
||||
### 更新流水线
|
||||
|
||||
```bash
|
||||
node scripts/gitea.js --role pm pipeline update <tracker_number> --step <1-7> --status "状态描述"
|
||||
# 审查通过 + 用户确认关闭后
|
||||
node scripts/gitea.js --role pm issue close <number> --body "## 任务完成 ✔\n**完成日期**: YYYY-MM-DD\n**产出**: path/to/output\n**审查记录**: docs/plans/reviews/<task-ID>-审查记录.md"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
---
|
||||
name: "level-builder"
|
||||
description: >
|
||||
白模关卡自动生成工具。当需要"搭建白模关卡"、"生成白模"、"Level Builder"、
|
||||
"白模搭建"、"自动摆放 Actor"、"解析搭建指南"、"parse build guide"时使用。
|
||||
从搭建指南 Markdown 自动解析、验证、在 UE5 编辑器中构建白模关卡并生成搭建报告。
|
||||
---
|
||||
|
||||
# 白模关卡自动生成工具 (Level Builder)
|
||||
|
||||
> 从搭建指南 Markdown → JSON 中间数据 → UE5 Actor 自动构建 + 搭建报告生成
|
||||
|
||||
## 触发条件
|
||||
|
||||
- **game-dev 白模搭建任务**:收到步骤⑥子任务 6a(白模搭建)时使用
|
||||
- **用户手动触发**:用户说"搭建白模"、"生成白模关卡"、"Level Builder"等
|
||||
- **修改关卡后重新生成**:搭建指南有更新,需要重新生成白模
|
||||
|
||||
---
|
||||
|
||||
## 工具位置
|
||||
|
||||
```
|
||||
tools/level-builder/
|
||||
main.py # CLI 入口
|
||||
parsers/
|
||||
markdown_table.py # 通用 Markdown 表格解析
|
||||
build_guide_parser.py # 搭建指南解析器(核心)
|
||||
config/
|
||||
asset_mapping.py # 37 种 BSP 资产 → UE5 路径
|
||||
material_mapping.py # 13 种标识色材质路径
|
||||
zone_config.py # 7 区域定义 + Outliner 结构
|
||||
substitution_rules.py # Primitives 替代规则
|
||||
builder/
|
||||
scene_builder.py # UE5 Python API Actor 构建
|
||||
validation/
|
||||
asset_validator.py # 离线资产验证
|
||||
report/
|
||||
report_generator.py # 搭建报告生成
|
||||
output/ # 生成物目录
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 前置条件
|
||||
|
||||
1. **PythonScriptPlugin 已启用** — `ChildHood.uproject` 中已配置
|
||||
2. **BlockingStarterPack 已导入** — `Content/BlockingStarterPack/` 目录存在
|
||||
3. **搭建指南已就绪** — `docs/technical/关卡-v<XX>-搭建指南.md` 存在
|
||||
|
||||
---
|
||||
|
||||
## 使用流程
|
||||
|
||||
### 第一步:解析搭建指南(离线,Claude 可执行)
|
||||
|
||||
将搭建指南 Markdown 解析为 JSON 中间数据:
|
||||
|
||||
```bash
|
||||
cd tools/level-builder
|
||||
python main.py parse -i ../../docs/technical/关卡-v01-搭建指南.md -o output/level_v01.json
|
||||
```
|
||||
|
||||
**预期输出**:
|
||||
```
|
||||
[INFO] level_builder: 解析搭建指南: ...
|
||||
[INFO] level_builder: 解析完成: 265 个 Actor
|
||||
[INFO] level_builder: 按类别: {'STR': 123, 'PROP': 53, ...}
|
||||
[INFO] level_builder: 唯一资产数: 37
|
||||
```
|
||||
|
||||
**生成文件**: `output/level_v01.json`
|
||||
|
||||
### 第二步:验证(离线,Claude 可执行)
|
||||
|
||||
验证 JSON 中所有资产引用是否有效:
|
||||
|
||||
```bash
|
||||
python main.py validate -i output/level_v01.json
|
||||
```
|
||||
|
||||
**预期输出**:
|
||||
```
|
||||
[INFO] level_builder: ✅ 验证通过!
|
||||
[INFO] level_builder: 有效 Actor: 265/265
|
||||
```
|
||||
|
||||
**生成文件**: `output/validation_result.json`
|
||||
|
||||
### 第三步:UE5 构建
|
||||
|
||||
有两种方式,**推荐使用命令行方式**(全自动,无需手动操作编辑器):
|
||||
|
||||
#### 方式 A:命令行一键构建(推荐)
|
||||
|
||||
```bat
|
||||
REM 从项目根目录运行
|
||||
tools\level-builder\build_level.bat
|
||||
```
|
||||
|
||||
**高级用法**:
|
||||
```bat
|
||||
build_level.bat --json output/v02.json --map LVL_SuburbanV02 # 指定 JSON 和关卡名
|
||||
build_level.bat --no-quit # 构建后保留编辑器
|
||||
build_level.bat --no-null-rhi # 显示编辑器窗口(调试)
|
||||
build_level.bat --dry-run # 只打印命令不执行
|
||||
```
|
||||
|
||||
**环境变量**:
|
||||
- `UE5_ROOT` — UE5 安装目录(未设置时自动检测 5.7/5.6 常见路径)
|
||||
|
||||
**预期输出**:
|
||||
```
|
||||
[LevelBuilder] 加载 265 个 Actor 定义
|
||||
[LevelBuilder] SUCCESS: 265 | FAILED: 0 | TOTAL: 265
|
||||
[LevelBuilder] ✅ 关卡已保存: /Game/Maps/LVL_SuburbanV01
|
||||
```
|
||||
|
||||
**生成文件**:
|
||||
- `Content/Maps/LVL_SuburbanV01.umap` — UE5 关卡文件
|
||||
- `tools/level-builder/output/build_log.json` — 构建日志
|
||||
|
||||
#### 方式 B:编辑器内手动执行
|
||||
|
||||
在 UE5 编辑器 Python 控制台中:
|
||||
|
||||
```
|
||||
方式 B1 — Output Log Python 控制台:
|
||||
exec(open(r"tools/level-builder/build_in_editor.py").read())
|
||||
|
||||
方式 B2 — File > Execute Python Script > 选择 build_in_editor.py
|
||||
|
||||
方式 B3 — CLI 方式(仅构建指定区域):
|
||||
exec(open(r"tools/level-builder/main.py").read())
|
||||
main(["build", "-i", "output/level_v01.json", "--zones", "Z1,Z3"])
|
||||
```
|
||||
|
||||
### 第四步:生成搭建报告(离线,Claude 可执行)
|
||||
|
||||
```bash
|
||||
python main.py report -i output/level_v01.json -l output/build_log.json -o output/搭建报告.md
|
||||
```
|
||||
|
||||
**生成文件**: `output/搭建报告.md`(Markdown 格式,含逐区域对照表)
|
||||
|
||||
---
|
||||
|
||||
## 输出说明
|
||||
|
||||
### JSON 中间数据结构
|
||||
|
||||
每个 Actor 的数据格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"actor_name": "LVL_Z1_STR_SouthWall_01",
|
||||
"asset_key": "Wall_400x300",
|
||||
"asset_type": "static_mesh",
|
||||
"category": "STR",
|
||||
"zone_id": "Z1",
|
||||
"location": [200, 0, 0],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1],
|
||||
"material": "MI_DevRed",
|
||||
"folder": "LVL_SuburbanV01/Z1_家/Structure",
|
||||
"note": ""
|
||||
}
|
||||
```
|
||||
|
||||
特殊 asset_type:
|
||||
- `player_start` — PlayerStart(重生点)
|
||||
- `directional_light` — DirectionalLight(太阳光)
|
||||
- `sky_light` — SkyLight(天空光)
|
||||
- `point_light` — PointLight(点光源)
|
||||
|
||||
### Actor 命名规范
|
||||
|
||||
`LVL_<ZoneID>_<Category>_<Description>`
|
||||
|
||||
| Category | 含义 | 示例 |
|
||||
|----------|------|------|
|
||||
| STR | 结构体 | `LVL_Z1_STR_SouthWall_01` |
|
||||
| PROP | 布景 | `LVL_Z3_PROP_StreetLamp_01` |
|
||||
| INTR | 交互物 | `LVL_Z1_INTR_Fridge_01` |
|
||||
| COLL | 收集品 | `LVL_Z3_COLL_GlassBead_01` |
|
||||
| BND | 边界 | `LVL_BND_East_01` |
|
||||
| LGT | 光源 | `LVL_LGT_DirectionalLight` |
|
||||
|
||||
### Outliner 文件夹结构
|
||||
|
||||
```
|
||||
LVL_SuburbanV01/
|
||||
Z1_家/
|
||||
Structure/
|
||||
Props/
|
||||
Interactables/
|
||||
Collectibles/
|
||||
Z2_小卖部/
|
||||
...
|
||||
Global/
|
||||
Boundaries/
|
||||
Lighting/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 新版本适配
|
||||
|
||||
当有新版本的关卡执行案时,需要:
|
||||
|
||||
1. **策划产出执行案** → `docs/design/<stage>/关卡-v<XX>-<名称>-执行案.md`
|
||||
2. **程序产出搭建指南** → `docs/technical/关卡-v<XX>-搭建指南.md`
|
||||
- 搭建指南格式参照 `docs/technical/关卡-v01-搭建指南.md`
|
||||
- 包含:资产映射表 + Phase A(地面) + Phase B(边界) + Phase C(逐区域) + Phase D(光照)
|
||||
3. **运行 Level Builder** → 按上述四步流程执行
|
||||
|
||||
### 如果需要添加新 BSP 资产
|
||||
|
||||
编辑 `config/asset_mapping.py`,添加条目:
|
||||
|
||||
```python
|
||||
"新资产名": {
|
||||
"path": f"{MESH_ROOT}/分类目录/SM_资产名.SM_资产名",
|
||||
"default_size": (宽, 深, 高), # cm
|
||||
"category": "wall|floor|stair|door|primitive|...",
|
||||
},
|
||||
```
|
||||
|
||||
### 如果需要添加新区域
|
||||
|
||||
编辑 `config/zone_config.py`,在 `ZONES` 字典中添加区域定义。
|
||||
|
||||
---
|
||||
|
||||
## 故障排查
|
||||
|
||||
| 问题 | 原因 | 解决 |
|
||||
|------|------|------|
|
||||
| `No module named 'unreal'` | Stage 2 在编辑器外运行 | 只在 UE5 编辑器 Python 控制台中执行 build 命令 |
|
||||
| 资产加载失败 (spawn returned None) | BSP 资产未导入或路径错误 | 检查 `Content/BlockingStarterPack/` 是否存在 |
|
||||
| Actor 数量与预期不符 | 搭建指南格式变化 | 检查 `parsers/build_guide_parser.py` 的解析逻辑 |
|
||||
| PythonScriptPlugin 未加载 | .uproject 修改后未重启编辑器 | 重启 UE5 编辑器 |
|
||||
| 坐标表达式解析失败 | `200+400n` 格式的范围行 | 检查 `_expand_range_row` 和 `_eval_coord_expr` |
|
||||
|
||||
---
|
||||
|
||||
## 配置参考
|
||||
|
||||
### 区域一览
|
||||
|
||||
| Zone ID | 名称 | 原点 | 尺寸 | 标识色 |
|
||||
|---------|------|------|------|--------|
|
||||
| Z1 | 家 | (0,0,0) | 1000×800 | Red |
|
||||
| Z2 | 小卖部 | (1000,200,0) | 600×600 | Orange |
|
||||
| Z3 | 主街道 | (0,800,0) | 5000×500 | Gray |
|
||||
| Z4 | 空地 | (3500,1300,0) | 1500×1200 | Brown |
|
||||
| Z5 | 巷子 | (2000,0,0) | 600×800 | Green |
|
||||
| Z6 | 水塘边 | (5500,0,0) | 1500×1000 | Blue |
|
||||
| Z7 | 老居民楼 | (5000,1300,0) | 1500×1200 | Yellow |
|
||||
|
||||
### 搭建报告发布
|
||||
|
||||
搭建报告需要双轨发布:
|
||||
1. **本地文件**: `docs/technical/关卡-v<XX>-搭建报告.md`
|
||||
2. **Issue 评论**: 在对应任务 Issue 下以评论形式发布(供 PM 和策划直接查看)
|
||||
|
||||
---
|
||||
|
||||
## 未来扩展
|
||||
|
||||
- **执行案解析器** — 直接从 `关卡-v<XX>-<名称>-执行案.md` 解析,跳过手动搭建指南
|
||||
- **蓝图支持** — 除 StaticMesh 外支持 Blueprint Actor 放置
|
||||
- **碰撞预设** — 自动为不同 Category 设置碰撞 Profile
|
||||
- **LOD 标记** — 为远距离布景标记 LOD 策略
|
||||
@@ -0,0 +1,137 @@
|
||||
---
|
||||
name: "project-initializer"
|
||||
description: "项目初始化技能 — 检查版本管理/项目管理配置、初始化 Gitea 标签/里程碑/任务。仅在项目无进度时由 PM 调用。"
|
||||
---
|
||||
|
||||
# 项目初始化
|
||||
|
||||
> 本技能处理项目冷启动。在 PM 上下文中运行,完成后 PM 直接感知初始化结果。
|
||||
> 仅在无 Pipeline Tracker 或无里程碑时触发。
|
||||
|
||||
---
|
||||
|
||||
## 初始化流程
|
||||
|
||||
### 步骤 1:版本管理确认
|
||||
|
||||
逐项确认以下内容:
|
||||
|
||||
1. **SSH 仓库地址**:确认 `git remote -v` 中的仓库地址正确
|
||||
2. **角色 Gitea 账号**:确认以下账号存在且可访问
|
||||
- `starmen1265`(用户)
|
||||
- `ai-pm`
|
||||
- `ai-designer`
|
||||
- `ai-programmer`
|
||||
- `ai-artist`
|
||||
- `ai-reviewer`
|
||||
3. **SSH key 配置**:运行 `ssh -T git@micecola.top -p 2223` 测试连接
|
||||
- 失败 → 引导用户生成并配置 SSH key
|
||||
|
||||
### 步骤 2:项目管理配置
|
||||
|
||||
确认以下配置就绪:
|
||||
|
||||
1. **Token 文件**:`scripts/.gitea-tokens.json` 存在且包含所有角色 token
|
||||
- `pm`、`designer`、`dev`、`artist`、`reviewer`
|
||||
2. **Token 文件已忽略**:`.gitignore` 中包含 `scripts/.gitea-tokens.json`
|
||||
3. **Gitea CLI 可用**:`node scripts/gitea.js --role pm test` 运行通过
|
||||
|
||||
缺失时引导用户配置。
|
||||
|
||||
### 步骤 3:现有代码与需求检查
|
||||
|
||||
**情况 A:有现有代码和需求**
|
||||
- 探索 `docs/` 和 `Content/` 目录结构
|
||||
- 读取现有设计文档
|
||||
- 生成迁移方案:
|
||||
- 哪些文档可直接映射到新阶段
|
||||
- 哪些需要拆分或重写
|
||||
- 哪些可以作为已有产出直接关闭任务
|
||||
- 将迁移方案提交用户确认后执行
|
||||
|
||||
**情况 B:无现有代码(全新项目)**
|
||||
- 建议用户手动准备开发环境:
|
||||
1. 安装 UE 5.7.4
|
||||
2. 创建第三人称/第一人称/顶视角模板项目
|
||||
3. 添加插件:BlockoutStarterPack、LowpolyCharacter
|
||||
4. 启用 PythonEditorPlugins 插件
|
||||
- 等待用户确认环境就绪
|
||||
|
||||
### 步骤 4:Gitea 初始化
|
||||
|
||||
依次执行(所有操作需用户确认后再执行):
|
||||
|
||||
```bash
|
||||
# 1. 创建标准标签
|
||||
node scripts/gitea.js --role pm label init
|
||||
|
||||
# 2. 创建 Pipeline Tracker Issue
|
||||
node scripts/gitea.js --role pm issue create \
|
||||
--title "Pipeline Tracker" \
|
||||
--body "# 流水线进度追踪
|
||||
|
||||
## 当前阶段
|
||||
草图阶段
|
||||
|
||||
## 当前里程碑
|
||||
- 名称: 草图阶段
|
||||
- 状态: open
|
||||
- Open Issues: 1
|
||||
- Closed Issues: 0
|
||||
|
||||
## 阶段历史
|
||||
| 阶段 | 里程碑 | 进入日期 | 完成日期 |
|
||||
|------|--------|----------|----------|
|
||||
| 草图阶段 | 草图阶段 | $(date +%Y-%m-%d) | — |" \
|
||||
--labels "stage:sketch,role:pm,status:in-progress"
|
||||
|
||||
# 3. 创建草图阶段里程碑
|
||||
node scripts/gitea.js --role pm milestone create \
|
||||
--title "草图阶段" \
|
||||
--description "概念设计与技术可行性验证"
|
||||
|
||||
# 4. 创建第一个任务:概念设计
|
||||
node scripts/gitea.js --role pm issue create \
|
||||
--title "D-001 概念设计" \
|
||||
--body "## 任务信息
|
||||
|
||||
- **任务ID**: D-001
|
||||
- **所属阶段**: 草图阶段
|
||||
- **角色**: 策划
|
||||
|
||||
## 预期产出
|
||||
|
||||
产出概念草案 \`docs/design/概念草案.md\`,包含以下 7 个必填字段:
|
||||
1. 一句话概述(这是什么游戏)
|
||||
2. 核心玩法(玩家做什么)
|
||||
3. 美术风格
|
||||
4. 目标平台
|
||||
5. 灵感来源
|
||||
6. 核心假设(为什么好玩)
|
||||
7. 范围约束(MVP 包含什么)
|
||||
|
||||
## 质量标准
|
||||
|
||||
- 每个字段有明确的、不含糊的描述
|
||||
- 美术风格有参考图或参考作品
|
||||
- 核心假设可验证(能通过原型验证)" \
|
||||
--milestone "草图阶段" \
|
||||
--labels "stage:sketch,role:design,status:ready" \
|
||||
--assignees "ai-designer"
|
||||
```
|
||||
|
||||
### 步骤 5:确认完成
|
||||
|
||||
初始化完成后向用户报告:
|
||||
- ✅ 版本管理配置状态
|
||||
- ✅ 项目管理配置状态
|
||||
- ✅ Gitea 初始化结果(标签数、Pipeline Tracker Issue 编号、里程碑、首个任务编号)
|
||||
- 📋 建议下一步:启动策划角色开始概念设计
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 每一步都需要用户确认后才执行,不自动推进
|
||||
- 如果中途失败(如 Gitea 连接失败),报告具体错误并建议修复方案
|
||||
- 初始化完成后,PM 进入草图阶段,后续由 stage-sketch 技能接管
|
||||
@@ -1,19 +1,19 @@
|
||||
---
|
||||
name: "quality-gate-review"
|
||||
description: "质量门审查技能 — 4 道质量门标准、阶段对应标准、审查流程、强制操作清单"
|
||||
description: "审查标准技能 — 4 道质量门标准、阶段对应标准、审查流程、强制操作清单。供 game-reviewer 子代理使用。"
|
||||
---
|
||||
|
||||
# 质量门审查
|
||||
# 审查标准
|
||||
|
||||
> 本技能定义 4 道质量门的审查标准、阶段对应标准和审查后强制操作。
|
||||
> 供 game-reviewer agent、game-pm agent 及各工种自审使用。
|
||||
> 供 **game-reviewer 子代理**使用——角色完成工作后启动 reviewer 子代理,reviewer 按此标准审查。
|
||||
> 项目管理工具操作细节参见 `.claude/skills/gitea-collab.md`。
|
||||
|
||||
---
|
||||
|
||||
## 审查流程
|
||||
|
||||
每次质量门审查必须严格按以下步骤执行:
|
||||
每次审查必须严格按以下步骤执行:
|
||||
|
||||
### 1. 读取交付物
|
||||
- 从对应 Issue 的评论中获取产出文件路径
|
||||
@@ -24,21 +24,23 @@ description: "质量门审查技能 — 4 道质量门标准、阶段对应标
|
||||
- 根据当前开发阶段选择对应标准(原型 vs 批量生产,见"阶段对应标准")
|
||||
|
||||
### 3. 写审查记录(本地文件)
|
||||
- 路径:`docs/plans/reviews/<task-ID>-review.md`
|
||||
- 路径:`docs/plans/reviews/<task-ID>-审查记录.md`
|
||||
- 格式见下方"审查记录格式"
|
||||
|
||||
### 4. Issue 评论(强制)
|
||||
- **无论通过或不通过,都必须在对应 Issue 上发布审查结论评论**
|
||||
- 使用 `--role reviewer` 操作
|
||||
- 评论中标注审查者身份:`审查 Agent (ai-reviewer)`
|
||||
- 无论通过或不通过,都必须在对应 Issue 上发布审查结论评论
|
||||
- 通过:摘要 + 通过结论
|
||||
- 不通过:摘要 + 具体问题清单
|
||||
|
||||
### 5. 更新标签
|
||||
- 通过:设置 `gate:*-pass` 标签,移除 `gate:*-fail`(如有)
|
||||
- 不通过:设置 `gate:*-fail` 标签,移除 `gate:*-pass`(如有)
|
||||
- 通过:设置 `review:pass` 标签,移除 `status:in-review`
|
||||
- 不通过:设置 `review:fail` 标签,移除 `status:in-review`,改为 `status:blocked`
|
||||
|
||||
### 6. 通过时关闭 Issue(强制)
|
||||
- **审查通过时必须关闭 Issue**,附完成说明
|
||||
- 不通过时 Issue 保持打开,等待修改
|
||||
### 6. 返回结果给启动角色
|
||||
- 不关闭 Issue(关闭决策权在用户)
|
||||
- 将审查结果返回给启动你的角色,由该角色向用户汇报
|
||||
|
||||
---
|
||||
|
||||
@@ -58,8 +60,6 @@ description: "质量门审查技能 — 4 道质量门标准、阶段对应标
|
||||
|
||||
> 关卡执行案的质量门**仅做资产存在验证**。空间精度、比例、跨文档一致性等维度的准确性由程序在白模搭建过程中验证——如果程序在 Issue 中汇报了搭建异常,审查者将异常汇总并汇报给用户确认,不自行判断通过或不通过。
|
||||
|
||||
标签:`gate:execution-pass` / `gate:execution-fail`
|
||||
|
||||
### 质量门 2:程序设计质量门(④→⑤)
|
||||
|
||||
检查维度:
|
||||
@@ -70,8 +70,6 @@ description: "质量门审查技能 — 4 道质量门标准、阶段对应标
|
||||
- **技术风险**:是否识别?
|
||||
- **工作量与拆分建议**:是否给出工作量估算?拆分方式是否合理,每个子任务可独立验证?拆分后的任务集合是否完整覆盖实现方案?
|
||||
|
||||
标签:`gate:tech-pass` / `gate:tech-fail`
|
||||
|
||||
### 质量门 3:美术资产验收门(⑤→⑥)
|
||||
|
||||
检查维度:
|
||||
@@ -80,8 +78,6 @@ description: "质量门审查技能 — 4 道质量门标准、阶段对应标
|
||||
- **资产规格**:尺寸和格式是否符合需求?
|
||||
- **缺失项处理**:未到位资产是否有明确处理方案?
|
||||
|
||||
标签:`gate:art-pass` / `gate:art-fail`
|
||||
|
||||
### 质量门 4:程序实现验收门(⑥→⑦)
|
||||
|
||||
检查维度:
|
||||
@@ -89,10 +85,8 @@ description: "质量门审查技能 — 4 道质量门标准、阶段对应标
|
||||
- **执行案覆盖度**:最终实现是否覆盖所有行为规则?
|
||||
- **资产使用确认**:美术资产是否正确集成?
|
||||
- **偏差处理**:
|
||||
- **轻微偏差**(不影响核心行为)→ 记录,在⑦复盘时同步文档
|
||||
- **重大偏差**(影响核心行为)→ 建议回退到③或开启新版本,用户决定
|
||||
|
||||
标签:`gate:impl-pass` / `gate:impl-fail`
|
||||
- **轻微偏差**(不影响核心行为)→ 记录,在复盘时同步文档
|
||||
- **重大偏差**(影响核心行为)→ 建议回退或开启新版本,用户决定
|
||||
|
||||
---
|
||||
|
||||
@@ -109,15 +103,15 @@ description: "质量门审查技能 — 4 道质量门标准、阶段对应标
|
||||
|
||||
## 判定规则
|
||||
|
||||
- **通过** → 发布通过评论 + 设置 pass 标签 + **关闭 Issue**
|
||||
- **不通过** → 发布不通过评论(附问题清单)+ 设置 fail 标签 + Issue 保持打开
|
||||
- **通过** → 发布通过评论 + 设置 `review:pass` 标签 + 返回结果(不关闭 Issue)
|
||||
- **不通过** → 发布不通过评论(附问题清单)+ 设置 `review:fail` 标签 + 设置 `status:blocked` + 返回结果
|
||||
- **争议** → 用户最终裁决;审查者提供明确证据和具体文档位置
|
||||
|
||||
---
|
||||
|
||||
## 审查记录格式
|
||||
|
||||
文件路径:`docs/plans/reviews/<task-ID>-review.md`
|
||||
文件路径:`docs/plans/reviews/<task-ID>-审查记录.md`
|
||||
|
||||
```markdown
|
||||
# [任务 ID] 审查报告
|
||||
@@ -126,6 +120,7 @@ description: "质量门审查技能 — 4 道质量门标准、阶段对应标
|
||||
**审查对象**:[任务名] — [产出文档路径]
|
||||
**宏观阶段**:[原型 / 批量生产]
|
||||
**质量门类型**:[执行案 / 程序设计 / 美术资产 / 程序实现]
|
||||
**审查者**:审查 Agent (ai-reviewer)
|
||||
|
||||
## 检查结果
|
||||
|
||||
@@ -148,8 +143,9 @@ description: "质量门审查技能 — 4 道质量门标准、阶段对应标
|
||||
|
||||
审查完成后,**必须**逐项确认以下操作:
|
||||
|
||||
- [ ] 在 `docs/plans/reviews/<task-ID>-review.md` 写了审查记录
|
||||
- [ ] 在对应 Issue 上发布了审查结论评论(通过/不通过 + 摘要)
|
||||
- [ ] 更新了 Issue 的 gate:* 标签(通过→pass,不通过→fail)
|
||||
- [ ] **通过时**:关闭 Issue 并附完成说明
|
||||
- [ ] **不通过时**:评论中包含具体问题清单,Issue 保持打开
|
||||
- [ ] 在 `docs/plans/reviews/<task-ID>-审查记录.md` 写了审查记录
|
||||
- [ ] 使用 `--role reviewer` 在对应 Issue 上发布了审查结论评论(标注审查者身份)
|
||||
- [ ] 更新了 Issue 的标签(通过→`review:pass`,不通过→`review:fail` + `status:blocked`)
|
||||
- [ ] 将审查结果返回给了启动你的角色
|
||||
- [ ] **不关闭 Issue**(关闭决策权在用户)
|
||||
- [ ] **不通过时**:评论中包含具体问题清单
|
||||
|
||||
@@ -21,10 +21,10 @@ description: >
|
||||
|
||||
按序阅读以下文档,获取完整的上下文信息:
|
||||
|
||||
1. **执行文档** (`docs/design/<stage>/<system-name>-execution.md`) — 行为规则细节(主要输入)
|
||||
2. **系统设计文档** (`docs/design/<stage>/<system-name>.md`) — 全局上下文
|
||||
1. **执行文档** (`docs/design/<stage>/<系统名>-执行案.md`) — 行为规则细节(主要输入)
|
||||
2. **系统设计文档** (`docs/design/<stage>/<系统名>.md`) — 全局上下文
|
||||
3. **配置表** (`GameConfig/`) — 可调参数
|
||||
4. **概念文档** (`docs/design/concept.md`) — 方向参考(需要理解设计意图时查阅)
|
||||
4. **概念文档** (`docs/design/概念草案.md`) — 方向参考(需要理解设计意图时查阅)
|
||||
|
||||
---
|
||||
|
||||
@@ -134,7 +134,7 @@ description: >
|
||||
|
||||
## 输出
|
||||
|
||||
最终产出为技术设计文档,保存到 `docs/technical/<system-name>-tech.md`。
|
||||
最终产出为技术设计文档,保存到 `docs/technical/<系统名>-技术方案.md`。
|
||||
|
||||
文档必须遵循 `references/output-template.md` 中定义的标准模板结构。8 步分析的结果整合到模板的对应章节中,同时保留现有的必要章节(美术资源需求、数据流、可调参数汇总、文件结构)。
|
||||
|
||||
|
||||
@@ -280,11 +280,11 @@ flowchart TD
|
||||
|
||||
### 3.3 项目实际案例参考
|
||||
|
||||
**需要时序图的特性**(从现有 `interaction-system-tech.md` 提取):
|
||||
**需要时序图的特性**(从现有 `交互系统-技术方案.md` 提取):
|
||||
- 完整交互流程(Enter → AnimLock → Active → Exit)
|
||||
- 拾取流程(交互系统 → 物品系统 → 收集系统)
|
||||
|
||||
**不需要时序图的特性**(从现有 `player-controller-tech.md` 提取):
|
||||
**不需要时序图的特性**(从现有 `玩家控制器-技术方案.md` 提取):
|
||||
- SpringArm 参数配置(纯构造函数设置)
|
||||
- CMC 移动参数调整(纯配置)
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
| 示例 | 来源文档 |
|
||||
|------|---------|
|
||||
| 交互系统 | `docs/technical/interaction-system-tech.md` |
|
||||
| 角色控制 | `docs/technical/player-controller-tech.md` |
|
||||
| 收集系统 | `docs/technical/collection-system-tech.md` |
|
||||
| 交互系统 | `docs/technical/交互系统-技术方案.md` |
|
||||
| 角色控制 | `docs/technical/玩家控制器-技术方案.md` |
|
||||
| 收集系统 | `docs/technical/收集系统-技术方案.md` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
# <系统名称> 技术设计文档(<版本号> <阶段>)
|
||||
|
||||
> 任务ID: <任务编号>
|
||||
> 对应执行案: `<执行案文件路径>`
|
||||
> 对应执行案: `docs/design/<stage>/<系统名>-执行案.md`
|
||||
> 关联系统: <上游/下游/平行系统列表>
|
||||
> 阶段: 原型 / 批量生产 / 测试
|
||||
> 日期: <YYYY-MM-DD>
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
---
|
||||
name: "stage-iteration"
|
||||
description: "迭代阶段技能 — 版本迭代开发管理。插件检查→设计补充→执行案→需求分析→开发→优化→整合→修复。"
|
||||
---
|
||||
|
||||
# 迭代阶段
|
||||
|
||||
> 当 PM 检测到 Pipeline Tracker 标记为「迭代阶段」时调用本技能。
|
||||
> 目标:按版本迭代推进开发,每个迭代完成一个可玩版本。
|
||||
|
||||
---
|
||||
|
||||
## 阶段概述
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 目标 | 将设计转化为可玩的迭代版本 |
|
||||
| 产出 | 可运行的 UE5 版本 |
|
||||
| 参与角色 | 策划(执行案)、程序(需求分析+开发) |
|
||||
| 完成标志 | Bug 修复任务关闭 |
|
||||
|
||||
---
|
||||
|
||||
## 任务序列
|
||||
|
||||
每个迭代版本按以下顺序逐步创建任务。**PM 每次激活时只创建下一个可创建的任务**,不要一次全部创建。
|
||||
|
||||
### 任务列表(按创建顺序)
|
||||
|
||||
| 编号 | 任务名称 | 角色 | 前置依赖 | 首次迭代 |
|
||||
|------|---------|------|---------|---------|
|
||||
| 1.4.0 | 插件资产检查与准备 | 程序 | 无 | 仅首次 |
|
||||
| 1.4.0.5 | 设计案迭代补充 | 策划 | 1.4.0(首次)或无 | 可选 |
|
||||
| 1.4.1 | 策划执行案 | 策划 | 1.4.0.5 | 必选 |
|
||||
| 1.4.2 | 程序需求分析 | 程序 | 1.4.1 | 必选 |
|
||||
| 1.4.3.1 | 关卡搭建 | 程序 | 1.4.2 | 有场景时 |
|
||||
| 1.4.3.2 | 系统开发 | 程序 | 1.4.2 | 必选 |
|
||||
| 1.4.3.3 | 优化意见 | 程序 | 1.4.3.1 + 1.4.3.2 | 必选 |
|
||||
| 1.4.3.5 | 资产接入整合 | 程序 | 1.4.3.3 | 必选 |
|
||||
| 1.4.4 | Bug 修复 | 程序 | 1.4.3.5 | 必选 |
|
||||
|
||||
---
|
||||
|
||||
### 各任务详细说明
|
||||
|
||||
#### 1.4.0 插件资产检查与准备(仅首次迭代)
|
||||
|
||||
```bash
|
||||
--title "P-XXX 插件资产检查与准备"
|
||||
--body "## 任务信息
|
||||
- **任务ID**: P-XXX
|
||||
- **所属阶段**: 迭代阶段
|
||||
- **角色**: 程序
|
||||
|
||||
## 预期产出
|
||||
插件资产引用表 \`docs/technical/插件资产引用表.md\`
|
||||
|
||||
## 检查内容
|
||||
1. BlockoutStarterPack — 白模资产完整性检查
|
||||
- 路径: Content/BlockingStarterPack/
|
||||
- 参考: .claude/refs/blockout-asset-reference.md
|
||||
2. Lowpoly 角色/场景资源
|
||||
- 路径: Content/Lowpoly*/ 或对应插件目录
|
||||
3. 人物动作相关插件(动画蓝图、骨骼等)
|
||||
4. 其他已安装插件
|
||||
|
||||
## 产出格式
|
||||
对每个插件/资产包:
|
||||
- 插件名称和版本
|
||||
- 安装路径
|
||||
- 关键资产列表(按类型分类:网格体、材质、动画、音效等)
|
||||
- 可用性评估(完整/部分可用/缺失)
|
||||
- 对开发的支持程度说明
|
||||
|
||||
## 目的
|
||||
生成资产引用表,使开发过程可以脱离 3D 美术进行。"
|
||||
--labels "stage:iteration,role:dev,status:ready"
|
||||
--assignees "ai-programmer"
|
||||
```
|
||||
|
||||
#### 1.4.0.5 设计案迭代补充(可选)
|
||||
|
||||
```bash
|
||||
--title "D-XXX 设计案迭代补充 — <版本号>"
|
||||
--body "## 任务信息
|
||||
- **任务ID**: D-XXX
|
||||
- **所属阶段**: 迭代阶段
|
||||
- **角色**: 策划
|
||||
|
||||
## 预期产出
|
||||
对现有设计文档的迭代补充,可能包括:
|
||||
- 新增系统设计细节
|
||||
- 调整已有系统参数
|
||||
- 补充关卡设计细节
|
||||
- 更新 UI/音频设计
|
||||
|
||||
## 说明
|
||||
每个迭代版本可能对总路线阶段的设计有调整。
|
||||
策划根据本版本的范围和目标,检查并补充设计文档。
|
||||
如果设计文档无需调整,此任务可以跳过(直接关闭并说明'无需补充')。"
|
||||
--labels "stage:iteration,role:design,status:ready"
|
||||
--assignees "ai-designer"
|
||||
```
|
||||
|
||||
#### 1.4.1 策划执行案
|
||||
|
||||
```bash
|
||||
--title "D-XXX 策划执行案 — <版本号>"
|
||||
--body "## 任务信息
|
||||
- **任务ID**: D-XXX
|
||||
- **所属阶段**: 迭代阶段
|
||||
- **角色**: 策划
|
||||
- **依赖**: D-XXX 设计案迭代补充完成
|
||||
|
||||
## 预期产出
|
||||
根据设计案为本版本写策划执行案:
|
||||
- 系统执行案(每份系统一份,如 D-XXXa 交互系统执行案)
|
||||
- 关卡执行案(如涉及场景搭建)
|
||||
|
||||
## 执行案要求
|
||||
- 只写玩家可感知的行为规则
|
||||
- 不包含引擎组件、代码架构、数据格式
|
||||
- 每条行为规则清晰无歧义
|
||||
- 边界情况有明确处理
|
||||
|
||||
## 参考文档
|
||||
- 设计案: docs/design/
|
||||
- 执行案模板: .claude/refs/level-execution-template.md(关卡执行案)"
|
||||
--labels "stage:iteration,role:design,status:backlog"
|
||||
--assignees "ai-designer"
|
||||
```
|
||||
|
||||
#### 1.4.2 程序需求分析
|
||||
|
||||
```bash
|
||||
--title "P-XXX 程序需求分析 — <版本号>"
|
||||
--body "## 任务信息
|
||||
- **任务ID**: P-XXX
|
||||
- **所属阶段**: 迭代阶段
|
||||
- **角色**: 程序
|
||||
- **依赖**: D-XXX 策划执行案完成
|
||||
|
||||
## 预期产出
|
||||
技术设计文档(使用 /requirements-analysis 技能)
|
||||
|
||||
## 工作方式
|
||||
基于策划执行案,使用 8 步需求分析技能产出技术设计文档:
|
||||
1. 特性点枚举
|
||||
2. 功能域图
|
||||
3. 类设计图
|
||||
4. 时序图(条件性)
|
||||
5. 核心数据结构
|
||||
6. 复杂接口描述(条件性)
|
||||
7. 技术风险
|
||||
8. 工作量估算
|
||||
|
||||
## 产出路径
|
||||
docs/technical/<系统名>-tech.md"
|
||||
--labels "stage:iteration,role:dev,status:backlog"
|
||||
--assignees "ai-programmer"
|
||||
```
|
||||
|
||||
#### 1.4.3.1 关卡搭建(有场景时)
|
||||
|
||||
```bash
|
||||
--title "P-XXX 关卡搭建 — <版本号>"
|
||||
--body "## 任务信息
|
||||
- **任务ID**: P-XXX
|
||||
- **所属阶段**: 迭代阶段
|
||||
- **角色**: 程序
|
||||
- **依赖**: P-XXX 程序需求分析完成
|
||||
|
||||
## 预期产出
|
||||
白模关卡文件 + 搭建报告
|
||||
|
||||
## 搭建流程
|
||||
参见 .claude/refs/level-execution-template.md 和 game-dev agent 的 Workflow B"
|
||||
--labels "stage:iteration,role:dev,status:backlog"
|
||||
--assignees "ai-programmer"
|
||||
```
|
||||
|
||||
#### 1.4.3.2 系统开发
|
||||
|
||||
```bash
|
||||
--title "P-XXX 系统开发 — <版本号>"
|
||||
--body "## 任务信息
|
||||
- **任务ID**: P-XXX
|
||||
- **所属阶段**: 迭代阶段
|
||||
- **角色**: 程序
|
||||
- **依赖**: P-XXX 程序需求分析完成
|
||||
|
||||
## 预期产出
|
||||
根据技术设计文档实现各系统代码
|
||||
|
||||
## 工作方式
|
||||
参见 game-dev agent 的 Workflow C"
|
||||
--labels "stage:iteration,role:dev,status:backlog"
|
||||
--assignees "ai-programmer"
|
||||
```
|
||||
|
||||
#### 1.4.3.3 优化意见
|
||||
|
||||
```bash
|
||||
--title "P-XXX 优化意见 — <版本号>"
|
||||
--body "## 任务信息
|
||||
- **任务ID**: P-XXX
|
||||
- **所属阶段**: 迭代阶段
|
||||
- **角色**: 程序
|
||||
- **依赖**: P-XXX 关卡搭建 + P-XXX 系统开发
|
||||
|
||||
## 预期产出
|
||||
优化意见报告,包含:
|
||||
- 性能分析(帧率、内存)
|
||||
- 关卡布局优化建议
|
||||
- 系统交互优化建议
|
||||
- 体验改进建议"
|
||||
--labels "stage:iteration,role:dev,status:backlog"
|
||||
--assignees "ai-programmer"
|
||||
```
|
||||
|
||||
#### 1.4.3.5 资产接入整合
|
||||
|
||||
```bash
|
||||
--title "P-XXX 资产接入整合 — <版本号>"
|
||||
--body "## 任务信息
|
||||
- **任务ID**: P-XXX
|
||||
- **所属阶段**: 迭代阶段
|
||||
- **角色**: 程序
|
||||
- **依赖**: P-XXX 优化意见完成
|
||||
|
||||
## 预期产出
|
||||
整合所有资产到最终关卡中,确保:
|
||||
- 白模资产正确替换(如有正式资产)
|
||||
- 所有系统正常交互
|
||||
- 关卡可完整游玩"
|
||||
--labels "stage:iteration,role:dev,status:backlog"
|
||||
--assignees "ai-programmer"
|
||||
```
|
||||
|
||||
#### 1.4.4 Bug 修复
|
||||
|
||||
```bash
|
||||
--title "P-XXX Bug修复 — <版本号>"
|
||||
--body "## 任务信息
|
||||
- **任务ID**: P-XXX
|
||||
- **所属阶段**: 迭代阶段
|
||||
- **角色**: 程序
|
||||
- **依赖**: P-XXX 资产接入整合完成
|
||||
|
||||
## 工作方式
|
||||
1. 完整游玩当前版本
|
||||
2. 记录发现的 Bug
|
||||
3. 按严重程度排序修复
|
||||
4. 验证修复后无回归"
|
||||
--labels "stage:iteration,role:dev,status:backlog"
|
||||
--assignees "ai-programmer"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 进度检查
|
||||
|
||||
每次 PM 激活时执行:
|
||||
|
||||
1. **读取当前迭代任务状态**:`issue list --milestone "迭代-vX.X" --state open`
|
||||
2. **判断当前焦点**:
|
||||
- 按 1.4.0 → 1.4.0.5 → 1.4.1 → 1.4.2 → 1.4.3.1/2 → 1.4.3.3 → 1.4.3.5 → 1.4.4 顺序
|
||||
- 找到第一个未完成的步骤 → 建议:启动对应角色完成该步骤
|
||||
- 如果某步骤的依赖未完成 → 建议:先完成依赖任务
|
||||
3. **只创建下一个可创建的任务**:
|
||||
- 已有任务进行中 → 不创建新任务
|
||||
- 所有前置依赖已完成 → 创建下一个任务并设为 `status:ready`
|
||||
4. **检查完成条件**:
|
||||
- 1.4.4 (Bug修复) 已关闭 → 触发迭代转换
|
||||
|
||||
---
|
||||
|
||||
## 迭代转换
|
||||
|
||||
**前置条件**:Bug 修复任务(1.4.4)已关闭。
|
||||
|
||||
**询问用户**:是否有下一个迭代版本?
|
||||
|
||||
### 情况 A:有下一个迭代
|
||||
|
||||
```bash
|
||||
# 1. 关闭当前迭代里程碑
|
||||
node scripts/gitea.js --role pm milestone update <当前里程碑ID> --state closed
|
||||
|
||||
# 2. 创建下一个迭代里程碑
|
||||
node scripts/gitea.js --role pm milestone create \
|
||||
--title "迭代-vX.X" \
|
||||
--description "第 N 个迭代版本:<简要描述>"
|
||||
|
||||
# 3. 从 1.4.0.5(设计补充)或 1.4.1(执行案)开始
|
||||
# 非首次迭代跳过 1.4.0(插件检查)
|
||||
|
||||
# 4. 更新 Pipeline Tracker
|
||||
```
|
||||
|
||||
### 情况 B:无下一个迭代,返回总路线
|
||||
|
||||
```bash
|
||||
# 1. 关闭当前迭代里程碑
|
||||
# 2. 重新打开或创建总路线阶段里程碑
|
||||
# 3. 更新 Pipeline Tracker 为「总路线阶段」
|
||||
# 4. 下次 PM 激活时将调用 stage-roadmap 技能
|
||||
```
|
||||
@@ -0,0 +1,183 @@
|
||||
---
|
||||
name: "stage-roadmap"
|
||||
description: "总路线阶段技能 — 全面设计文档产出与版本路径规划。管理总路线阶段任务创建和阶段转换。"
|
||||
---
|
||||
|
||||
# 总路线阶段
|
||||
|
||||
> 当 PM 检测到 Pipeline Tracker 标记为「总路线阶段」时调用本技能。
|
||||
> 目标:产出完整的游戏设计文档体系,规划版本迭代路径。
|
||||
|
||||
---
|
||||
|
||||
## 阶段概述
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 目标 | 建立"这个游戏长什么样"的完整蓝图 |
|
||||
| 产出 | 9 份设计文档 + 版本路径图 |
|
||||
| 参与角色 | 策划(全部文档) |
|
||||
| 完成标志 | 所有任务关闭 + 里程碑下无 open issues |
|
||||
|
||||
---
|
||||
|
||||
## 任务创建
|
||||
|
||||
### 检查已有任务
|
||||
|
||||
先通过 `issue list --milestone "总路线阶段" --state open` 检查现有任务,避免重复创建。
|
||||
|
||||
### 需要创建的任务(共 9 个)
|
||||
|
||||
**占位规则**:除系统设计文档(⑤)和关卡设计文档(⑥)必须完整内容外,其他文档允许暂时留空文件占位(包含标题、章节结构和「待填充」说明),后续迭代中逐步完善。
|
||||
|
||||
---
|
||||
|
||||
**任务 1:设计总索引文档** (D-XXX)
|
||||
|
||||
```bash
|
||||
--title "D-XXX 设计总索引文档"
|
||||
--body "## 任务信息
|
||||
- **任务ID**: D-XXX
|
||||
- **所属阶段**: 总路线阶段
|
||||
- **角色**: 策划
|
||||
- **优先级**: 最高(其他设计文档的索引基础)
|
||||
|
||||
## 预期产出
|
||||
设计总索引 \`docs/design/设计总索引.md\`,包含:
|
||||
- 所有设计文档的目录和链接
|
||||
- 文档间的依赖关系图
|
||||
- 当前完成状态追踪
|
||||
|
||||
## 占位规则
|
||||
此文档必须完整产出(不允许占位)。"
|
||||
--labels "stage:roadmap,role:design,status:ready"
|
||||
--assignees "ai-designer"
|
||||
```
|
||||
|
||||
**任务 2:世界观设计文档** (D-XXX) — 可占位
|
||||
|
||||
```
|
||||
--title "D-XXX 世界观设计"
|
||||
--body "...产出 docs/design/世界观设计.md
|
||||
占位规则:允许占位(标题+章节结构+待填充说明)"
|
||||
--labels "stage:roadmap,role:design,status:ready"
|
||||
```
|
||||
|
||||
**任务 3:剧情设计文档** (D-XXX) — 可占位
|
||||
|
||||
```
|
||||
--title "D-XXX 剧情设计"
|
||||
--body "...产出 docs/design/剧情设计.md
|
||||
占位规则:允许占位"
|
||||
--labels "stage:roadmap,role:design,status:ready"
|
||||
```
|
||||
|
||||
**任务 4:角色设计文档** (D-XXX) — 可占位
|
||||
|
||||
```
|
||||
--title "D-XXX 角色设计"
|
||||
--body "...产出 docs/design/角色设计.md
|
||||
占位规则:允许占位"
|
||||
--labels "stage:roadmap,role:design,status:ready"
|
||||
```
|
||||
|
||||
**任务 5:系统设计文档** (D-XXX) — ⚠️ 必须完整
|
||||
|
||||
```
|
||||
--title "D-XXX 系统设计"
|
||||
--body "...产出 docs/design/系统设计.md(含各子系统的设计)
|
||||
⚠️ 不允许占位,必须产出完整内容。"
|
||||
--labels "stage:roadmap,role:design,status:ready"
|
||||
```
|
||||
|
||||
**任务 6:关卡设计文档** (D-XXX) — ⚠️ 必须完整
|
||||
|
||||
```
|
||||
--title "D-XXX 关卡设计"
|
||||
--body "...产出 docs/design/关卡设计.md
|
||||
⚠️ 不允许占位,必须产出完整内容。"
|
||||
--labels "stage:roadmap,role:design,status:ready"
|
||||
```
|
||||
|
||||
**任务 7:UI 设计文档** (D-XXX) — 可占位
|
||||
|
||||
```
|
||||
--title "D-XXX UI设计"
|
||||
--body "...产出 docs/design/UI设计.md
|
||||
占位规则:允许占位"
|
||||
--labels "stage:roadmap,role:design,status:ready"
|
||||
```
|
||||
|
||||
**任务 8:音频设计文档** (D-XXX) — 可占位
|
||||
|
||||
```
|
||||
--title "D-XXX 音频设计"
|
||||
--body "...产出 docs/design/音频设计.md
|
||||
占位规则:允许占位"
|
||||
--labels "stage:roadmap,role:design,status:ready"
|
||||
```
|
||||
|
||||
**任务 9:版本路径图** (D-XXX)
|
||||
|
||||
```
|
||||
--title "D-XXX 版本路径图"
|
||||
--body "...产出 docs/plans/版本路径图.md
|
||||
包含:版本列表、每个版本的核心目标、范围概述、内容完成度、
|
||||
涉及的系统清单、版本间递进逻辑、验收标准
|
||||
依赖:任务 2-8(至少了解各文档的概要信息后才能规划版本路径)"
|
||||
--labels "stage:roadmap,role:design,status:backlog"
|
||||
```
|
||||
|
||||
### 依赖关系
|
||||
|
||||
```
|
||||
任务 1(总索引)→ 先完成
|
||||
任务 2-8(各设计文档)→ 1 完成后可并行创建,建议按序执行
|
||||
任务 9(版本路径图)→ 依赖 2-8,最后完成
|
||||
```
|
||||
|
||||
设置依赖:`issue block <任务9> <任务2>` ~ `issue block <任务9> <任务8>`
|
||||
|
||||
---
|
||||
|
||||
## 进度检查
|
||||
|
||||
每次 PM 激活时执行:
|
||||
|
||||
1. **读取任务状态**:`issue list --milestone "总路线阶段" --state open`
|
||||
2. **判断当前焦点**:
|
||||
- 任务 1 未完成 → 建议:先完成设计总索引
|
||||
- 任务 2-8 有未完成的 → 建议按序完成各设计文档
|
||||
- 任务 2-8 全完成,任务 9 未开始 → 更新任务 9 为 `status:ready`
|
||||
- 任务 9 进行中 → 等待
|
||||
3. **检查完成条件**:
|
||||
- 总路线阶段里程碑下 open issues = 0 → 触发阶段转换
|
||||
|
||||
---
|
||||
|
||||
## 阶段转换
|
||||
|
||||
**前置条件**:总路线阶段里程碑下无 open issues。
|
||||
|
||||
**转换操作**(每步需用户确认):
|
||||
|
||||
```bash
|
||||
# 1. 关闭总路线阶段里程碑
|
||||
node scripts/gitea.js --role pm milestone update <总路线里程碑ID> --state closed
|
||||
|
||||
# 2. 创建第一个迭代版本里程碑
|
||||
node scripts/gitea.js --role pm milestone create \
|
||||
--title "迭代-v0.1" \
|
||||
--description "第一个迭代版本"
|
||||
|
||||
# 3. 创建第一个迭代任务:插件资产检查与准备
|
||||
# (详细参数见 stage-iteration 技能)
|
||||
|
||||
# 4. 更新 Pipeline Tracker
|
||||
# 更新正文:当前阶段改为「迭代阶段」
|
||||
# 阶段历史表添加总路线阶段完成日期
|
||||
|
||||
# 5. 告知用户已进入迭代阶段
|
||||
# 下次 PM 激活时将调用 stage-iteration 技能
|
||||
```
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
name: "stage-sketch"
|
||||
description: "草图阶段技能 — 概念设计与技术可行性验证。管理草图阶段任务创建和阶段转换。"
|
||||
---
|
||||
|
||||
# 草图阶段
|
||||
|
||||
> 当 PM 检测到 Pipeline Tracker 标记为「草图阶段」时调用本技能。
|
||||
> 目标:验证游戏概念可行,产出概念草案和技术可行性报告。
|
||||
|
||||
---
|
||||
|
||||
## 阶段概述
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 目标 | 验证"这个游戏值得做、技术上可行" |
|
||||
| 产出 | 概念草案 + 技术可行性报告 |
|
||||
| 参与角色 | 策划(概念设计)、程序(技术评估) |
|
||||
| 完成标志 | 两个任务都关闭 + 里程碑下无 open issues |
|
||||
|
||||
---
|
||||
|
||||
## 任务创建
|
||||
|
||||
### 检查已有任务
|
||||
|
||||
先通过 `issue list --milestone "草图阶段" --state open` 检查现有任务。
|
||||
|
||||
### 需要创建的任务(共 2 个)
|
||||
|
||||
**任务 1:概念设计** (D-XXX)
|
||||
|
||||
```bash
|
||||
node scripts/gitea.js --role pm issue create \
|
||||
--title "D-XXX 概念设计" \
|
||||
--body "## 任务信息
|
||||
- **任务ID**: D-XXX
|
||||
- **所属阶段**: 草图阶段
|
||||
- **角色**: 策划
|
||||
|
||||
## 预期产出
|
||||
概念草案 \`docs/design/概念草案.md\`,包含 7 个必填字段:
|
||||
1. 一句话概述
|
||||
2. 核心玩法
|
||||
3. 美术风格
|
||||
4. 目标平台
|
||||
5. 灵感来源
|
||||
6. 核心假设(为什么好玩)
|
||||
7. 范围约束(MVP 包含什么)
|
||||
|
||||
## 参考文档
|
||||
- 游戏愿景:docs/plans/游戏愿景.md(如有)" \
|
||||
--milestone "草图阶段" \
|
||||
--labels "stage:sketch,role:design,status:ready" \
|
||||
--assignees "ai-designer"
|
||||
```
|
||||
|
||||
**任务 2:技术可行性报告** (P-XXX)
|
||||
|
||||
```bash
|
||||
node scripts/gitea.js --role pm issue create \
|
||||
--title "P-XXX 技术可行性评估" \
|
||||
--body "## 任务信息
|
||||
- **任务ID**: P-XXX
|
||||
- **所属阶段**: 草图阶段
|
||||
- **角色**: 程序
|
||||
- **依赖**: D-XXX 概念设计完成
|
||||
|
||||
## 预期产出
|
||||
技术可行性报告 \`docs/technical/可行性评估.md\`,评估:
|
||||
1. 引擎能力匹配度(UE5 能否实现核心玩法)
|
||||
2. 性能预期(目标平台帧率目标)
|
||||
3. 关键技术风险(需要验证的技术点)
|
||||
4. 插件/工具链可行性
|
||||
5. 结论:可行/有条件可行/不可行" \
|
||||
--milestone "草图阶段" \
|
||||
--labels "stage:sketch,role:dev,status:backlog" \
|
||||
--assignees "ai-programmer"
|
||||
```
|
||||
|
||||
设置依赖关系:`issue block <D-XXX> <P-XXX>`
|
||||
|
||||
---
|
||||
|
||||
## 进度检查
|
||||
|
||||
每次 PM 激活时执行:
|
||||
|
||||
1. **读取任务状态**:`issue list --milestone "草图阶段" --state open`
|
||||
2. **判断当前焦点**:
|
||||
- D-XXX 未完成 → 建议用户启动策划做概念设计
|
||||
- D-XXX 完成,P-XXX 未开始 → 更新 P-XXX 标签为 `status:ready`,建议启动程序做技术评估
|
||||
- P-XXX 进行中 → 等待
|
||||
3. **检查完成条件**:
|
||||
- 草图阶段里程碑下 open issues = 0 → 触发阶段转换
|
||||
|
||||
---
|
||||
|
||||
## 阶段转换
|
||||
|
||||
**前置条件**:草图阶段里程碑下无 open issues。
|
||||
|
||||
**转换操作**(每步需用户确认):
|
||||
|
||||
```bash
|
||||
# 1. 关闭草图阶段里程碑
|
||||
node scripts/gitea.js --role pm milestone update <草图里程碑ID> --state closed
|
||||
|
||||
# 2. 创建总路线阶段里程碑
|
||||
node scripts/gitea.js --role pm milestone create \
|
||||
--title "总路线阶段" \
|
||||
--description "全面设计文档产出:世界观、剧情、角色、系统、关卡、UI、音频、版本路径"
|
||||
|
||||
# 3. 更新 Pipeline Tracker
|
||||
# 更新 Issue 正文:当前阶段改为「总路线阶段」,阶段历史表添加草图阶段完成日期
|
||||
|
||||
# 4. 告知用户已进入总路线阶段
|
||||
# 下次 PM 激活时将调用 stage-roadmap 技能
|
||||
```
|
||||
+8
-1
@@ -30,6 +30,13 @@
|
||||
{
|
||||
"Name": "GameplayStateTree",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "PythonScriptPlugin",
|
||||
"Enabled": true,
|
||||
"TargetAllowList": [
|
||||
"Editor"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
+8
-1
@@ -29,6 +29,13 @@ const STANDARD_LABELS = [
|
||||
{ name: 'gate:art-fail', color: '#d73a49', description: '美术资产验收门 ❌' },
|
||||
{ name: 'gate:impl-pass', color: '#28a745', description: '程序实现验收门 ✅' },
|
||||
{ name: 'gate:impl-fail', color: '#d73a49', description: '程序实现验收门 ❌' },
|
||||
// Review (2)
|
||||
{ name: 'review:pass', color: '#28a745', description: '审查已通过 ✅' },
|
||||
{ name: 'review:fail', color: '#d73a49', description: '审查未通过 ❌' },
|
||||
// Stage (3)
|
||||
{ name: 'stage:sketch', color: '#bfd4f2', description: '草图阶段' },
|
||||
{ name: 'stage:roadmap', color: '#d4c5f9', description: '总路线阶段' },
|
||||
{ name: 'stage:iteration', color: '#fce6c0', description: '迭代阶段' },
|
||||
];
|
||||
|
||||
// ═══════════════════════════════════════════════════
|
||||
@@ -702,7 +709,7 @@ function resolveToken(options) {
|
||||
// 优先级 2:--role 参数
|
||||
const role = options.role;
|
||||
if (role) {
|
||||
const validRoles = ['pm', 'designer', 'dev', 'artist'];
|
||||
const validRoles = ['pm', 'designer', 'dev', 'artist', 'reviewer'];
|
||||
if (!validRoles.includes(role)) {
|
||||
console.error(`❌ 无效角色: ${role},可选: ${validRoles.join(', ')}`);
|
||||
process.exit(1);
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
"""UE5 命令行白模关卡构建脚本。
|
||||
|
||||
由 build_level.bat 通过 UE5 的 -ExecutePythonScript 参数调用。
|
||||
在 UE5 编辑器 Python 环境中运行,无需手动打开编辑器。
|
||||
|
||||
用法:
|
||||
build_level.bat # 默认配置
|
||||
build_level.bat --json output/v02.json # 指定 JSON
|
||||
build_level.bat --no-quit # 构建后不退出编辑器
|
||||
|
||||
环境变量(由 build_level.bat 设置):
|
||||
LB_JSON_PATH — JSON 数据文件路径(相对于项目根目录)
|
||||
LB_MAP_NAME — 目标关卡资产名
|
||||
LB_MAP_PACKAGE — 关卡包路径(如 /Game/Maps)
|
||||
LB_NO_QUIT — 设为 "1" 不退出编辑器
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
# ---- 从环境变量读取配置 ----
|
||||
json_rel = os.environ.get("LB_JSON_PATH", "tools/level-builder/output/level_v01.json")
|
||||
map_name = os.environ.get("LB_MAP_NAME", "LVL_SuburbanV01")
|
||||
map_package = os.environ.get("LB_MAP_PACKAGE", "/Game/Maps")
|
||||
no_quit = os.environ.get("LB_NO_QUIT", "0") == "1"
|
||||
map_asset_path = f"{map_package}/{map_name}"
|
||||
|
||||
# ---- 检查 unreal 模块 ----
|
||||
try:
|
||||
import unreal
|
||||
except ImportError:
|
||||
print("[LevelBuilder] FATAL: unreal 模块不可用,必须在 UE5 环境中运行")
|
||||
sys.exit(1)
|
||||
|
||||
# ---- 项目根目录 ----
|
||||
# UE5 -ExecutePythonScript 模式下 CWD 是引擎目录,不是项目目录
|
||||
# 所以必须通过环境变量传入项目路径
|
||||
_project_root_str = os.environ.get("LB_PROJECT_ROOT", "")
|
||||
if _project_root_str:
|
||||
PROJECT_ROOT = Path(_project_root_str)
|
||||
else:
|
||||
# Fallback: 尝试从 .uproject 文件定位
|
||||
_cwd = Path(os.getcwd())
|
||||
if (_cwd / "ChildHood.uproject").exists():
|
||||
PROJECT_ROOT = _cwd
|
||||
else:
|
||||
# 最后尝试 unreal API
|
||||
try:
|
||||
PROJECT_ROOT = Path(unreal.Paths.project_dir()).parent
|
||||
except Exception:
|
||||
PROJECT_ROOT = _cwd
|
||||
|
||||
json_path = PROJECT_ROOT / json_rel
|
||||
|
||||
print(f"[LevelBuilder] ==========================================")
|
||||
print(f"[LevelBuilder] ChildHood Level Builder — Command Line")
|
||||
print(f"[LevelBuilder] ==========================================")
|
||||
print(f"[LevelBuilder] Project: {PROJECT_ROOT}")
|
||||
print(f"[LevelBuilder] JSON: {json_rel}")
|
||||
print(f"[LevelBuilder] Map: {map_asset_path}")
|
||||
print(f"[LevelBuilder]")
|
||||
|
||||
|
||||
# ---- 等待编辑器子系统就绪 ----
|
||||
def wait_for_editor(timeout=90):
|
||||
"""轮询等待 EditorActorSubsystem 可用。"""
|
||||
print("[LevelBuilder] 等待编辑器初始化...")
|
||||
start = time.time()
|
||||
while time.time() - start < timeout:
|
||||
try:
|
||||
subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
||||
if subsys is not None:
|
||||
elapsed = time.time() - start
|
||||
print(f"[LevelBuilder] 编辑器就绪 ({elapsed:.1f}s)")
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(1)
|
||||
print(f"[LevelBuilder] FATAL: 编辑器初始化超时 ({timeout}s)")
|
||||
return False
|
||||
|
||||
|
||||
if not wait_for_editor():
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# ---- 设置 sys.path ----
|
||||
lb_dir = str(PROJECT_ROOT / "tools" / "level-builder")
|
||||
for p in [str(PROJECT_ROOT), lb_dir]:
|
||||
if p not in sys.path:
|
||||
sys.path.insert(0, p)
|
||||
|
||||
|
||||
# ---- 确保目标关卡存在 ----
|
||||
def ensure_map(asset_path):
|
||||
"""创建或加载目标关卡。成功返回 True。"""
|
||||
# 已存在 → 加载
|
||||
if unreal.EditorAssetLibrary.does_asset_exist(asset_path):
|
||||
print(f"[LevelBuilder] 加载已有关卡: {asset_path}")
|
||||
unreal.EditorLevelLibrary.load_level(asset_path)
|
||||
return True
|
||||
|
||||
print(f"[LevelBuilder] 创建新关卡: {asset_path}")
|
||||
|
||||
# 方法 1: new_level(asset_path) — UE5.7 EditorLevelLibrary 直接创建
|
||||
try:
|
||||
unreal.EditorLevelLibrary.new_level(asset_path)
|
||||
print("[LevelBuilder] ✅ 关卡创建成功 (new_level)")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"[LevelBuilder] new_level 失败: {e}")
|
||||
|
||||
# 方法 2: AssetTools + WorldFactory(某些版本可用)
|
||||
try:
|
||||
asset_tools = unreal.get_editor_subsystem(unreal.AssetToolsSubsystem)
|
||||
factory = unreal.WorldFactory()
|
||||
new_asset = asset_tools.create_asset(
|
||||
asset_name=map_name,
|
||||
package_path=map_package,
|
||||
asset_class=None,
|
||||
factory=factory,
|
||||
)
|
||||
if new_asset:
|
||||
unreal.EditorLevelLibrary.load_level(asset_path)
|
||||
print("[LevelBuilder] ✅ 关卡创建成功 (WorldFactory)")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"[LevelBuilder] WorldFactory 失败: {e}")
|
||||
|
||||
print("[LevelBuilder] FATAL: 无法创建关卡")
|
||||
return False
|
||||
|
||||
|
||||
if not ensure_map(map_asset_path):
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# ---- 验证 JSON ----
|
||||
if not json_path.exists():
|
||||
print(f"[LevelBuilder] FATAL: JSON 文件不存在: {json_path}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# ---- 导入并执行构建 ----
|
||||
from builder.scene_builder import build_level
|
||||
|
||||
print(f"[LevelBuilder] 开始构建...")
|
||||
build_log = build_level(str(json_path), clear_existing=True)
|
||||
|
||||
|
||||
# ---- 保存关卡 ----
|
||||
print("[LevelBuilder] 保存关卡...")
|
||||
try:
|
||||
unreal.EditorLevelLibrary.save_current_level()
|
||||
print(f"[LevelBuilder] ✅ 关卡已保存: {map_asset_path}")
|
||||
except Exception as e:
|
||||
print(f"[LevelBuilder] ⚠️ 保存失败: {e}")
|
||||
|
||||
|
||||
# ---- 构建结果汇总 ----
|
||||
if build_log:
|
||||
ok = sum(1 for b in build_log if b.get("status") == "ok")
|
||||
fail = len(build_log) - ok
|
||||
print(f"")
|
||||
print(f"[LevelBuilder] {'=' * 40}")
|
||||
print(f"[LevelBuilder] SUCCESS: {ok} | FAILED: {fail} | TOTAL: {len(build_log)}")
|
||||
print(f"[LevelBuilder] {'=' * 40}")
|
||||
if fail > 0:
|
||||
print(f"[LevelBuilder] 失败列表:")
|
||||
for b in build_log:
|
||||
if b.get("status") != "ok":
|
||||
print(f" ❌ {b.get('name', '?')}: {b.get('reason', '未知')}")
|
||||
|
||||
|
||||
# ---- 退出编辑器 ----
|
||||
if not no_quit:
|
||||
print("[LevelBuilder] 退出编辑器...")
|
||||
unreal.SystemLibrary.execute_console_command(None, "quit")
|
||||
|
||||
print("[LevelBuilder] Done.")
|
||||
@@ -0,0 +1,67 @@
|
||||
"""UE5 编辑器内白模关卡构建启动脚本。
|
||||
|
||||
使用方式:
|
||||
1. 打开 UE5 编辑器
|
||||
2. Window → Developer Tools → Output Log
|
||||
3. 左下角下拉菜单切换到 "Python"
|
||||
4. 粘贴执行:
|
||||
exec(open(r"<项目绝对路径>/tools/level-builder/build_in_editor.py").read())
|
||||
|
||||
或者:
|
||||
File → Execute Python Script → 选择此文件
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# ---- 配置 ----
|
||||
PROJECT_ROOT = Path(r"E:/MiceCola/work/ChildHood")
|
||||
LEVEL_JSON = PROJECT_ROOT / "tools/level-builder/output/level_v01.json"
|
||||
# ---- 配置结束 ----
|
||||
|
||||
# 将项目路径加入 sys.path
|
||||
TOOLS_DIR = PROJECT_ROOT / "tools"
|
||||
LEVEL_BUILDER_DIR = PROJECT_ROOT / "tools/level-builder"
|
||||
for p in [str(PROJECT_ROOT), str(TOOLS_DIR), str(LEVEL_BUILDER_DIR)]:
|
||||
if p not in sys.path:
|
||||
sys.path.insert(0, p)
|
||||
|
||||
# 检查 unreal 模块
|
||||
try:
|
||||
import unreal
|
||||
except ImportError:
|
||||
print("[错误] 未检测到 unreal 模块,此脚本必须在 UE5 编辑器 Python 环境中运行!")
|
||||
print(" 请在 Output Log 中切换到 Python 控制台后执行。")
|
||||
raise
|
||||
|
||||
# 检查 JSON 文件存在
|
||||
if not LEVEL_JSON.exists():
|
||||
print(f"[错误] JSON 文件不存在: {LEVEL_JSON}")
|
||||
print(" 请先在命令行运行: python main.py parse -i ../../docs/technical/关卡-v01-搭建指南.md")
|
||||
raise FileNotFoundError(str(LEVEL_JSON))
|
||||
|
||||
# 导入并执行构建
|
||||
from builder.scene_builder import build_level
|
||||
|
||||
print(f"[LevelBuilder] 项目根目录: {PROJECT_ROOT}")
|
||||
print(f"[LevelBuilder] JSON 数据: {LEVEL_JSON}")
|
||||
print(f"[LevelBuilder] 开始构建...")
|
||||
|
||||
build_log = build_level(
|
||||
json_path=str(LEVEL_JSON),
|
||||
clear_existing=True,
|
||||
zones=None, # 构建所有区域;如仅需部分区域改为 ["Z1", "Z3"]
|
||||
)
|
||||
|
||||
if build_log:
|
||||
ok = sum(1 for b in build_log if b.get("status") == "ok")
|
||||
failed = sum(1 for b in build_log if b.get("status") != "ok")
|
||||
print(f"\n[LevelBuilder] ========== 构建结果 ==========")
|
||||
print(f" 成功: {ok}")
|
||||
print(f" 失败: {failed}")
|
||||
print(f" 总计: {len(build_log)}")
|
||||
if failed > 0:
|
||||
print(f"\n 失败列表:")
|
||||
for b in build_log:
|
||||
if b.get("status") != "ok":
|
||||
print(f" ❌ {b.get('name', '?')}: {b.get('reason', '未知错误')}")
|
||||
@@ -0,0 +1,192 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM === ChildHood Level Builder - Command Line Build ===
|
||||
REM
|
||||
REM Usage:
|
||||
REM build_level.bat (default config)
|
||||
REM build_level.bat --json output/v02.json (specify JSON)
|
||||
REM build_level.bat --map LVL_SuburbanV02 (specify map name)
|
||||
REM build_level.bat --no-quit (keep editor open)
|
||||
REM build_level.bat --no-null-rhi (show editor window)
|
||||
REM build_level.bat --dry-run (print command only)
|
||||
|
||||
set "SCRIPT_DIR=%~dp0"
|
||||
set "PROJECT_DIR=%SCRIPT_DIR%..\.."
|
||||
for %%i in ("%PROJECT_DIR%\.") do set "PROJECT_ROOT=%%~fi"
|
||||
set "UPROJECT=%PROJECT_ROOT%\ChildHood.uproject"
|
||||
|
||||
REM ---- Defaults ----
|
||||
set "JSON_REL=tools/level-builder/output/level_v01.json"
|
||||
set "MAP_NAME=LVL_SuburbanV01"
|
||||
set "MAP_PACKAGE=/Game/Maps"
|
||||
set "NO_QUIT=0"
|
||||
set "NO_NULL_RHI=0"
|
||||
set "DRY_RUN=0"
|
||||
|
||||
REM ---- Parse Arguments ----
|
||||
:parse_args
|
||||
if "%~1"=="" goto done_args
|
||||
if /i "%~1"=="--json" (
|
||||
set "JSON_REL=%~2"
|
||||
shift
|
||||
shift
|
||||
goto parse_args
|
||||
)
|
||||
if /i "%~1"=="--map" (
|
||||
set "MAP_NAME=%~2"
|
||||
shift
|
||||
shift
|
||||
goto parse_args
|
||||
)
|
||||
if /i "%~1"=="--package" (
|
||||
set "MAP_PACKAGE=%~2"
|
||||
shift
|
||||
shift
|
||||
goto parse_args
|
||||
)
|
||||
if /i "%~1"=="--no-quit" (
|
||||
set "NO_QUIT=1"
|
||||
shift
|
||||
goto parse_args
|
||||
)
|
||||
if /i "%~1"=="--no-null-rhi" (
|
||||
set "NO_NULL_RHI=1"
|
||||
shift
|
||||
goto parse_args
|
||||
)
|
||||
if /i "%~1"=="--dry-run" (
|
||||
set "DRY_RUN=1"
|
||||
shift
|
||||
goto parse_args
|
||||
)
|
||||
if /i "%~1"=="--help" goto show_help
|
||||
if /i "%~1"=="-h" goto show_help
|
||||
echo [WARN] Unknown argument: %~1
|
||||
shift
|
||||
goto parse_args
|
||||
:done_args
|
||||
|
||||
REM ---- Find UE5 Editor ----
|
||||
set "UE_EDITOR="
|
||||
if defined UE5_ROOT (
|
||||
for %%b in (UnrealEditor.exe UE5Editor.exe) do (
|
||||
if exist "%UE5_ROOT%\Engine\Binaries\Win64\%%b" (
|
||||
set "UE_EDITOR=%UE5_ROOT%\Engine\Binaries\Win64\%%b"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if "%UE_EDITOR%"=="" (
|
||||
for %%p in (
|
||||
"C:\Program Files\Epic Games\UE_5.7"
|
||||
"C:\Program Files (x86)\Epic Games\UE_5.7"
|
||||
"D:\Program Files\Epic Games\UE_5.7"
|
||||
"E:\Program Files\Epic Games\UE_5.7"
|
||||
"C:\Program Files\Epic Games\UE_5.6"
|
||||
"C:\Program Files (x86)\Epic Games\UE_5.6"
|
||||
"D:\Program Files\Epic Games\UE_5.6"
|
||||
"E:\Program Files\Epic Games\UE_5.6"
|
||||
) do (
|
||||
if not defined UE_EDITOR (
|
||||
for %%b in (UnrealEditor.exe UE5Editor.exe) do (
|
||||
if exist "%%~p\Engine\Binaries\Win64\%%b" (
|
||||
set "UE_EDITOR=%%~p\Engine\Binaries\Win64\%%b"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if "%UE_EDITOR%"=="" (
|
||||
echo.
|
||||
echo [ERROR] UE5 Editor not found!
|
||||
echo Set UE5_ROOT to your UE5 install directory, e.g.:
|
||||
echo set UE5_ROOT=C:\Program Files\Epic Games\UE_5.7
|
||||
echo.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM ---- Path conversion (UE5 uses forward slashes) ----
|
||||
set "SCRIPT_PATH=%SCRIPT_DIR%build_cmdline.py"
|
||||
set "SCRIPT_PATH=!SCRIPT_PATH:\=/!"
|
||||
set "UPROJECT_FWD=!UPROJECT:\=/!"
|
||||
|
||||
REM ---- Build command line ----
|
||||
set "CMD_ARGS=-unattended -NoSound"
|
||||
if "!NO_NULL_RHI!"=="0" set "CMD_ARGS=!CMD_ARGS! -NullRHI"
|
||||
set "CMD_ARGS=!CMD_ARGS! -ExecutePythonScript=!SCRIPT_PATH!"
|
||||
|
||||
REM ---- Pass config to Python via env vars ----
|
||||
set "LB_PROJECT_ROOT=!PROJECT_ROOT!"
|
||||
set "LB_JSON_PATH=%JSON_REL%"
|
||||
set "LB_MAP_NAME=%MAP_NAME%"
|
||||
set "LB_MAP_PACKAGE=%MAP_PACKAGE%"
|
||||
if "!NO_QUIT!"=="1" set "LB_NO_QUIT=1"
|
||||
|
||||
REM ---- Print info ----
|
||||
echo.
|
||||
echo ============================================
|
||||
echo ChildHood Level Builder - Command Line
|
||||
echo ============================================
|
||||
echo UE5: !UE_EDITOR!
|
||||
echo Project: !UPROJECT_FWD!
|
||||
echo JSON: %JSON_REL%
|
||||
echo Map: %MAP_PACKAGE%/%MAP_NAME%
|
||||
echo NullRHI: %NO_NULL_RHI%
|
||||
echo ============================================
|
||||
echo.
|
||||
|
||||
REM ---- Dry Run ----
|
||||
if "%DRY_RUN%"=="1" (
|
||||
echo [DRY RUN] Command:
|
||||
echo.
|
||||
echo "!UE_EDITOR!" "!UPROJECT_FWD!" !CMD_ARGS!
|
||||
echo.
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
REM ---- Check JSON exists ----
|
||||
set "JSON_ABS=%PROJECT_ROOT%\%JSON_REL%"
|
||||
set "JSON_ABS=!JSON_ABS:/=\!"
|
||||
if not exist "!JSON_ABS!" (
|
||||
echo [ERROR] JSON not found: !JSON_ABS!
|
||||
echo Run first: python tools/level-builder/main.py parse
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM ---- Execute ----
|
||||
echo [LevelBuilder] Launching UE5 Editor (command line mode)...
|
||||
echo [LevelBuilder] Editor will auto-save and quit after build.
|
||||
echo.
|
||||
|
||||
"!UE_EDITOR!" "!UPROJECT_FWD!" !CMD_ARGS!
|
||||
|
||||
set "EXIT_CODE=!ERRORLEVEL!"
|
||||
echo.
|
||||
if "!EXIT_CODE!"=="0" (
|
||||
echo [LevelBuilder] Build complete. Editor exited.
|
||||
) else (
|
||||
echo [LevelBuilder] Editor exit code: !EXIT_CODE!
|
||||
)
|
||||
exit /b !EXIT_CODE!
|
||||
|
||||
:show_help
|
||||
echo.
|
||||
echo ChildHood Level Builder - Command Line Build
|
||||
echo.
|
||||
echo Usage: build_level.bat [options]
|
||||
echo.
|
||||
echo Options:
|
||||
echo --json PATH JSON data file (relative to project root)
|
||||
echo --map NAME Target map asset name (default: LVL_SuburbanV01)
|
||||
echo --package PATH Map package path (default: /Game/Maps)
|
||||
echo --no-quit Keep editor open after build
|
||||
echo --no-null-rhi Show editor window (for debugging)
|
||||
echo --dry-run Print command without executing
|
||||
echo --help, -h Show this help
|
||||
echo.
|
||||
echo Environment:
|
||||
echo UE5_ROOT UE5 install directory
|
||||
echo.
|
||||
exit /b 0
|
||||
@@ -0,0 +1,257 @@
|
||||
"""UE5 场景构建器。
|
||||
|
||||
在 UE5 编辑器的 Python 环境中运行。读取 JSON 中间数据,
|
||||
使用 unreal Python API 创建 Actor、设置 Transform/材质/Outliner 文件夹。
|
||||
|
||||
使用方式:
|
||||
在 UE5 编辑器 Output Log 中切换到 Python,执行:
|
||||
exec(open(r"path/to/scene_builder.py").read())
|
||||
build_level(r"path/to/level_v01.json")
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 尝试导入 unreal,失败时给出提示
|
||||
try:
|
||||
import unreal
|
||||
HAS_UNREAL = True
|
||||
except ImportError:
|
||||
HAS_UNREAL = False
|
||||
|
||||
# 将 level-builder 根目录加入 sys.path 以支持 config 导入
|
||||
# 注意: 目录名含连字符 (level-builder),不能用 tools.level_builder 形式导入
|
||||
_SCRIPT_DIR = Path(__file__).resolve().parent # builder/
|
||||
_LB_DIR = _SCRIPT_DIR.parent # tools/level-builder/
|
||||
if str(_LB_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(_LB_DIR))
|
||||
|
||||
from config.asset_mapping import MESH_ASSETS
|
||||
from config.material_mapping import MATERIAL_PATHS
|
||||
|
||||
logger = logging.getLogger("level_builder.builder")
|
||||
|
||||
|
||||
def build_level(json_path: str, clear_existing: bool = True, zones: list[str] = None):
|
||||
"""从 JSON 数据构建 UE5 关卡。
|
||||
|
||||
Args:
|
||||
json_path: 中间 JSON 文件路径。
|
||||
clear_existing: 是否清除已有的 LVL_* Actor。
|
||||
zones: 仅构建指定区域(如 ["Z1", "Z3"]),None 表示全部。
|
||||
"""
|
||||
if not HAS_UNREAL:
|
||||
print("[错误] 未检测到 unreal 模块。此脚本必须在 UE5 编辑器 Python 环境中运行。")
|
||||
return
|
||||
|
||||
# 加载数据
|
||||
data = _load_json(json_path)
|
||||
if not data:
|
||||
return
|
||||
|
||||
actors = data.get("actors", [])
|
||||
print(f"[LevelBuilder] 加载 {len(actors)} 个 Actor 定义")
|
||||
|
||||
# 可选:清除已有 LVL_* Actor
|
||||
if clear_existing:
|
||||
_clear_existing_actors()
|
||||
|
||||
# 过滤区域
|
||||
if zones:
|
||||
actors = [a for a in actors if a.get("zone_id") in zones or a.get("zone_id") == "GLOBAL"]
|
||||
print(f"[LevelBuilder] 过滤为 {len(actors)} 个 Actor(区域: {zones})")
|
||||
|
||||
# 获取编辑器子系统
|
||||
editor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
||||
if not editor_subsystem:
|
||||
print("[错误] 无法获取 EditorActorSubsystem")
|
||||
return
|
||||
|
||||
# 统计
|
||||
success = 0
|
||||
failed = 0
|
||||
build_log = []
|
||||
|
||||
for i, actor_def in enumerate(actors):
|
||||
name = actor_def.get("actor_name", f"Actor_{i}")
|
||||
asset_type = actor_def.get("asset_type", "static_mesh")
|
||||
|
||||
try:
|
||||
if asset_type == "player_start":
|
||||
actor = _spawn_player_start(editor_subsystem, actor_def)
|
||||
elif asset_type == "directional_light":
|
||||
actor = _spawn_directional_light(editor_subsystem, actor_def)
|
||||
elif asset_type == "sky_light":
|
||||
actor = _spawn_sky_light(editor_subsystem, actor_def)
|
||||
elif asset_type == "point_light":
|
||||
actor = _spawn_point_light(editor_subsystem, actor_def)
|
||||
else:
|
||||
actor = _spawn_mesh_actor(editor_subsystem, actor_def)
|
||||
|
||||
if actor:
|
||||
success += 1
|
||||
build_log.append({"name": name, "status": "ok", **actor_def})
|
||||
else:
|
||||
failed += 1
|
||||
build_log.append({"name": name, "status": "failed", "reason": "spawn returned None"})
|
||||
|
||||
except Exception as e:
|
||||
failed += 1
|
||||
error_msg = str(e)
|
||||
build_log.append({"name": name, "status": "error", "reason": error_msg})
|
||||
print(f" [错误] {name}: {error_msg}")
|
||||
|
||||
# 进度
|
||||
if (i + 1) % 20 == 0:
|
||||
print(f" 进度: {i + 1}/{len(actors)} ({success} 成功, {failed} 失败)")
|
||||
|
||||
print(f"\n[LevelBuilder] 构建完成!")
|
||||
print(f" 成功: {success}")
|
||||
print(f" 失败: {failed}")
|
||||
print(f" 总计: {len(actors)}")
|
||||
|
||||
# 保存构建日志
|
||||
log_path = Path(json_path).parent / "build_log.json"
|
||||
with open(log_path, "w", encoding="utf-8") as f:
|
||||
json.dump(build_log, f, ensure_ascii=False, indent=2)
|
||||
print(f" 日志: {log_path}")
|
||||
|
||||
return build_log
|
||||
|
||||
|
||||
def _load_json(path: str) -> dict:
|
||||
"""加载 JSON 文件。"""
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
print(f"[错误] 无法加载 JSON: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def _clear_existing_actors():
|
||||
"""清除场景中所有 LVL_ 前缀的 Actor。"""
|
||||
editor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
||||
all_actors = unreal.EditorLevelLibrary.get_all_level_actors()
|
||||
lvl_actors = [a for a in all_actors if a.get_actor_label().startswith("LVL_")]
|
||||
if lvl_actors:
|
||||
print(f"[LevelBuilder] 清除 {len(lvl_actors)} 个已有 LVL_* Actor...")
|
||||
for a in lvl_actors:
|
||||
unreal.EditorLevelLibrary.destroy_actor(a)
|
||||
else:
|
||||
print("[LevelBuilder] 无已有 LVL_* Actor 需清除")
|
||||
|
||||
|
||||
def _spawn_mesh_actor(editor_subsystem, actor_def: dict):
|
||||
"""生成 StaticMesh Actor。"""
|
||||
asset_key = actor_def.get("asset_key", "")
|
||||
if not asset_key or asset_key not in MESH_ASSETS:
|
||||
print(f" [警告] {actor_def['actor_name']}: 资产 '{asset_key}' 未找到,使用默认 Cube")
|
||||
asset_key = "Cube"
|
||||
|
||||
mesh_path = MESH_ASSETS[asset_key]["path"]
|
||||
static_mesh = unreal.load_asset(mesh_path)
|
||||
if not static_mesh:
|
||||
print(f" [警告] {actor_def['actor_name']}: 无法加载资产 '{mesh_path}'")
|
||||
return None
|
||||
|
||||
# 生成
|
||||
loc = actor_def.get("location", [0, 0, 0])
|
||||
actor = editor_subsystem.spawn_actor_from_object(
|
||||
static_mesh,
|
||||
unreal.Vector(loc[0], loc[1], loc[2])
|
||||
)
|
||||
|
||||
if not actor:
|
||||
return None
|
||||
|
||||
# 设置 Transform
|
||||
rot = actor_def.get("rotation", [0, 0, 0])
|
||||
actor.set_actor_rotation(unreal.Rotator(rot[0], rot[1], rot[2]), False)
|
||||
|
||||
scale = actor_def.get("scale", [1, 1, 1])
|
||||
actor.set_actor_scale3d(unreal.Vector(scale[0], scale[1], scale[2]))
|
||||
|
||||
# 设置材质
|
||||
material_name = actor_def.get("material", "")
|
||||
if material_name and material_name in MATERIAL_PATHS:
|
||||
mat_path = MATERIAL_PATHS[material_name]
|
||||
material = unreal.load_asset(mat_path)
|
||||
if material:
|
||||
mesh_comp = actor.get_component_by_class(unreal.StaticMeshComponent)
|
||||
if mesh_comp:
|
||||
mesh_comp.set_material(0, material)
|
||||
|
||||
# 设置名称和文件夹
|
||||
actor.set_actor_label(actor_def["actor_name"])
|
||||
folder = actor_def.get("folder", "")
|
||||
if folder:
|
||||
actor.set_folder_path(folder)
|
||||
|
||||
return actor
|
||||
|
||||
|
||||
def _spawn_player_start(editor_subsystem, actor_def: dict):
|
||||
"""生成 PlayerStart Actor。"""
|
||||
loc = actor_def.get("location", [0, 0, 0])
|
||||
actor = editor_subsystem.spawn_actor_from_class(
|
||||
unreal.PlayerStart,
|
||||
unreal.Vector(loc[0], loc[1], loc[2])
|
||||
)
|
||||
if actor:
|
||||
rot = actor_def.get("rotation", [0, 0, 0])
|
||||
actor.set_actor_rotation(unreal.Rotator(rot[0], rot[1], rot[2]), False)
|
||||
actor.set_actor_label(actor_def["actor_name"])
|
||||
folder = actor_def.get("folder", "")
|
||||
if folder:
|
||||
actor.set_folder_path(folder)
|
||||
return actor
|
||||
|
||||
|
||||
def _spawn_directional_light(editor_subsystem, actor_def: dict):
|
||||
"""生成 DirectionalLight Actor。"""
|
||||
loc = actor_def.get("location", [0, 0, 500])
|
||||
actor = editor_subsystem.spawn_actor_from_class(
|
||||
unreal.DirectionalLight,
|
||||
unreal.Vector(loc[0], loc[1], loc[2])
|
||||
)
|
||||
if actor:
|
||||
rot = actor_def.get("rotation", [-45, 315, 0])
|
||||
actor.set_actor_rotation(unreal.Rotator(rot[0], rot[1], rot[2]), False)
|
||||
actor.set_actor_label(actor_def["actor_name"])
|
||||
folder = actor_def.get("folder", "")
|
||||
if folder:
|
||||
actor.set_folder_path(folder)
|
||||
return actor
|
||||
|
||||
|
||||
def _spawn_sky_light(editor_subsystem, actor_def: dict):
|
||||
"""生成 SkyLight Actor。"""
|
||||
loc = actor_def.get("location", [0, 0, 0])
|
||||
actor = editor_subsystem.spawn_actor_from_class(
|
||||
unreal.SkyLight,
|
||||
unreal.Vector(loc[0], loc[1], loc[2])
|
||||
)
|
||||
if actor:
|
||||
actor.set_actor_label(actor_def["actor_name"])
|
||||
folder = actor_def.get("folder", "")
|
||||
if folder:
|
||||
actor.set_folder_path(folder)
|
||||
return actor
|
||||
|
||||
|
||||
def _spawn_point_light(editor_subsystem, actor_def: dict):
|
||||
"""生成 PointLight Actor。"""
|
||||
loc = actor_def.get("location", [0, 0, 0])
|
||||
actor = editor_subsystem.spawn_actor_from_class(
|
||||
unreal.PointLight,
|
||||
unreal.Vector(loc[0], loc[1], loc[2])
|
||||
)
|
||||
if actor:
|
||||
actor.set_actor_label(actor_def["actor_name"])
|
||||
folder = actor_def.get("folder", "")
|
||||
if folder:
|
||||
actor.set_folder_path(folder)
|
||||
return actor
|
||||
@@ -0,0 +1,211 @@
|
||||
"""BSP 资产设计名 → UE5 内容路径映射。
|
||||
|
||||
数据来源: docs/technical/关卡-v01-搭建指南.md 第一章 (BSP 资产映射表)
|
||||
资产根路径: Content/BlockingStarterPack/Meshes/
|
||||
|
||||
路径格式: /Game/<ContentSubPath>/<AssetName>.<AssetName>
|
||||
"""
|
||||
|
||||
# 资产路径前缀
|
||||
MESH_ROOT = "/Game/BlockingStarterPack/Meshes"
|
||||
MATERIAL_ROOT = "/Game/BlockingStarterPack/Materials"
|
||||
|
||||
MESH_ASSETS = {
|
||||
# ---- 建筑 — 墙体 ----
|
||||
"Wall_400x300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Walls/SM_Wall_400x300.SM_Wall_400x300",
|
||||
"default_size": (400, 20, 300),
|
||||
"category": "wall",
|
||||
},
|
||||
"Wall_200x300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Walls/SM_Wall_200x300.SM_Wall_200x300",
|
||||
"default_size": (200, 20, 300),
|
||||
"category": "wall",
|
||||
},
|
||||
"Wall_100x300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Walls/SM_Wall_100x300.SM_Wall_100x300",
|
||||
"default_size": (100, 20, 300),
|
||||
"category": "wall",
|
||||
},
|
||||
"Wall_400x100": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Walls/SM_Wall_400x100.SM_Wall_400x100",
|
||||
"default_size": (400, 20, 100),
|
||||
"category": "wall",
|
||||
},
|
||||
"WallDoor_200x300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Walls/SM_WallDoor_200x300.SM_WallDoor_200x300",
|
||||
"default_size": (200, 20, 300),
|
||||
"category": "wall_door",
|
||||
},
|
||||
"WallDoorGarage_400x300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Walls/SM_WallDoorGarage_400x300.SM_WallDoorGarage_400x300",
|
||||
"default_size": (400, 20, 300),
|
||||
"category": "wall_door",
|
||||
},
|
||||
"WallWindow_400x300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Walls/SM_WallWindow_400x300.SM_WallWindow_400x300",
|
||||
"default_size": (400, 20, 300),
|
||||
"category": "wall_window",
|
||||
},
|
||||
"WallWindowSmall_200x300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Walls/SM_WallWindowSmall_200x300.SM_WallWindowSmall_200x300",
|
||||
"default_size": (200, 20, 300),
|
||||
"category": "wall_window",
|
||||
},
|
||||
# ---- 建筑 — 地板 ----
|
||||
"Floor_1000x1000": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Floors/SM_Floor_1000x1000.SM_Floor_1000x1000",
|
||||
"default_size": (1000, 1000, 10),
|
||||
"category": "floor",
|
||||
},
|
||||
"Floor_300x400": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Floors/SM_Floor_300x400.SM_Floor_300x400",
|
||||
"default_size": (300, 400, 10),
|
||||
"category": "floor",
|
||||
},
|
||||
"Floor_200x300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Floors/SM_Floor_200x300.SM_Floor_200x300",
|
||||
"default_size": (200, 300, 10),
|
||||
"category": "floor",
|
||||
},
|
||||
# ---- 建筑 — 门 ----
|
||||
"RotativeDoor_01": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Doors/SM_RotativeDoor_01.SM_RotativeDoor_01",
|
||||
"default_size": None,
|
||||
"category": "door",
|
||||
},
|
||||
"GarageDoorOpen_01": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Doors/SM_GarageDoorOpen_01.SM_GarageDoorOpen_01",
|
||||
"default_size": None,
|
||||
"category": "door",
|
||||
},
|
||||
# ---- 建筑 — 窗户 ----
|
||||
"FakeWindowsFrame": {
|
||||
"path": f"{MESH_ROOT}/Architecture/WindowsSet/SM_FakeWindowsFrame.SM_FakeWindowsFrame",
|
||||
"default_size": None,
|
||||
"category": "window",
|
||||
},
|
||||
# ---- 建筑 — 围栏/围挡 ----
|
||||
"BarrierWall_600": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Barriers/SM_BarrierWall_600.SM_BarrierWall_600",
|
||||
"default_size": (600, 20, 100),
|
||||
"category": "barrier",
|
||||
},
|
||||
"BarrierWall_200": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Barriers/SM_BarrierWall_200.SM_BarrierWall_200",
|
||||
"default_size": (200, 20, 100),
|
||||
"category": "barrier",
|
||||
},
|
||||
"Fence_Small_400": {
|
||||
"path": f"{MESH_ROOT}/Industrials/SM_Fence_Small_400.SM_Fence_Small_400",
|
||||
"default_size": (400, 5, 100),
|
||||
"category": "fence",
|
||||
},
|
||||
# ---- 建筑 — 横梁 ----
|
||||
"Beam_300": {
|
||||
"path": f"{MESH_ROOT}/Architecture/Beams/SM_Beam_300.SM_Beam_300",
|
||||
"default_size": None,
|
||||
"category": "beam",
|
||||
},
|
||||
# ---- 建筑 — 大型建筑 ----
|
||||
"Building_01": {
|
||||
"path": f"{MESH_ROOT}/BigBuildings/SM_Building_01.SM_Building_01",
|
||||
"default_size": None,
|
||||
"category": "building",
|
||||
},
|
||||
# ---- 家具 ----
|
||||
"SimpleTable_01": {
|
||||
"path": f"{MESH_ROOT}/Furnitures/SM_SimpleTable_01.SM_SimpleTable_01",
|
||||
"default_size": None,
|
||||
"category": "furniture",
|
||||
},
|
||||
"SimpleTable_02": {
|
||||
"path": f"{MESH_ROOT}/Furnitures/SM_SimpleTable_02.SM_SimpleTable_02",
|
||||
"default_size": None,
|
||||
"category": "furniture",
|
||||
},
|
||||
"BedSingle": {
|
||||
"path": f"{MESH_ROOT}/Bedroom/SM_BedSingle.SM_BedSingle",
|
||||
"default_size": None,
|
||||
"category": "furniture",
|
||||
},
|
||||
"Monitor_01": {
|
||||
"path": f"{MESH_ROOT}/Furnitures/SM_Monitor_01.SM_Monitor_01",
|
||||
"default_size": None,
|
||||
"category": "furniture",
|
||||
},
|
||||
# ---- 工业/装饰 ----
|
||||
"Barrel": {
|
||||
"path": f"{MESH_ROOT}/Industrials/SM_Barrel.SM_Barrel",
|
||||
"default_size": None,
|
||||
"category": "industrial",
|
||||
},
|
||||
"WoodenBox_Small": {
|
||||
"path": f"{MESH_ROOT}/Industrials/SM_WoodenBox_Small.SM_WoodenBox_Small",
|
||||
"default_size": None,
|
||||
"category": "industrial",
|
||||
},
|
||||
"WoodenBox_Normal": {
|
||||
"path": f"{MESH_ROOT}/Industrials/SM_WoodenBox_Normal.SM_WoodenBox_Normal",
|
||||
"default_size": None,
|
||||
"category": "industrial",
|
||||
},
|
||||
"WoodenPallet_01": {
|
||||
"path": f"{MESH_ROOT}/Industrials/SM_WoodenPallet_01.SM_WoodenPallet_01",
|
||||
"default_size": None,
|
||||
"category": "industrial",
|
||||
},
|
||||
"Bucket_01": {
|
||||
"path": f"{MESH_ROOT}/Decorations/SM_Bucket_01.SM_Bucket_01",
|
||||
"default_size": None,
|
||||
"category": "industrial",
|
||||
},
|
||||
"TrashCans_01": {
|
||||
"path": f"{MESH_ROOT}/Trashs/SM_TrashCans_01.SM_TrashCans_01",
|
||||
"default_size": None,
|
||||
"category": "industrial",
|
||||
},
|
||||
"Container_Normal": {
|
||||
"path": f"{MESH_ROOT}/Industrials/SM_Container_Normal.SM_Container_Normal",
|
||||
"default_size": None,
|
||||
"category": "industrial",
|
||||
},
|
||||
# ---- 载具 ----
|
||||
"Car01": {
|
||||
"path": f"{MESH_ROOT}/Vehicles/SM_Car01.SM_Car01",
|
||||
"default_size": None,
|
||||
"category": "vehicle",
|
||||
},
|
||||
# ---- 基础体 ----
|
||||
"Cube": {
|
||||
"path": f"{MESH_ROOT}/Primitives/SM_Cube.SM_Cube",
|
||||
"default_size": (100, 100, 100),
|
||||
"category": "primitive",
|
||||
},
|
||||
"Cylinder": {
|
||||
"path": f"{MESH_ROOT}/Primitives/SM_Cylinder.SM_Cylinder",
|
||||
"default_size": (100, 100, 100),
|
||||
"category": "primitive",
|
||||
},
|
||||
"CirclePlatform": {
|
||||
"path": f"{MESH_ROOT}/Extra/SM_CirclePlatform.SM_CirclePlatform",
|
||||
"default_size": None,
|
||||
"category": "primitive",
|
||||
},
|
||||
# ---- 其他 ----
|
||||
"Tree_01": {
|
||||
"path": f"{MESH_ROOT}/Vegetations/SM_Tree_01.SM_Tree_01",
|
||||
"default_size": None,
|
||||
"category": "vegetation",
|
||||
},
|
||||
"RoadLamp_01": {
|
||||
"path": f"{MESH_ROOT}/Lights/SM_RoadLamp_01.SM_RoadLamp_01",
|
||||
"default_size": None,
|
||||
"category": "light_fixture",
|
||||
},
|
||||
"DisplayPanels_400x200": {
|
||||
"path": f"{MESH_ROOT}/DisplayPanels/SM_DisplayPanels_400x200.SM_DisplayPanels_400x200",
|
||||
"default_size": None,
|
||||
"category": "decoration",
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
"""标识色材质路径映射。
|
||||
|
||||
数据来源: docs/technical/关卡-v01-搭建指南.md §1.13
|
||||
"""
|
||||
|
||||
MATERIAL_PATHS = {
|
||||
# ---- Basics(标识色) ----
|
||||
"MI_DevRed": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevRed.MI_DevRed",
|
||||
"MI_DevOrange": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevOrange.MI_DevOrange",
|
||||
"MI_DevGray": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevGray.MI_DevGray",
|
||||
"MI_DevBrown": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevBrown.MI_DevBrown",
|
||||
"MI_DevWhite": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevWhite.MI_DevWhite",
|
||||
"MI_DevBlue": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevBlue.MI_DevBlue",
|
||||
"MI_DevGreen": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevGreen.MI_DevGreen",
|
||||
"MI_DevSkyBlue": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevSkyBlue.MI_DevSkyBlue",
|
||||
"MI_DevYellow": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevYellow.MI_DevYellow",
|
||||
"MI_DevFoliage": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevFoliage.MI_DevFoliage",
|
||||
"MI_DevPink": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevPink.MI_DevPink",
|
||||
# ---- Glass ----
|
||||
"MI_DevGlassWhite": "/Game/BlockingStarterPack/Materials/Glass/MI_DevGlassWhite.MI_DevGlassWhite",
|
||||
"MI_DevGlassBlue": "/Game/BlockingStarterPack/Materials/Glass/MI_DevGlassBlue.MI_DevGlassBlue",
|
||||
}
|
||||
|
||||
# 材质在 Content/ 目录下的文件系统路径前缀(用于离线文件存在性验证)
|
||||
MATERIAL_FS_PREFIX = "Content/BlockingStarterPack/Materials"
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Primitives 替代规则。
|
||||
|
||||
数据来源: docs/technical/关卡-v01-搭建报告.md 第四节(缩放记录)
|
||||
|
||||
当搭建指南中直接指定了资产和缩放时,无需查此表。
|
||||
此表用于未来执行案解析器,将语义化物件名映射到 Primitives 替代方案。
|
||||
"""
|
||||
|
||||
SUBSTITUTION_RULES = {
|
||||
# ---- 家具/家电 ----
|
||||
"冰箱": {"asset": "Cube", "scale": (0.3, 0.3, 1.6), "material": "MI_DevWhite"},
|
||||
"冰柜": {"asset": "Cube", "scale": (1.0, 0.6, 0.85), "material": "MI_DevWhite"},
|
||||
"电视柜": {"asset": "SimpleTable_02", "scale": (0.85, 0.75, 0.67)},
|
||||
# ---- 建筑细节 ----
|
||||
"门垫": {"asset": "Cube", "scale": (0.6, 0.4, 0.02), "material": "MI_DevGray"},
|
||||
"日历": {"asset": "Cube", "scale": (0.3, 0.02, 0.4), "material": "MI_DevRed"},
|
||||
"招牌": {"asset": "Cube", "scale": (3.0, 0.05, 0.4), "material": "MI_DevBlue"},
|
||||
"涂鸦": {"asset": "Cube", "scale": (1.2, 0.05, 0.8), "material": "MI_DevYellow"},
|
||||
# ---- 玻璃 ----
|
||||
"玻璃柜台": {"asset": "Cube", "scale": (2.0, 0.6, 0.9), "material": "MI_DevGlassWhite"},
|
||||
"积水坑": {"asset": "Cube", "scale": (1.2, 0.8, 0.01), "material": "MI_DevGlassBlue"},
|
||||
# ---- 街道/城郊物件 ----
|
||||
"电话亭": {"asset": "Cube", "scale": (0.8, 0.8, 2.2), "material": "MI_DevGray"},
|
||||
"电线杆": {"asset": "Cylinder", "scale": (0.04, 0.04, 12.0), "material": "MI_DevGray"},
|
||||
"晾衣杆_柱": {"asset": "Cylinder", "scale": (0.05, 0.05, 3.6), "material": "MI_DevGray"},
|
||||
"芦苇": {"asset": "Cylinder", "scale": (0.08, 0.08, 2.0), "material": "MI_DevFoliage"},
|
||||
"花盆": {"asset": "Cylinder", "scale": (0.2, 0.2, 0.25), "material": "MI_DevGreen"},
|
||||
# ---- 水塘 ----
|
||||
"荷叶": {"asset": "CirclePlatform", "scale": (0.06, 0.06, 0.01), "material": "MI_DevGreen"},
|
||||
"青蛙": {"asset": "Cube", "scale": (0.05, 0.05, 0.04), "material": "MI_DevGreen"},
|
||||
"下水道盖": {"asset": "CirclePlatform", "scale": (0.12, 0.12, 0.02), "material": "MI_DevGray"},
|
||||
"弹珠圈": {"asset": "CirclePlatform", "scale": (0.16, 0.16, 0.01), "material": "MI_DevGray"},
|
||||
# ---- 自然 ----
|
||||
"青苔": {"asset": "Cube", "scale": (0.6, 0.4, 0.01), "material": "MI_DevFoliage"},
|
||||
"蜘蛛网": {"asset": "Cube", "scale": (0.3, 0.01, 0.3), "material": "MI_DevWhite"},
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
"""区域定义、Outliner 文件夹结构、Actor 命名规范。
|
||||
|
||||
数据来源:
|
||||
- docs/design/prototype/关卡-v01-城郊小街-执行案.md §二
|
||||
- docs/technical/关卡-v01-搭建指南.md §〇
|
||||
"""
|
||||
|
||||
# ---- 场景全局参数 ----
|
||||
SCENE_PARAMS = {
|
||||
"scene_size": (8000, 6000), # cm
|
||||
"ground_z": 0,
|
||||
"character_height": 120, # cm
|
||||
"camera_height": 100, # cm
|
||||
"time_of_day": "15:00",
|
||||
"origin_corner": "西南角地平面",
|
||||
}
|
||||
|
||||
# ---- 区域定义 ----
|
||||
ZONES = {
|
||||
"Z1": {
|
||||
"name": "家",
|
||||
"name_en": "Home",
|
||||
"origin": (0, 0, 0),
|
||||
"bounds": (1000, 800),
|
||||
"color_material": "MI_DevRed",
|
||||
},
|
||||
"Z2": {
|
||||
"name": "小卖部",
|
||||
"name_en": "CornerShop",
|
||||
"origin": (1000, 200, 0),
|
||||
"bounds": (600, 600),
|
||||
"color_material": "MI_DevOrange",
|
||||
},
|
||||
"Z3": {
|
||||
"name": "主街道",
|
||||
"name_en": "MainStreet",
|
||||
"origin": (0, 800, 0),
|
||||
"bounds": (5000, 500),
|
||||
"color_material": "MI_DevGray",
|
||||
},
|
||||
"Z4": {
|
||||
"name": "空地",
|
||||
"name_en": "VacantLot",
|
||||
"origin": (3500, 1300, 0),
|
||||
"bounds": (1500, 1200),
|
||||
"color_material": "MI_DevBrown",
|
||||
},
|
||||
"Z5": {
|
||||
"name": "巷子",
|
||||
"name_en": "Alley",
|
||||
"origin": (2300, 1300, 0),
|
||||
"bounds": (300, 2000),
|
||||
"color_material": "MI_DevWhite",
|
||||
},
|
||||
"Z6": {
|
||||
"name": "水塘边",
|
||||
"name_en": "Pond",
|
||||
"origin": (1600, 3300, 0),
|
||||
"bounds": (1500, 1400),
|
||||
"color_material": "MI_DevSkyBlue",
|
||||
},
|
||||
"Z7": {
|
||||
"name": "老居民楼",
|
||||
"name_en": "OldBuilding",
|
||||
"origin": (5800, 200, 0),
|
||||
"bounds": (1200, 1800),
|
||||
"color_material": "MI_DevGray",
|
||||
},
|
||||
}
|
||||
|
||||
# ---- Outliner 文件夹结构 ----
|
||||
LEVEL_NAME = "LVL_SuburbanV01"
|
||||
|
||||
ZONE_FOLDERS = {
|
||||
"Z1": f"{LEVEL_NAME}/Z1_家",
|
||||
"Z2": f"{LEVEL_NAME}/Z2_小卖部",
|
||||
"Z3": f"{LEVEL_NAME}/Z3_主街道",
|
||||
"Z4": f"{LEVEL_NAME}/Z4_空地",
|
||||
"Z5": f"{LEVEL_NAME}/Z5_巷子",
|
||||
"Z6": f"{LEVEL_NAME}/Z6_水塘边",
|
||||
"Z7": f"{LEVEL_NAME}/Z7_老居民楼",
|
||||
"GLOBAL": f"{LEVEL_NAME}/Global",
|
||||
}
|
||||
|
||||
CATEGORY_FOLDERS = {
|
||||
"STR": "Structure",
|
||||
"PROP": "Props",
|
||||
"INTR": "Interactables",
|
||||
"COLL": "Collectibles",
|
||||
"BND": "Boundaries",
|
||||
"LGT": "Lighting",
|
||||
}
|
||||
|
||||
GLOBAL_SUBFOLDERS = {
|
||||
"BND": f"{LEVEL_NAME}/Global/Boundaries",
|
||||
"LGT": f"{LEVEL_NAME}/Global/Lighting",
|
||||
"PLAY": f"{LEVEL_NAME}/Global/PlayerStart",
|
||||
}
|
||||
|
||||
|
||||
def get_folder_path(zone_id: str, category: str) -> str:
|
||||
"""根据区域 ID 和类别获取 Outliner 文件夹路径。"""
|
||||
if zone_id == "GLOBAL":
|
||||
return GLOBAL_SUBFOLDERS.get(category, f"{LEVEL_NAME}/Global")
|
||||
base = ZONE_FOLDERS.get(zone_id, f"{LEVEL_NAME}/Global")
|
||||
sub = CATEGORY_FOLDERS.get(category, "")
|
||||
if sub:
|
||||
return f"{base}/{sub}"
|
||||
return base
|
||||
|
||||
|
||||
# ---- Actor 命名模板 ----
|
||||
# LVL_<ZoneID>_<Category>_<Description>
|
||||
# 已在搭建指南中给出完整名称,脚本直接使用。
|
||||
|
||||
|
||||
# ---- 搭建顺序 ----
|
||||
BUILD_ORDER = ["Z1", "Z3", "Z2", "Z4", "Z5", "Z6", "Z7"]
|
||||
|
||||
# ---- Player Start ----
|
||||
PLAYER_START = {
|
||||
"location": (500, 720, 0),
|
||||
"rotation": (0, 0, 0), # 面向 +Y(北)
|
||||
}
|
||||
|
||||
# ---- 光照 ----
|
||||
LIGHTING = {
|
||||
"directional_light": {
|
||||
"location": (0, 0, 500),
|
||||
"rotation": (-45, 45, 0), # 从西南照射,仰角45°
|
||||
"intensity": 3.0,
|
||||
"color": (1.0, 0.95, 0.85), # 暖白
|
||||
},
|
||||
"sky_light": {
|
||||
"intensity": 0.5,
|
||||
},
|
||||
"street_lights": [
|
||||
{"location": (1500, 1050, 0), "intensity": 0.3},
|
||||
{"location": (3000, 1050, 0), "intensity": 0.3},
|
||||
{"location": (4500, 1050, 0), "intensity": 0.3},
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
"""Level Builder CLI — 白模关卡自动生成工具。
|
||||
|
||||
用法:
|
||||
# Stage 1: 解析搭建指南 → JSON
|
||||
python main.py parse --input ../../docs/technical/关卡-v01-搭建指南.md --output output/level_v01.json
|
||||
|
||||
# Stage 1.5: 验证
|
||||
python main.py validate --input output/level_v01.json [--project-root ../..]
|
||||
|
||||
# Stage 2: UE5 编辑器内构建(需在编辑器 Python 环境中运行)
|
||||
# 方式 A: 在 Output Log Python 控制台:
|
||||
# exec(open(r"tools/level-builder/main.py").read())
|
||||
# main(["build", "--input", "output/level_v01.json"])
|
||||
# 方式 B: File > Execute Python Script
|
||||
|
||||
# Stage 3: 生成报告
|
||||
python main.py report --input output/level_v01.json [--build-log output/build_log.json] --output report.md
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 将项目 tools 目录加入 sys.path
|
||||
_TOOLS_DIR = Path(__file__).resolve().parent
|
||||
_LEVEL_BUILDER_DIR = _TOOLS_DIR
|
||||
_PROJECT_ROOT = _TOOLS_DIR.parent.parent
|
||||
|
||||
if str(_TOOLS_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(_TOOLS_DIR))
|
||||
if str(_PROJECT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_PROJECT_ROOT))
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="[%(levelname)s] %(name)s: %(message)s",
|
||||
)
|
||||
logger = logging.getLogger("level_builder")
|
||||
|
||||
|
||||
def cmd_parse(args):
|
||||
"""解析搭建指南 Markdown → JSON。"""
|
||||
from parsers.build_guide_parser import BuildGuideParser
|
||||
|
||||
input_path = args.input
|
||||
output_path = args.output or "output/level_data.json"
|
||||
|
||||
logger.info(f"解析搭建指南: {input_path}")
|
||||
|
||||
parser = BuildGuideParser()
|
||||
data = parser.parse_file(input_path)
|
||||
|
||||
# 写入 JSON
|
||||
out_path = Path(output_path)
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
summary = data.get("summary", {})
|
||||
logger.info(f"解析完成: {summary.get('total_actors', 0)} 个 Actor")
|
||||
logger.info(f" 按类别: {summary.get('by_category', {})}")
|
||||
logger.info(f" 按区域: {summary.get('by_zone', {})}")
|
||||
logger.info(f" 唯一资产数: {summary.get('unique_assets', 0)}")
|
||||
logger.info(f"输出: {out_path.resolve()}")
|
||||
|
||||
|
||||
def cmd_validate(args):
|
||||
"""验证 JSON 数据中的资产引用。"""
|
||||
from validation.asset_validator import validate_actors
|
||||
|
||||
input_path = args.input
|
||||
project_root = args.project_root or str(_PROJECT_ROOT)
|
||||
|
||||
logger.info(f"验证: {input_path}")
|
||||
|
||||
with open(input_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
actors = data.get("actors", [])
|
||||
result = validate_actors(actors, project_root)
|
||||
|
||||
if result.is_valid:
|
||||
logger.info("✅ 验证通过!")
|
||||
else:
|
||||
logger.error(f"❌ 验证失败: {len(result.errors)} 个错误")
|
||||
for err in result.errors:
|
||||
logger.error(f" {err}")
|
||||
|
||||
if result.warnings:
|
||||
for warn in result.warnings:
|
||||
logger.warning(f" {warn}")
|
||||
|
||||
logger.info(f"有效 Actor: {result.valid_actors}/{result.total_actors}")
|
||||
|
||||
# 保存验证结果
|
||||
output_path = Path(input_path).parent / "validation_result.json"
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
json.dump({
|
||||
"is_valid": result.is_valid,
|
||||
"total": result.total_actors,
|
||||
"valid": result.valid_actors,
|
||||
"errors": result.errors,
|
||||
"warnings": result.warnings,
|
||||
"missing_assets": sorted(result.missing_assets),
|
||||
"missing_materials": sorted(result.missing_materials),
|
||||
}, f, ensure_ascii=False, indent=2)
|
||||
logger.info(f"验证结果: {output_path}")
|
||||
|
||||
|
||||
def cmd_build(args):
|
||||
"""在 UE5 编辑器中构建关卡。"""
|
||||
try:
|
||||
from builder.scene_builder import build_level
|
||||
except ImportError:
|
||||
print("[错误] 无法导入 builder 模块。确保在 UE5 编辑器 Python 环境中运行。")
|
||||
print(" 在 Output Log 中切换到 Python,然后执行:")
|
||||
print(f' exec(open(r"{Path(__file__).resolve()}").read())')
|
||||
print(f' build_level(r"{Path(args.input).resolve()}")')
|
||||
return
|
||||
|
||||
zones = args.zones.split(",") if args.zones else None
|
||||
build_level(args.input, clear_existing=args.clear, zones=zones)
|
||||
|
||||
|
||||
def cmd_report(args):
|
||||
"""生成搭建报告。"""
|
||||
from report.report_generator import generate_report
|
||||
|
||||
input_path = args.input
|
||||
output_path = args.output or "output/搭建报告.md"
|
||||
build_log_path = args.build_log
|
||||
|
||||
logger.info(f"生成报告: {input_path}")
|
||||
|
||||
with open(input_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
build_log = None
|
||||
if build_log_path:
|
||||
try:
|
||||
with open(build_log_path, "r", encoding="utf-8") as f:
|
||||
build_log = json.load(f)
|
||||
except FileNotFoundError:
|
||||
logger.warning(f"构建日志不存在: {build_log_path},生成无状态报告")
|
||||
|
||||
report = generate_report(data, build_log, output_path)
|
||||
logger.info(f"报告已生成: {output_path}")
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
"""CLI 入口。"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="ChildHood 白模关卡自动生成工具",
|
||||
prog="level-builder",
|
||||
)
|
||||
subparsers = parser.add_subparsers(dest="command", help="子命令")
|
||||
|
||||
# parse
|
||||
p_parse = subparsers.add_parser("parse", help="解析搭建指南 Markdown → JSON")
|
||||
p_parse.add_argument("--input", "-i", required=True, help="搭建指南 Markdown 文件路径")
|
||||
p_parse.add_argument("--output", "-o", default="output/level_data.json", help="输出 JSON 路径")
|
||||
|
||||
# validate
|
||||
p_validate = subparsers.add_parser("validate", help="验证 JSON 数据")
|
||||
p_validate.add_argument("--input", "-i", required=True, help="JSON 文件路径")
|
||||
p_validate.add_argument("--project-root", "-r", help="项目根目录(用于文件存在性检查)")
|
||||
|
||||
# build
|
||||
p_build = subparsers.add_parser("build", help="在 UE5 中构建关卡")
|
||||
p_build.add_argument("--input", "-i", required=True, help="JSON 文件路径")
|
||||
p_build.add_argument("--clear", action="store_true", default=True, help="清除已有 LVL_* Actor")
|
||||
p_build.add_argument("--no-clear", dest="clear", action="store_false", help="不清除已有 Actor")
|
||||
p_build.add_argument("--zones", "-z", help="仅构建指定区域,逗号分隔(如 Z1,Z3)")
|
||||
|
||||
# report
|
||||
p_report = subparsers.add_parser("report", help="生成搭建报告")
|
||||
p_report.add_argument("--input", "-i", required=True, help="JSON 文件路径")
|
||||
p_report.add_argument("--build-log", "-l", help="构建日志 JSON 路径")
|
||||
p_report.add_argument("--output", "-o", default="output/搭建报告.md", help="报告输出路径")
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
if args.command == "parse":
|
||||
cmd_parse(args)
|
||||
elif args.command == "validate":
|
||||
cmd_validate(args)
|
||||
elif args.command == "build":
|
||||
cmd_build(args)
|
||||
elif args.command == "report":
|
||||
cmd_report(args)
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"is_valid": true,
|
||||
"total": 265,
|
||||
"valid": 265,
|
||||
"errors": [],
|
||||
"warnings": [],
|
||||
"missing_assets": [],
|
||||
"missing_materials": []
|
||||
}
|
||||
@@ -0,0 +1,455 @@
|
||||
# 白模搭建报告 — 自动生成
|
||||
|
||||
> 生成时间: 2026-06-07 23:03
|
||||
> 数据来源: 关卡-v01-搭建指南.md
|
||||
> 场景尺寸: 8000cm × 6000cm
|
||||
|
||||
## 一、搭建概要
|
||||
|
||||
- **总 Actor 数**: 265
|
||||
- 结构体 (STR): 123
|
||||
- 布景 (PROP): 53
|
||||
- 交互物 (INTR): 10
|
||||
- 收集品 (COLL): 9
|
||||
- 边界 (BND): 61
|
||||
- 光源 (LGT): 8
|
||||
- PlayerStart (PLAY): 1
|
||||
|
||||
## 二、逐区域对照
|
||||
|
||||
### Z1 家
|
||||
|
||||
共 40 个 Actor
|
||||
|
||||
| # | Actor 名称 | 资产 | 位置 | 缩放 | 材质 | 状态 |
|
||||
|---|-----------|------|------|------|------|------|
|
||||
| 1 | LVL_Z1_STR_Floor | Floor_1000x1000 | (500,400,0) | (1.00,0.80,1.00) | MI_DevGray | ✅ |
|
||||
| 2 | LVL_Z1_STR_SouthWall_01 | Wall_400x300 | (200,0,0) | — | MI_DevRed | ✅ |
|
||||
| 3 | LVL_Z1_STR_SouthWall_02 | Wall_400x300 | (600,0,0) | — | MI_DevRed | ✅ |
|
||||
| 4 | LVL_Z1_STR_WestWall_01 | Wall_400x300 | (0,200,0) | — | MI_DevRed | ✅ |
|
||||
| 5 | LVL_Z1_STR_WestWall_02 | Wall_400x300 | (0,600,0) | — | MI_DevRed | ✅ |
|
||||
| 6 | LVL_Z1_STR_EastWall_01 | Wall_400x300 | (980,200,0) | — | MI_DevRed | ✅ |
|
||||
| 7 | LVL_Z1_STR_EastWall_02 | Wall_400x300 | (980,600,0) | — | MI_DevRed | ✅ |
|
||||
| 8 | LVL_Z1_STR_NorthWall_West | Wall_400x300 | (200,780,0) | — | MI_DevRed | ✅ |
|
||||
| 9 | LVL_Z1_STR_NorthWall_Gate | WallDoor_200x300 | (500,780,0) | — | MI_DevRed | ✅ |
|
||||
| 10 | LVL_Z1_STR_NorthWall_East | Wall_400x300 | (800,780,0) | — | MI_DevRed | ✅ |
|
||||
| 11 | LVL_Z1_STR_GateDoor | RotativeDoor_01 | (500,780,0) | — | MI_DevGreen | ✅ |
|
||||
| 12 | LVL_Z1_STR_HouseSouthWall | WallWindow_400x300 | (400,30,0) | — | MI_DevWhite | ✅ |
|
||||
| 13 | LVL_Z1_STR_HouseSouthWin | WallWindowSmall_200x300 | (600,30,0) | — | MI_DevWhite | ✅ |
|
||||
| 14 | LVL_Z1_STR_HouseWestWall | Wall_400x300 | (200,220,0) | — | MI_DevWhite | ✅ |
|
||||
| 15 | LVL_Z1_STR_HouseWestTop | Wall_400x100 | (200,220,300) | — | MI_DevWhite | ✅ |
|
||||
| 16 | LVL_Z1_STR_HouseEastWall | Wall_400x300 | (780,220,0) | — | MI_DevWhite | ✅ |
|
||||
| 17 | LVL_Z1_STR_HouseEastTop | Wall_400x100 | (780,220,300) | — | MI_DevWhite | ✅ |
|
||||
| 18 | LVL_Z1_STR_HouseNorthWall | WallDoor_200x300 | (400,410,0) | — | MI_DevWhite | ✅ |
|
||||
| 19 | LVL_Z1_STR_HouseNorthFill | Wall_400x300 | (600,410,0) | — | MI_DevWhite | ✅ |
|
||||
| 20 | LVL_Z1_STR_HouseRoof_01 | Floor_300x400 | (350,230,280) | — | MI_DevBrown | ✅ |
|
||||
| 21 | LVL_Z1_STR_HouseRoof_02 | Floor_300x400 | (650,230,280) | — | MI_DevBrown | ✅ |
|
||||
| 22 | LVL_Z1_STR_CourtyardFloor | Floor_1000x1000 | (500,400,0) | (1.00,0.80,1.00) | MI_DevGray | ✅ |
|
||||
| 23 | LVL_Z1_PROP_OldBicycle | Car01 | (100,650,0) | (0.20,0.20,0.20) | — | ✅ |
|
||||
| 24 | LVL_Z1_PROP_ClothesPole_Beam | Beam_300 | (500,600,180) | — | MI_DevWhite | ✅ |
|
||||
| 25 | LVL_Z1_PROP_ClothesPole_Pole1 | Cylinder | (380,600,0) | (0.05,0.05,3.60) | MI_DevGray | ✅ |
|
||||
| 26 | LVL_Z1_PROP_ClothesPole_Pole2 | Cylinder | (620,600,0) | (0.05,0.05,3.60) | MI_DevGray | ✅ |
|
||||
| 27 | LVL_Z1_PROP_JunkPile_Box1 | WoodenBox_Small | (850,100,0) | — | — | ✅ |
|
||||
| 28 | LVL_Z1_PROP_JunkPile_Box2 | WoodenBox_Small | (870,120,35) | — | — | ✅ |
|
||||
| 29 | LVL_Z1_PROP_JunkPile_Bucket | Bucket_01 | (890,80,0) | — | — | ✅ |
|
||||
| 30 | LVL_Z1_PROP_DoorMat | Cube | (480,740,0) | (0.60,0.40,0.02) | MI_DevGray | ✅ |
|
||||
| 31 | LVL_Z1_PROP_Fridge | Barrel | (720,80,0) | (0.30,0.30,1.60) | MI_DevWhite | ✅ |
|
||||
| 32 | LVL_Z1_PROP_TV | Monitor_01 | (320,80,45) | — | MI_DevGray | ✅ |
|
||||
| 33 | LVL_Z1_PROP_Desk | SimpleTable_01 | (300,70,0) | — | — | ✅ |
|
||||
| 34 | LVL_Z1_PROP_Bed | BedSingle | (350,250,0) | — | — | ✅ |
|
||||
| 35 | LVL_Z1_PROP_Calendar | Cube | (500,40,180) | (0.30,0.02,0.40) | MI_DevRed | ✅ |
|
||||
| 36 | LVL_Z1_PROP_TVStand | SimpleTable_02 | (320,55,0) | (0.85,0.75,0.67) | — | ✅ |
|
||||
| 37 | LVL_Z1_INTR_01_Fridge | Cube | (720,80,80) | (0.30,0.30,0.30) | MI_DevYellow | ✅ |
|
||||
| 38 | LVL_Z1_INTR_02_Gate | Cube | (500,780,0) | (0.30,0.30,0.30) | MI_DevYellow | ✅ |
|
||||
| 39 | LVL_Z1_INTR_06_TV | Cube | (320,80,70) | (0.30,0.30,0.30) | MI_DevYellow | ✅ |
|
||||
| 40 | LVL_Z1_COLL_10_CatEyeMarble | CirclePlatform | (870,140,0) | (0.03,0.03,0.03) | MI_DevYellow | ✅ |
|
||||
|
||||
### Z2 小卖部
|
||||
|
||||
共 26 个 Actor
|
||||
|
||||
| # | Actor 名称 | 资产 | 位置 | 缩放 | 材质 | 状态 |
|
||||
|---|-----------|------|------|------|------|------|
|
||||
| 1 | LVL_Z2_STR_Floor | Floor_1000x1000 | (1300,500,0) | (0.60,0.60,1.00) | MI_DevGray | ✅ |
|
||||
| 2 | LVL_Z2_STR_SouthWall | Wall_400x300 | (1200,200,0) | — | MI_DevOrange | ✅ |
|
||||
| 3 | LVL_Z2_STR_SouthWall2 | Wall_200x300 | (1500,200,0) | — | MI_DevOrange | ✅ |
|
||||
| 4 | LVL_Z2_STR_WestWall | Wall_400x300 | (1000,400,0) | — | MI_DevOrange | ✅ |
|
||||
| 5 | LVL_Z2_STR_WestWallTop | Wall_400x100 | (1000,400,300) | — | MI_DevOrange | ✅ |
|
||||
| 6 | LVL_Z2_STR_EastWall | Wall_400x300 | (1580,400,0) | — | MI_DevOrange | ✅ |
|
||||
| 7 | LVL_Z2_STR_EastWallTop | Wall_400x100 | (1580,400,300) | — | MI_DevOrange | ✅ |
|
||||
| 8 | LVL_Z2_STR_NorthWall_West | Wall_200x300 | (1100,580,0) | — | MI_DevOrange | ✅ |
|
||||
| 9 | LVL_Z2_STR_NorthWall_Garage | WallDoorGarage_400x300 | (1300,580,0) | — | MI_DevOrange | ✅ |
|
||||
| 10 | LVL_Z2_STR_NorthWall_East | Wall_200x300 | (1500,580,0) | — | MI_DevOrange | ✅ |
|
||||
| 11 | LVL_Z2_STR_GarageDoor | GarageDoorOpen_01 | (1300,580,0) | — | MI_DevGray | ✅ |
|
||||
| 12 | LVL_Z2_STR_Roof_01 | Floor_300x400 | (1250,400,280) | — | MI_DevBrown | ✅ |
|
||||
| 13 | LVL_Z2_STR_Roof_02 | Floor_300x400 | (1350,400,280) | — | MI_DevBrown | ✅ |
|
||||
| 14 | LVL_Z2_STR_Awning_01 | Floor_200x300 | (1100,700,260) | — | MI_DevWhite | ✅ |
|
||||
| 15 | LVL_Z2_STR_Awning_02 | Floor_200x300 | (1400,700,260) | — | MI_DevWhite | ✅ |
|
||||
| 16 | LVL_Z2_PROP_Counter | Cube | (1200,500,0) | (2.00,0.60,0.90) | MI_DevGlassWhite | ✅ |
|
||||
| 17 | LVL_Z2_PROP_ShelfEast_01 | Fence_Small_400 | (1520,300,0) | (1.00,1.00,1.80) | MI_DevGray | ✅ |
|
||||
| 18 | LVL_Z2_PROP_ShelfEast_02 | Fence_Small_400 | (1520,300,180) | (1.00,1.00,1.80) | MI_DevGray | ✅ |
|
||||
| 19 | LVL_Z2_PROP_ShelfSouth | Fence_Small_400 | (1100,230,0) | (1.00,1.00,1.80) | MI_DevGray | ✅ |
|
||||
| 20 | LVL_Z2_PROP_Freezer | Container_Normal | (1050,400,0) | (0.25,0.15,0.40) | MI_DevWhite | ✅ |
|
||||
| 21 | LVL_Z2_PROP_SmallTV | Monitor_01 | (1350,540,90) | (0.60,0.60,0.60) | MI_DevGray | ✅ |
|
||||
| 22 | LVL_Z2_PROP_Signboard | Cube | (1150,610,280) | (3.00,0.05,0.40) | MI_DevBlue | ✅ |
|
||||
| 23 | LVL_Z2_PROP_CardRack | Cube | (1400,550,0) | (0.40,0.20,0.30) | MI_DevGray | ✅ |
|
||||
| 24 | LVL_Z2_INTR_05_ShopOwner | Cube | (1300,480,0) | (0.30,0.30,0.30) | MI_DevYellow | ✅ |
|
||||
| 25 | LVL_Z2_INTR_07_SnackShelf | Cube | (1520,350,90) | (0.30,0.30,0.30) | MI_DevYellow | ✅ |
|
||||
| 26 | LVL_Z2_COLL_09_BouncyBall | CirclePlatform | (1300,700,0) | (0.03,0.03,0.03) | MI_DevWhite | ✅ |
|
||||
|
||||
### Z3 主街道
|
||||
|
||||
共 42 个 Actor
|
||||
|
||||
| # | Actor 名称 | 资产 | 位置 | 缩放 | 材质 | 状态 |
|
||||
|---|-----------|------|------|------|------|------|
|
||||
| 1 | LVL_Z3_STR_Floor_01 | Floor_1000x1000 | (500,1050,0) | (1.00,0.50,1.00) | MI_DevGray | ✅ |
|
||||
| 2 | LVL_Z3_STR_Floor_02 | Floor_1000x1000 | (1500,1050,0) | (1.00,0.50,1.00) | MI_DevGray | ✅ |
|
||||
| 3 | LVL_Z3_STR_Floor_03 | Floor_1000x1000 | (2500,1050,0) | (1.00,0.50,1.00) | MI_DevGray | ✅ |
|
||||
| 4 | LVL_Z3_STR_Floor_04 | Floor_1000x1000 | (3500,1050,0) | (1.00,0.50,1.00) | MI_DevGray | ✅ |
|
||||
| 5 | LVL_Z3_STR_Floor_05 | Floor_1000x1000 | (4500,1050,0) | (1.00,0.50,1.00) | MI_DevGray | ✅ |
|
||||
| 6 | LVL_Z3_STR_SouthWall_C1 | Wall_400x300 | (1800,800,0) | — | MI_DevGray | ✅ |
|
||||
| 7 | LVL_Z3_STR_SouthWall_C2 | Wall_400x300 | (2200,800,0) | — | MI_DevGray | ✅ |
|
||||
| 8 | LVL_Z3_STR_SouthWall_C3 | Wall_400x300 | (2600,800,0) | — | MI_DevGray | ✅ |
|
||||
| 9 | LVL_Z3_STR_SouthWall_C4 | Wall_400x300 | (3000,800,0) | — | MI_DevGray | ✅ |
|
||||
| 10 | LVL_Z3_STR_SouthWall_C5 | Wall_400x300 | (3400,800,0) | — | MI_DevGray | ✅ |
|
||||
| 11 | LVL_Z3_STR_SouthWall_C6 | Wall_400x300 | (3800,800,0) | — | MI_DevGray | ✅ |
|
||||
| 12 | LVL_Z3_STR_SouthWall_D1 | Wall_400x300 | (4200,800,0) | — | MI_DevGray | ✅ |
|
||||
| 13 | LVL_Z3_STR_SouthWall_D2 | Wall_400x300 | (4600,800,0) | — | MI_DevGray | ✅ |
|
||||
| 14 | LVL_Z3_STR_NorthWall_A1 | Wall_400x300 | (200,1300,0) | — | MI_DevGray | ✅ |
|
||||
| 15 | LVL_Z3_STR_NorthWall_A2 | Wall_400x300 | (600,1300,0) | — | MI_DevGray | ✅ |
|
||||
| 16 | LVL_Z3_STR_NorthWall_A3 | Wall_400x300 | (1000,1300,0) | — | MI_DevGray | ✅ |
|
||||
| 17 | LVL_Z3_STR_NorthWall_A4 | Wall_400x300 | (1400,1300,0) | — | MI_DevGray | ✅ |
|
||||
| 18 | LVL_Z3_STR_NorthWall_A5 | Wall_400x300 | (1800,1300,0) | — | MI_DevGray | ✅ |
|
||||
| 19 | LVL_Z3_STR_NorthWall_A6 | Wall_200x300 | (2100,1300,0) | — | MI_DevGray | ✅ |
|
||||
| 20 | LVL_Z3_STR_NorthWall_C1 | Wall_400x300 | (2800,1300,0) | — | MI_DevGray | ✅ |
|
||||
| 21 | LVL_Z3_STR_NorthWall_C2 | Wall_400x300 | (3200,1300,0) | — | MI_DevGray | ✅ |
|
||||
| 22 | LVL_Z3_STR_NorthWall_C3 | Wall_100x300 | (3450,1300,0) | — | MI_DevGray | ✅ |
|
||||
| 23 | LVL_Z3_PROP_PhoneBooth | Cube | (1800,380,0) | (0.80,0.80,2.20) | MI_DevGray | ✅ |
|
||||
| 24 | LVL_Z3_PROP_Bicycle | Car01 | (2800,880,0) | (0.20,0.20,0.20) | — | ✅ |
|
||||
| 25 | LVL_Z3_PROP_ClothesPole | Beam_300 | (1200,1250,180) | — | MI_DevWhite | ✅ |
|
||||
| 26 | LVL_Z3_PROP_ClothesPolePole | Cylinder | (1200,1250,0) | (0.05,0.05,3.60) | MI_DevGray | ✅ |
|
||||
| 27 | LVL_Z3_PROP_SewerCover | CirclePlatform | (800,1050,0) | (0.12,0.12,0.02) | MI_DevGray | ✅ |
|
||||
| 28 | LVL_Z3_PROP_Graffiti | Cube | (3200,1270,100) | (1.20,0.05,0.80) | MI_DevYellow | ✅ |
|
||||
| 29 | LVL_Z3 PROP_AdWall | DisplayPanels_400x200 | (3800,1280,0) | — | MI_DevGray | ✅ |
|
||||
| 30 | LVL_Z3_PROP_TrashCan | TrashCans_01 | (4200,850,0) | — | MI_DevGreen | ✅ |
|
||||
| 31 | LVL_Z3_PROP_PoleWest | Cylinder | (1000,1050,0) | (0.04,0.04,12.00) | MI_DevGray | ✅ |
|
||||
| 32 | LVL_Z3_PROP_PoleEast | Cylinder | (3500,1050,0) | (0.04,0.04,12.00) | MI_DevGray | ✅ |
|
||||
| 33 | LVL_Z3_INTR_04_PhoneBooth | Cube | (1800,380,110) | (0.30,0.30,0.30) | MI_DevYellow | ✅ |
|
||||
| 34 | LVL_Z3_INTR_09_Graffiti | Cube | (3200,1270,140) | (0.30,0.30,0.30) | MI_DevYellow | ✅ |
|
||||
| 35 | LVL_Z3_COLL_08_IceFlowerMarble | CirclePlatform | (820,1060,0) | (0.03,0.03,0.03) | MI_DevWhite | ✅ |
|
||||
| 36 | LVL_Z3_LGT_StreetLight_01 | RoadLamp_01 | (1500,1050,0) | — | — | ✅ |
|
||||
| 37 | LVL_Z3_LGT_StreetLight_02 | RoadLamp_01 | (3000,1050,0) | — | — | ✅ |
|
||||
| 38 | LVL_Z3_LGT_StreetLight_03 | RoadLamp_01 | (4500,1050,0) | — | — | ✅ |
|
||||
| 39 | LVL_Z3_PROP_TrashCan_02 | TrashCans_01 | (2000,850,0) | — | MI_DevGreen | ✅ |
|
||||
| 40 | LVL_Z3_LGT_PointLight_01 | | (1500,1050,400) | — | — | ✅ |
|
||||
| 41 | LVL_Z3_LGT_PointLight_02 | | (3000,1050,400) | — | — | ✅ |
|
||||
| 42 | LVL_Z3_LGT_PointLight_03 | | (4500,1050,400) | — | — | ✅ |
|
||||
|
||||
### Z4 空地
|
||||
|
||||
共 20 个 Actor
|
||||
|
||||
| # | Actor 名称 | 资产 | 位置 | 缩放 | 材质 | 状态 |
|
||||
|---|-----------|------|------|------|------|------|
|
||||
| 1 | LVL_Z4_STR_Floor_01 | Floor_1000x1000 | (4000,1800,10) | (1.50,1.20,1.00) | MI_DevBrown | ✅ |
|
||||
| 2 | LVL_Z4_STR_EastBound_01 | BarrierWall_600 | (4980,1600,0) | — | MI_DevBlue | ✅ |
|
||||
| 3 | LVL_Z4_STR_EastBound_02 | BarrierWall_600 | (4980,2200,0) | — | MI_DevBlue | ✅ |
|
||||
| 4 | LVL_Z4_STR_NorthBound_01 | BarrierWall_600 | (3800,2480,0) | — | MI_DevBlue | ✅ |
|
||||
| 5 | LVL_Z4_STR_NorthBound_02 | BarrierWall_600 | (4400,2480,0) | — | MI_DevBlue | ✅ |
|
||||
| 6 | LVL_Z4_STR_NorthBound_03 | BarrierWall_200 | (4700,2480,0) | — | MI_DevBlue | ✅ |
|
||||
| 7 | LVL_Z4_STR_WestLowWall_01 | BarrierWall_200 | (3500,1500,0) | — | MI_DevGray | ✅ |
|
||||
| 8 | LVL_Z4_STR_WestLowWall_02 | BarrierWall_200 | (3500,1900,0) | — | MI_DevGray | ✅ |
|
||||
| 9 | LVL_Z4_STR_WestLowWall_03 | BarrierWall_200 | (3500,2300,0) | — | MI_DevGray | ✅ |
|
||||
| 10 | LVL_Z4_PROP_BigTree | Tree_01 | (4250,1900,10) | (1.20,1.20,1.20) | MI_DevFoliage | ✅ |
|
||||
| 11 | LVL_Z4_PROP_Bricks_01~05 | WoodenBox_Small | (0,0,0) | — | — | ✅ |
|
||||
| 12 | LVL_Z4_PROP_Puddle | Cube | (4600,1500,5) | (1.20,0.80,0.01) | MI_DevGlassBlue | ✅ |
|
||||
| 13 | LVL_Z4_PROP_MarbleCircle | CirclePlatform | (4100,1800,12) | (0.16,0.16,0.01) | MI_DevGray | ✅ |
|
||||
| 14 | LVL_Z4_PROP_Debris_01 | WoodenPallet_01 | (4800,2300,10) | — | — | ✅ |
|
||||
| 15 | LVL_Z4_PROP_Debris_02 | WoodenBox_Normal | (4850,2250,10) | — | — | ✅ |
|
||||
| 16 | LVL_Z4_INTR_03_BlueMarble | Cube | (3900,1700,10) | (0.30,0.30,0.30) | MI_DevYellow | ✅ |
|
||||
| 17 | LVL_Z4_INTR_08_SmallStone | Cube | (4550,1550,10) | (0.30,0.30,0.30) | MI_DevYellow | ✅ |
|
||||
| 18 | LVL_Z4_COLL_01_BlueMarble | CirclePlatform | (3900,1700,10) | (0.03,0.03,0.03) | MI_DevWhite | ✅ |
|
||||
| 19 | LVL_Z4_COLL_03_SmallStone | CirclePlatform | (4550,1550,10) | (0.03,0.03,0.03) | MI_DevWhite | ✅ |
|
||||
| 20 | LVL_Z4_COLL_04_LuZhishenCard | CirclePlatform | (3720,1610,10) | (0.03,0.03,0.03) | MI_DevYellow | ✅ |
|
||||
|
||||
### Z5 巷子
|
||||
|
||||
共 19 个 Actor
|
||||
|
||||
| # | Actor 名称 | 资产 | 位置 | 缩放 | 材质 | 状态 |
|
||||
|---|-----------|------|------|------|------|------|
|
||||
| 1 | LVL_Z5_STR_Floor_01 | Floor_1000x1000 | (2450,1800,0) | (0.30,1.00,1.00) | MI_DevGray | ✅ |
|
||||
| 2 | LVL_Z5_STR_Floor_02 | Floor_1000x1000 | (2450,2800,0) | (0.30,1.00,1.00) | MI_DevGray | ✅ |
|
||||
| 3 | LVL_Z5_STR_WestWall_01 | Wall_400x300 | (2300,1500,0) | — | MI_DevWhite | ✅ |
|
||||
| 4 | LVL_Z5_STR_WestWall_02 | Wall_400x300 | (2300,1900,0) | — | MI_DevWhite | ✅ |
|
||||
| 5 | LVL_Z5_STR_WestWall_03 | Wall_400x300 | (2300,2300,0) | — | MI_DevWhite | ✅ |
|
||||
| 6 | LVL_Z5_STR_WestWall_04 | Wall_400x300 | (2300,2700,0) | — | MI_DevWhite | ✅ |
|
||||
| 7 | LVL_Z5_STR_WestWall_05 | Wall_400x300 | (2300,3100,0) | — | MI_DevWhite | ✅ |
|
||||
| 8 | LVL_Z5_STR_EastWall_01 | Wall_400x300 | (2580,1500,0) | — | MI_DevWhite | ✅ |
|
||||
| 9 | LVL_Z5_STR_EastWall_02 | Wall_400x300 | (2580,1900,0) | — | MI_DevWhite | ✅ |
|
||||
| 10 | LVL_Z5_STR_EastWall_03 | Wall_400x300 | (2580,2300,0) | — | MI_DevWhite | ✅ |
|
||||
| 11 | LVL_Z5_STR_EastWall_04 | Wall_400x300 | (2580,2700,0) | — | MI_DevWhite | ✅ |
|
||||
| 12 | LVL_Z5_STR_EastWall_05 | Wall_400x300 | (2580,3100,0) | — | MI_DevWhite | ✅ |
|
||||
| 13 | LVL_Z5_PROP_WasteBox | WoodenBox_Small | (2380,2900,0) | — | — | ✅ |
|
||||
| 14 | LVL_Z5_PROP_SpiderWeb | Cube | (2305,2100,250) | (0.30,0.30,0.01) | MI_DevWhite | ✅ |
|
||||
| 15 | LVL_Z5_PROP_OldWindow | FakeWindowsFrame | (2310,2100,150) | — | MI_DevGray | ✅ |
|
||||
| 16 | LVL_Z5_PROP_FlowerPot | Cylinder | (2340,2100,150) | (0.20,0.20,0.25) | MI_DevGreen | ✅ |
|
||||
| 17 | LVL_Z5_PROP_Moss | Cube | (2400,2700,0) | (0.60,0.40,0.01) | MI_DevFoliage | ✅ |
|
||||
| 18 | LVL_Z5_INTR_10_WasteBox | Cube | (2380,2900,25) | (0.30,0.30,0.30) | MI_DevYellow | ✅ |
|
||||
| 19 | LVL_Z5_COLL_02_RedMarble | CirclePlatform | (2500,3000,0) | (0.03,0.03,0.03) | MI_DevWhite | ✅ |
|
||||
|
||||
### Z6 水塘边
|
||||
|
||||
共 16 个 Actor
|
||||
|
||||
| # | Actor 名称 | 资产 | 位置 | 缩放 | 材质 | 状态 |
|
||||
|---|-----------|------|------|------|------|------|
|
||||
| 1 | LVL_Z6_STR_GroundSouth | Floor_1000x1000 | (2350,3475,0) | (1.50,0.35,1.00) | MI_DevBrown | ✅ |
|
||||
| 2 | LVL_Z6_STR_GroundEast | Floor_1000x1000 | (3075,4000,0) | (0.15,0.80,1.00) | MI_DevBrown | ✅ |
|
||||
| 3 | LVL_Z6_STR_GroundWest | Floor_1000x1000 | (1675,4000,0) | (0.15,0.80,1.00) | MI_DevBrown | ✅ |
|
||||
| 4 | LVL_Z6_STR_Water | Floor_1000x1000 | (2350,4000,-20) | (1.20,0.80,0.50) | MI_DevSkyBlue | ✅ |
|
||||
| 5 | LVL_Z6_STR_EastBound_01 | BarrierWall_600 | (3080,3600,0) | — | MI_DevBlue | ✅ |
|
||||
| 6 | LVL_Z6_STR_EastBound_02 | BarrierWall_600 | (3080,4200,0) | — | MI_DevBlue | ✅ |
|
||||
| 7 | LVL_Z6_STR_NorthBound_01 | BarrierWall_600 | (1900,4680,0) | — | MI_DevBlue | ✅ |
|
||||
| 8 | LVL_Z6_STR_NorthBound_02 | BarrierWall_600 | (2500,4680,0) | — | MI_DevBlue | ✅ |
|
||||
| 9 | LVL_Z6_STR_NorthBound_03 | BarrierWall_200 | (2800,4680,0) | — | MI_DevBlue | ✅ |
|
||||
| 10 | LVL_Z6_PROP_Reeds_S1~5 | Cylinder | (1800,3600,0) | (0.08,0.08,2.00) | MI_DevFoliage | ✅ |
|
||||
| 11 | LVL_Z6_PROP_Reeds_S2_1~5 | Cylinder | (2700,3600,0) | (0.08,0.08,2.00) | MI_DevFoliage | ✅ |
|
||||
| 12 | LVL_Z6_PROP_Reeds_E_1~3 | Cylinder | (2900,4100,0) | (0.08,0.08,1.60) | MI_DevFoliage | ✅ |
|
||||
| 13 | LVL_Z6_PROP_LilyPad_1~4 | CirclePlatform | (2200,3900,-18) | (0.06,0.06,0.01) | MI_DevGreen | ✅ |
|
||||
| 14 | LVL_Z6_PROP_Frog | Cube | (2100,3680,0) | (0.05,0.05,0.04) | MI_DevGreen | ✅ |
|
||||
| 15 | LVL_Z6_COLL_05_CandyWrapper | CirclePlatform | (2000,3500,0) | (0.03,0.03,0.03) | MI_DevWhite | ✅ |
|
||||
| 16 | LVL_Z6_COLL_06_Eraser | CirclePlatform | (2500,3450,0) | (0.03,0.03,0.03) | MI_DevWhite | ✅ |
|
||||
|
||||
### Z7 老居民楼
|
||||
|
||||
共 38 个 Actor
|
||||
|
||||
| # | Actor 名称 | 资产 | 位置 | 缩放 | 材质 | 状态 |
|
||||
|---|-----------|------|------|------|------|------|
|
||||
| 1 | LVL_BND_West_01 | Wall_400x300 | (0,200,0) | — | MI_DevRed | ✅ |
|
||||
| 2 | LVL_BND_West_02 | Wall_400x300 | (0,600,0) | — | MI_DevRed | ✅ |
|
||||
| 3 | LVL_BND_West_03 | Wall_400x300 | (0,1000,0) | — | MI_DevRed | ✅ |
|
||||
| 4 | LVL_BND_West_04 | Wall_400x300 | (0,1400,0) | — | MI_DevRed | ✅ |
|
||||
| 5 | LVL_BND_West_05 | Wall_400x300 | (0,1800,0) | — | MI_DevRed | ✅ |
|
||||
| 6 | LVL_BND_West_06 | Wall_400x300 | (0,2200,0) | — | MI_DevRed | ✅ |
|
||||
| 7 | LVL_BND_West_07 | Wall_400x300 | (0,2600,0) | — | MI_DevRed | ✅ |
|
||||
| 8 | LVL_BND_West_08 | Wall_400x300 | (0,3000,0) | — | MI_DevRed | ✅ |
|
||||
| 9 | LVL_BND_West_09 | Wall_400x300 | (0,3400,0) | — | MI_DevRed | ✅ |
|
||||
| 10 | LVL_BND_West_10 | Wall_400x300 | (0,3800,0) | — | MI_DevRed | ✅ |
|
||||
| 11 | LVL_BND_West_11 | Wall_400x300 | (0,4200,0) | — | MI_DevRed | ✅ |
|
||||
| 12 | LVL_BND_West_12 | Wall_400x300 | (0,4600,0) | — | MI_DevRed | ✅ |
|
||||
| 13 | LVL_BND_West_13 | Wall_400x300 | (0,5000,0) | — | MI_DevRed | ✅ |
|
||||
| 14 | LVL_BND_West_14 | Wall_400x300 | (0,5400,0) | — | MI_DevRed | ✅ |
|
||||
| 15 | LVL_BND_West_15 | Wall_400x300 | (0,5800,0) | — | MI_DevRed | ✅ |
|
||||
| 16 | LVL_BND_East_01~15 | BarrierWall_600 | (0,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 17 | LVL_BND_North_01~20 | Wall_400x300 | (0,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 18 | LVL_BND_South_01 | BarrierWall_600 | (1900,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 19 | LVL_BND_South_02 | BarrierWall_600 | (2500,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 20 | LVL_BND_South_03 | BarrierWall_600 | (3100,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 21 | LVL_BND_South_04 | BarrierWall_600 | (3700,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 22 | LVL_BND_South_05 | BarrierWall_600 | (4300,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 23 | LVL_BND_South_06 | BarrierWall_600 | (4900,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 24 | LVL_BND_South_07 | BarrierWall_600 | (5500,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 25 | LVL_BND_South_08 | BarrierWall_600 | (6100,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 26 | LVL_BND_South_09 | BarrierWall_600 | (6700,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 27 | LVL_BND_South_10 | BarrierWall_600 | (7300,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 28 | LVL_BND_South_11 | BarrierWall_600 | (7900,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 29 | LVL_Z7_STR_Building | Building_01 | (6400,800,0) | — | MI_DevGray | ✅ |
|
||||
| 30 | LVL_Z7_STR_CollisionFront | Wall_400x300 | (6200,150,0) | — | MI_DevGray | ✅ |
|
||||
| 31 | LVL_Z7_STR_CollisionFront2 | Wall_400x300 | (6600,150,0) | — | MI_DevGray | ✅ |
|
||||
| 32 | LVL_Z7_STR_CollisionFront3 | Wall_400x300 | (7000,150,0) | — | MI_DevGray | ✅ |
|
||||
| 33 | LVL_Z7_STR_CollisionSide | Wall_400x300 | (5750,400,0) | (0.00,90.00,0.00) | MI_DevGray | ✅ |
|
||||
| 34 | LVL_Z7_STR_CollisionSide2 | Wall_400x300 | (5750,800,0) | (0.00,90.00,0.00) | MI_DevGray | ✅ |
|
||||
| 35 | LVL_Z7_PROP_ClothesPole_01 | Beam_300 | (6100,190,800) | — | MI_DevWhite | ✅ |
|
||||
| 36 | LVL_Z7_PROP_ClothesPole_02 | Beam_300 | (6500,190,1200) | — | MI_DevWhite | ✅ |
|
||||
| 37 | LVL_Z7_PROP_ClothesPole_03 | Beam_300 | (6900,190,1600) | — | MI_DevWhite | ✅ |
|
||||
| 38 | LVL_Z7_PROP_TrashCan | TrashCans_01 | (6000,140,0) | — | MI_DevGreen | ✅ |
|
||||
|
||||
### GLOBAL 全局
|
||||
|
||||
共 64 个 Actor
|
||||
|
||||
| # | Actor 名称 | 资产 | 位置 | 缩放 | 材质 | 状态 |
|
||||
|---|-----------|------|------|------|------|------|
|
||||
| 1 | LVL_BND_West_01 | Wall_400x300 | (0,200,0) | — | MI_DevRed | ✅ |
|
||||
| 2 | LVL_BND_West_02 | Wall_400x300 | (0,600,0) | — | MI_DevRed | ✅ |
|
||||
| 3 | LVL_BND_West_03 | Wall_400x300 | (0,1000,0) | — | MI_DevRed | ✅ |
|
||||
| 4 | LVL_BND_West_04 | Wall_400x300 | (0,1400,0) | — | MI_DevRed | ✅ |
|
||||
| 5 | LVL_BND_West_05 | Wall_400x300 | (0,1800,0) | — | MI_DevRed | ✅ |
|
||||
| 6 | LVL_BND_West_06 | Wall_400x300 | (0,2200,0) | — | MI_DevRed | ✅ |
|
||||
| 7 | LVL_BND_West_07 | Wall_400x300 | (0,2600,0) | — | MI_DevRed | ✅ |
|
||||
| 8 | LVL_BND_West_08 | Wall_400x300 | (0,3000,0) | — | MI_DevRed | ✅ |
|
||||
| 9 | LVL_BND_West_09 | Wall_400x300 | (0,3400,0) | — | MI_DevRed | ✅ |
|
||||
| 10 | LVL_BND_West_10 | Wall_400x300 | (0,3800,0) | — | MI_DevRed | ✅ |
|
||||
| 11 | LVL_BND_West_11 | Wall_400x300 | (0,4200,0) | — | MI_DevRed | ✅ |
|
||||
| 12 | LVL_BND_West_12 | Wall_400x300 | (0,4600,0) | — | MI_DevRed | ✅ |
|
||||
| 13 | LVL_BND_West_13 | Wall_400x300 | (0,5000,0) | — | MI_DevRed | ✅ |
|
||||
| 14 | LVL_BND_West_14 | Wall_400x300 | (0,5400,0) | — | MI_DevRed | ✅ |
|
||||
| 15 | LVL_BND_West_15 | Wall_400x300 | (0,5800,0) | — | MI_DevRed | ✅ |
|
||||
| 16 | LVL_BND_East_01 | BarrierWall_600 | (8000,200,0) | — | MI_DevBlue | ✅ |
|
||||
| 17 | LVL_BND_East_02 | BarrierWall_600 | (8000,600,0) | — | MI_DevBlue | ✅ |
|
||||
| 18 | LVL_BND_East_03 | BarrierWall_600 | (8000,1000,0) | — | MI_DevBlue | ✅ |
|
||||
| 19 | LVL_BND_East_04 | BarrierWall_600 | (8000,1400,0) | — | MI_DevBlue | ✅ |
|
||||
| 20 | LVL_BND_East_05 | BarrierWall_600 | (8000,1800,0) | — | MI_DevBlue | ✅ |
|
||||
| 21 | LVL_BND_East_06 | BarrierWall_600 | (8000,2200,0) | — | MI_DevBlue | ✅ |
|
||||
| 22 | LVL_BND_East_07 | BarrierWall_600 | (8000,2600,0) | — | MI_DevBlue | ✅ |
|
||||
| 23 | LVL_BND_East_08 | BarrierWall_600 | (8000,3000,0) | — | MI_DevBlue | ✅ |
|
||||
| 24 | LVL_BND_East_09 | BarrierWall_600 | (8000,3400,0) | — | MI_DevBlue | ✅ |
|
||||
| 25 | LVL_BND_East_10 | BarrierWall_600 | (8000,3800,0) | — | MI_DevBlue | ✅ |
|
||||
| 26 | LVL_BND_East_11 | BarrierWall_600 | (8000,4200,0) | — | MI_DevBlue | ✅ |
|
||||
| 27 | LVL_BND_East_12 | BarrierWall_600 | (8000,4600,0) | — | MI_DevBlue | ✅ |
|
||||
| 28 | LVL_BND_East_13 | BarrierWall_600 | (8000,5000,0) | — | MI_DevBlue | ✅ |
|
||||
| 29 | LVL_BND_East_14 | BarrierWall_600 | (8000,5400,0) | — | MI_DevBlue | ✅ |
|
||||
| 30 | LVL_BND_East_15 | BarrierWall_600 | (8000,5800,0) | — | MI_DevBlue | ✅ |
|
||||
| 31 | LVL_BND_North_01 | Wall_400x300 | (200,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 32 | LVL_BND_North_02 | Wall_400x300 | (600,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 33 | LVL_BND_North_03 | Wall_400x300 | (1000,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 34 | LVL_BND_North_04 | Wall_400x300 | (1400,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 35 | LVL_BND_North_05 | Wall_400x300 | (1800,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 36 | LVL_BND_North_06 | Wall_400x300 | (2200,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 37 | LVL_BND_North_07 | Wall_400x300 | (2600,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 38 | LVL_BND_North_08 | Wall_400x300 | (3000,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 39 | LVL_BND_North_09 | Wall_400x300 | (3400,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 40 | LVL_BND_North_10 | Wall_400x300 | (3800,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 41 | LVL_BND_North_11 | Wall_400x300 | (4200,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 42 | LVL_BND_North_12 | Wall_400x300 | (4600,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 43 | LVL_BND_North_13 | Wall_400x300 | (5000,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 44 | LVL_BND_North_14 | Wall_400x300 | (5400,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 45 | LVL_BND_North_15 | Wall_400x300 | (5800,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 46 | LVL_BND_North_16 | Wall_400x300 | (6200,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 47 | LVL_BND_North_17 | Wall_400x300 | (6600,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 48 | LVL_BND_North_18 | Wall_400x300 | (7000,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 49 | LVL_BND_North_19 | Wall_400x300 | (7400,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 50 | LVL_BND_North_20 | Wall_400x300 | (7800,6000,0) | — | MI_DevBlue | ✅ |
|
||||
| 51 | LVL_BND_South_01 | BarrierWall_600 | (1900,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 52 | LVL_BND_South_02 | BarrierWall_600 | (2500,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 53 | LVL_BND_South_03 | BarrierWall_600 | (3100,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 54 | LVL_BND_South_04 | BarrierWall_600 | (3700,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 55 | LVL_BND_South_05 | BarrierWall_600 | (4300,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 56 | LVL_BND_South_06 | BarrierWall_600 | (4900,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 57 | LVL_BND_South_07 | BarrierWall_600 | (5500,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 58 | LVL_BND_South_08 | BarrierWall_600 | (6100,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 59 | LVL_BND_South_09 | BarrierWall_600 | (6700,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 60 | LVL_BND_South_10 | BarrierWall_600 | (7300,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 61 | LVL_BND_South_11 | BarrierWall_600 | (7900,0,0) | — | MI_DevBlue | ✅ |
|
||||
| 62 | LVL_Global_DirectionalLight | | (0,0,500) | — | — | ✅ |
|
||||
| 63 | LVL_Global_SkyLight | | (0,0,0) | — | — | ✅ |
|
||||
| 64 | LVL_PlayerStart | | (500,720,0) | — | — | ✅ |
|
||||
|
||||
## 三、资产使用统计
|
||||
|
||||
| 资产名 | 使用次数 |
|
||||
|--------|---------|
|
||||
| Wall_400x300 | 95 |
|
||||
| BarrierWall_600 | 46 |
|
||||
| Cube | 21 |
|
||||
| Floor_1000x1000 | 15 |
|
||||
| CirclePlatform | 12 |
|
||||
| Cylinder | 9 |
|
||||
| Beam_300 | 5 |
|
||||
| BarrierWall_200 | 5 |
|
||||
| Wall_400x100 | 4 |
|
||||
| Floor_300x400 | 4 |
|
||||
| WoodenBox_Small | 4 |
|
||||
| Wall_200x300 | 4 |
|
||||
| TrashCans_01 | 3 |
|
||||
| Fence_Small_400 | 3 |
|
||||
| RoadLamp_01 | 3 |
|
||||
| WallDoor_200x300 | 2 |
|
||||
| Car01 | 2 |
|
||||
| Monitor_01 | 2 |
|
||||
| Floor_200x300 | 2 |
|
||||
| RotativeDoor_01 | 1 |
|
||||
| WallWindow_400x300 | 1 |
|
||||
| WallWindowSmall_200x300 | 1 |
|
||||
| Bucket_01 | 1 |
|
||||
| Barrel | 1 |
|
||||
| SimpleTable_01 | 1 |
|
||||
| BedSingle | 1 |
|
||||
| SimpleTable_02 | 1 |
|
||||
| Wall_100x300 | 1 |
|
||||
| DisplayPanels_400x200 | 1 |
|
||||
| WallDoorGarage_400x300 | 1 |
|
||||
| GarageDoorOpen_01 | 1 |
|
||||
| Container_Normal | 1 |
|
||||
| Tree_01 | 1 |
|
||||
| WoodenPallet_01 | 1 |
|
||||
| WoodenBox_Normal | 1 |
|
||||
| FakeWindowsFrame | 1 |
|
||||
| Building_01 | 1 |
|
||||
|
||||
## 四、缩放记录
|
||||
|
||||
| Actor 名称 | 资产 | 缩放 | 实际尺寸 (cm) |
|
||||
|-----------|------|------|-------------|
|
||||
| LVL_Z1_STR_Floor | Floor_1000x1000 | (1.00,0.80,1.00) | 1000×800×10 |
|
||||
| LVL_Z2_STR_Floor | Floor_1000x1000 | (0.60,0.60,1.00) | 600×600×10 |
|
||||
| LVL_Z3_STR_Floor_01 | Floor_1000x1000 | (1.00,0.50,1.00) | 1000×500×10 |
|
||||
| LVL_Z3_STR_Floor_02 | Floor_1000x1000 | (1.00,0.50,1.00) | 1000×500×10 |
|
||||
| LVL_Z3_STR_Floor_03 | Floor_1000x1000 | (1.00,0.50,1.00) | 1000×500×10 |
|
||||
| LVL_Z3_STR_Floor_04 | Floor_1000x1000 | (1.00,0.50,1.00) | 1000×500×10 |
|
||||
| LVL_Z3_STR_Floor_05 | Floor_1000x1000 | (1.00,0.50,1.00) | 1000×500×10 |
|
||||
| LVL_Z4_STR_Floor_01 | Floor_1000x1000 | (1.50,1.20,1.00) | 1500×1200×10 |
|
||||
| LVL_Z5_STR_Floor_01 | Floor_1000x1000 | (0.30,1.00,1.00) | 300×1000×10 |
|
||||
| LVL_Z5_STR_Floor_02 | Floor_1000x1000 | (0.30,1.00,1.00) | 300×1000×10 |
|
||||
| LVL_Z6_STR_GroundSouth | Floor_1000x1000 | (1.50,0.35,1.00) | 1500×350×10 |
|
||||
| LVL_Z6_STR_GroundEast | Floor_1000x1000 | (0.15,0.80,1.00) | 150×800×10 |
|
||||
| LVL_Z6_STR_GroundWest | Floor_1000x1000 | (0.15,0.80,1.00) | 150×800×10 |
|
||||
| LVL_Z6_STR_Water | Floor_1000x1000 | (1.20,0.80,0.50) | 1200×800×5 |
|
||||
| LVL_Z1_STR_CourtyardFloor | Floor_1000x1000 | (1.00,0.80,1.00) | 1000×800×10 |
|
||||
| LVL_Z1_PROP_OldBicycle | Car01 | (0.20,0.20,0.20) | (默认尺寸未知) |
|
||||
| LVL_Z1_PROP_ClothesPole_Pole1 | Cylinder | (0.05,0.05,3.60) | 5×5×360 |
|
||||
| LVL_Z1_PROP_ClothesPole_Pole2 | Cylinder | (0.05,0.05,3.60) | 5×5×360 |
|
||||
| LVL_Z1_PROP_DoorMat | Cube | (0.60,0.40,0.02) | 60×40×2 |
|
||||
| LVL_Z1_PROP_Fridge | Barrel | (0.30,0.30,1.60) | (默认尺寸未知) |
|
||||
| LVL_Z1_PROP_Calendar | Cube | (0.30,0.02,0.40) | 30×2×40 |
|
||||
| LVL_Z1_PROP_TVStand | SimpleTable_02 | (0.85,0.75,0.67) | (默认尺寸未知) |
|
||||
| LVL_Z1_INTR_01_Fridge | Cube | (0.30,0.30,0.30) | 30×30×30 |
|
||||
| LVL_Z1_INTR_02_Gate | Cube | (0.30,0.30,0.30) | 30×30×30 |
|
||||
| LVL_Z1_INTR_06_TV | Cube | (0.30,0.30,0.30) | 30×30×30 |
|
||||
| LVL_Z1_COLL_10_CatEyeMarble | CirclePlatform | (0.03,0.03,0.03) | (默认尺寸未知) |
|
||||
| LVL_Z3_PROP_PhoneBooth | Cube | (0.80,0.80,2.20) | 80×80×220 |
|
||||
| LVL_Z3_PROP_Bicycle | Car01 | (0.20,0.20,0.20) | (默认尺寸未知) |
|
||||
| LVL_Z3_PROP_ClothesPolePole | Cylinder | (0.05,0.05,3.60) | 5×5×360 |
|
||||
| LVL_Z3_PROP_SewerCover | CirclePlatform | (0.12,0.12,0.02) | (默认尺寸未知) |
|
||||
| LVL_Z3_PROP_Graffiti | Cube | (1.20,0.05,0.80) | 120×5×80 |
|
||||
| LVL_Z3_PROP_PoleWest | Cylinder | (0.04,0.04,12.00) | 4×4×1200 |
|
||||
| LVL_Z3_PROP_PoleEast | Cylinder | (0.04,0.04,12.00) | 4×4×1200 |
|
||||
| LVL_Z3_INTR_04_PhoneBooth | Cube | (0.30,0.30,0.30) | 30×30×30 |
|
||||
| LVL_Z3_INTR_09_Graffiti | Cube | (0.30,0.30,0.30) | 30×30×30 |
|
||||
| LVL_Z3_COLL_08_IceFlowerMarble | CirclePlatform | (0.03,0.03,0.03) | (默认尺寸未知) |
|
||||
| LVL_Z2_PROP_Counter | Cube | (2.00,0.60,0.90) | 200×60×90 |
|
||||
| LVL_Z2_PROP_ShelfEast_01 | Fence_Small_400 | (1.00,1.00,1.80) | 400×5×180 |
|
||||
| LVL_Z2_PROP_ShelfEast_02 | Fence_Small_400 | (1.00,1.00,1.80) | 400×5×180 |
|
||||
| LVL_Z2_PROP_ShelfSouth | Fence_Small_400 | (1.00,1.00,1.80) | 400×5×180 |
|
||||
| LVL_Z2_PROP_Freezer | Container_Normal | (0.25,0.15,0.40) | (默认尺寸未知) |
|
||||
| LVL_Z2_PROP_SmallTV | Monitor_01 | (0.60,0.60,0.60) | (默认尺寸未知) |
|
||||
| LVL_Z2_PROP_Signboard | Cube | (3.00,0.05,0.40) | 300×5×40 |
|
||||
| LVL_Z2_PROP_CardRack | Cube | (0.40,0.20,0.30) | 40×20×30 |
|
||||
| LVL_Z2_INTR_05_ShopOwner | Cube | (0.30,0.30,0.30) | 30×30×30 |
|
||||
| LVL_Z2_INTR_07_SnackShelf | Cube | (0.30,0.30,0.30) | 30×30×30 |
|
||||
| LVL_Z2_COLL_09_BouncyBall | CirclePlatform | (0.03,0.03,0.03) | (默认尺寸未知) |
|
||||
| LVL_Z4_PROP_BigTree | Tree_01 | (1.20,1.20,1.20) | (默认尺寸未知) |
|
||||
| LVL_Z4_PROP_Puddle | Cube | (1.20,0.80,0.01) | 120×80×1 |
|
||||
| LVL_Z4_PROP_MarbleCircle | CirclePlatform | (0.16,0.16,0.01) | (默认尺寸未知) |
|
||||
| LVL_Z4_INTR_03_BlueMarble | Cube | (0.30,0.30,0.30) | 30×30×30 |
|
||||
| LVL_Z4_INTR_08_SmallStone | Cube | (0.30,0.30,0.30) | 30×30×30 |
|
||||
| LVL_Z4_COLL_01_BlueMarble | CirclePlatform | (0.03,0.03,0.03) | (默认尺寸未知) |
|
||||
| LVL_Z4_COLL_03_SmallStone | CirclePlatform | (0.03,0.03,0.03) | (默认尺寸未知) |
|
||||
| LVL_Z4_COLL_04_LuZhishenCard | CirclePlatform | (0.03,0.03,0.03) | (默认尺寸未知) |
|
||||
| LVL_Z5_PROP_SpiderWeb | Cube | (0.30,0.30,0.01) | 30×30×1 |
|
||||
| LVL_Z5_PROP_FlowerPot | Cylinder | (0.20,0.20,0.25) | 20×20×25 |
|
||||
| LVL_Z5_PROP_Moss | Cube | (0.60,0.40,0.01) | 60×40×1 |
|
||||
| LVL_Z5_INTR_10_WasteBox | Cube | (0.30,0.30,0.30) | 30×30×30 |
|
||||
| LVL_Z5_COLL_02_RedMarble | CirclePlatform | (0.03,0.03,0.03) | (默认尺寸未知) |
|
||||
| LVL_Z6_PROP_Reeds_S1~5 | Cylinder | (0.08,0.08,2.00) | 8×8×200 |
|
||||
| LVL_Z6_PROP_Reeds_S2_1~5 | Cylinder | (0.08,0.08,2.00) | 8×8×200 |
|
||||
| LVL_Z6_PROP_Reeds_E_1~3 | Cylinder | (0.08,0.08,1.60) | 8×8×160 |
|
||||
| LVL_Z6_PROP_LilyPad_1~4 | CirclePlatform | (0.06,0.06,0.01) | (默认尺寸未知) |
|
||||
| LVL_Z6_PROP_Frog | Cube | (0.05,0.05,0.04) | 5×5×4 |
|
||||
| LVL_Z6_COLL_05_CandyWrapper | CirclePlatform | (0.03,0.03,0.03) | (默认尺寸未知) |
|
||||
| LVL_Z6_COLL_06_Eraser | CirclePlatform | (0.03,0.03,0.03) | (默认尺寸未知) |
|
||||
| LVL_Z7_STR_CollisionSide | Wall_400x300 | (0.00,90.00,0.00) | 0×1800×0 |
|
||||
| LVL_Z7_STR_CollisionSide2 | Wall_400x300 | (0.00,90.00,0.00) | 0×1800×0 |
|
||||
@@ -0,0 +1,772 @@
|
||||
"""搭建指南 Markdown 解析器。
|
||||
|
||||
解析 docs/technical/关卡-v01-搭建指南.md,输出统一的 Actor 列表。
|
||||
|
||||
搭建指南的表格格式(4 种变体 + 特殊表格):
|
||||
- 结构体表: # / Actor名称 / 资产 / 位置 / [旋转] / [缩放] / 材质
|
||||
- 布景表: # / Actor名称 / 资产 / 位置 / [缩放] / 材质 / 说明
|
||||
- 交互物表: # / Actor名称 / 位置 / 触发半径 / [交互类型] / 引用
|
||||
- 收集品表: # / Actor名称 / 位置 / 稀有度 / 引用
|
||||
- 边界表: # / Actor名称 / 资产 / 位置 / [旋转] / [缩放] / 材质
|
||||
- 光照表: # / Actor名称 / 类型 / 位置 / 参数
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from typing import Optional
|
||||
|
||||
from parsers.markdown_table import (
|
||||
parse_table,
|
||||
parse_location,
|
||||
parse_rotation,
|
||||
parse_scale,
|
||||
clean_cell,
|
||||
)
|
||||
|
||||
logger = logging.getLogger("level_builder.parser")
|
||||
|
||||
# ============================================================
|
||||
# 数据结构
|
||||
# ============================================================
|
||||
|
||||
@dataclass
|
||||
class ActorData:
|
||||
"""单个 Actor 的完整规格。"""
|
||||
actor_name: str
|
||||
asset_key: str = ""
|
||||
asset_type: str = "static_mesh" # static_mesh | player_start | directional_light | sky_light | point_light
|
||||
category: str = "" # STR | PROP | INTR | COLL | BND | LGT | PLAY
|
||||
zone_id: str = "" # Z1-Z7 | GLOBAL
|
||||
location: tuple = (0.0, 0.0, 0.0)
|
||||
rotation: tuple = (0.0, 0.0, 0.0)
|
||||
scale: tuple = (1.0, 1.0, 1.0)
|
||||
material: str = ""
|
||||
folder: str = ""
|
||||
note: str = ""
|
||||
trigger_radius: float = 0.0
|
||||
interact_type: str = ""
|
||||
rarity: str = ""
|
||||
source_ref: str = ""
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 标题/上下文检测
|
||||
# ============================================================
|
||||
|
||||
# 区域标题匹配:### C1. Z1 — 家(Home)
|
||||
ZONE_HEADING = re.compile(
|
||||
r'###\s+C\d+\.\s+(Z\d+)\s*[—\-–]\s*(.+?)(?:((.+)))?$'
|
||||
)
|
||||
|
||||
# 分类子标题匹配:#### C1.1 结构体(Structure)
|
||||
CATEGORY_HEADING = re.compile(
|
||||
r'####\s+C\d+\.\d+\s+(结构体|布景|交互物|收集品|布景物)'
|
||||
)
|
||||
|
||||
# Phase 标题
|
||||
PHASE_HEADING = re.compile(r'^##\s+(二|三|四|五|六|七|八)')
|
||||
|
||||
# 边界区域标题
|
||||
BOUNDARY_HEADING = re.compile(r'####\s+(西边界|东边界|北边界|南边界)')
|
||||
|
||||
# 全局物件标题
|
||||
GLOBAL_HEADING = re.compile(r'###\s+G-(\d+|E\d+)\s+(.+)')
|
||||
|
||||
# 光照标题
|
||||
LIGHT_HEADING = re.compile(r'###\s+D\d+\s+(.+)')
|
||||
|
||||
# 地面子标题
|
||||
FLOOR_HEADING = re.compile(r'####\s+Z\d+\s+(.+?)\s+地面')
|
||||
|
||||
CATEGORY_MAP = {
|
||||
"结构体": "STR",
|
||||
"布景": "PROP",
|
||||
"布景物": "PROP",
|
||||
"交互物": "INTR",
|
||||
"交互物标记": "INTR",
|
||||
"收集品": "COLL",
|
||||
"收集品标记": "COLL",
|
||||
}
|
||||
|
||||
ZONE_ID_MAP = {
|
||||
"Z1": "Z1", "Z2": "Z2", "Z3": "Z3",
|
||||
"Z4": "Z4", "Z5": "Z5", "Z6": "Z6", "Z7": "Z7",
|
||||
}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 核心解析逻辑
|
||||
# ============================================================
|
||||
|
||||
class BuildGuideParser:
|
||||
"""搭建指南解析器。"""
|
||||
|
||||
def __init__(self):
|
||||
self.actors: list[ActorData] = []
|
||||
self.current_zone = ""
|
||||
self.current_category = ""
|
||||
self.current_phase = ""
|
||||
self.metadata = {}
|
||||
|
||||
def parse_file(self, filepath: str) -> dict:
|
||||
"""解析搭建指南文件,返回完整的关卡数据。"""
|
||||
path = Path(filepath)
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"搭建指南文件不存在: {filepath}")
|
||||
|
||||
text = path.read_text(encoding="utf-8")
|
||||
return self.parse_text(text)
|
||||
|
||||
def parse_text(self, text: str) -> dict:
|
||||
"""解析搭建指南文本。"""
|
||||
lines = text.split("\n")
|
||||
|
||||
# 解析全局参数(第〇章)
|
||||
self._parse_global_info(lines)
|
||||
|
||||
# 解析资产映射表(第一章)— 可选,用于验证
|
||||
# 逐 Phase 解析
|
||||
self._parse_phase_ground(lines)
|
||||
self._parse_phase_boundaries(lines)
|
||||
self._parse_phase_zones(lines)
|
||||
self._parse_phase_global_objects(lines)
|
||||
self._parse_phase_lighting(lines)
|
||||
|
||||
# 添加 Player Start
|
||||
self._add_player_start()
|
||||
|
||||
# 构建输出
|
||||
result = {
|
||||
"metadata": self.metadata,
|
||||
"actors": [asdict(a) for a in self.actors],
|
||||
"summary": self._build_summary(),
|
||||
}
|
||||
return result
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 全局参数
|
||||
# ----------------------------------------------------------
|
||||
def _parse_global_info(self, lines: list[str]):
|
||||
"""解析场景全局参数。"""
|
||||
self.metadata = {
|
||||
"source": "关卡-v01-搭建指南.md",
|
||||
"scene_size": [8000, 6000],
|
||||
"ground_z": 0,
|
||||
"character_height": 120,
|
||||
"camera_height": 100,
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Phase B — 地面
|
||||
# ----------------------------------------------------------
|
||||
def _parse_phase_ground(self, lines: list[str]):
|
||||
"""解析 Phase B 地面铺设表格。"""
|
||||
text = "\n".join(lines)
|
||||
# 找到 Phase B 区域
|
||||
b_start = self._find_section_start(lines, "Phase B")
|
||||
b_end = self._find_section_start(lines, "Phase C")
|
||||
if b_start < 0:
|
||||
return
|
||||
if b_end < 0:
|
||||
b_end = len(lines)
|
||||
|
||||
section_text = "\n".join(lines[b_start:b_end])
|
||||
|
||||
# 地面子区域标题:#### Z1 家 地面(MI_DevGray)
|
||||
floor_pattern = re.compile(
|
||||
r'####\s+Z(\d+)\s+\S+\s+地面(?:((.+?)))?'
|
||||
)
|
||||
|
||||
current_zone = ""
|
||||
current_material = ""
|
||||
section_lines = lines[b_start:b_end]
|
||||
|
||||
i = 0
|
||||
while i < len(section_lines):
|
||||
line = section_lines[i].strip()
|
||||
|
||||
# 检测地面子区域
|
||||
fm = floor_pattern.match(line)
|
||||
if fm:
|
||||
current_zone = f"Z{fm.group(1)}"
|
||||
current_material = fm.group(2) or ""
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# 检测水面区域
|
||||
if '水面' in line and current_zone:
|
||||
current_material = "MI_DevSkyBlue"
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# 检测表格
|
||||
if '|' in line and '#' in line:
|
||||
table_lines = []
|
||||
while i < len(section_lines) and '|' in section_lines[i]:
|
||||
table_lines.append(section_lines[i])
|
||||
i += 1
|
||||
rows = parse_table(table_lines)
|
||||
if rows:
|
||||
for row in rows:
|
||||
actor = self._parse_floor_row(row, current_zone, current_material)
|
||||
if actor:
|
||||
self.actors.append(actor)
|
||||
continue
|
||||
|
||||
i += 1
|
||||
|
||||
def _parse_floor_row(self, row: dict, zone: str, default_material: str) -> Optional[ActorData]:
|
||||
"""解析地面表格行。"""
|
||||
name = clean_cell(row.get("Actor 名称", row.get("Actor名称", "")))
|
||||
if not name:
|
||||
return None
|
||||
|
||||
asset_key = clean_cell(row.get("资产", ""))
|
||||
location = parse_location(row.get("位置 (X,Y,Z)", row.get("位置", "")))
|
||||
scale = parse_scale(row.get("缩放", "—"))
|
||||
material = clean_cell(row.get("材质", ""))
|
||||
if not material:
|
||||
material = default_material
|
||||
|
||||
return ActorData(
|
||||
actor_name=name,
|
||||
asset_key=asset_key,
|
||||
asset_type="static_mesh",
|
||||
category="STR",
|
||||
zone_id=zone,
|
||||
location=location,
|
||||
scale=scale,
|
||||
material=material,
|
||||
folder=self._get_folder(zone, "STR"),
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Phase B — 边界
|
||||
# ----------------------------------------------------------
|
||||
def _parse_phase_boundaries(self, lines: list[str]):
|
||||
"""解析 Phase B 边界围挡。"""
|
||||
b_start = self._find_section_start(lines, "放置边界围挡")
|
||||
b_end = self._find_section_start(lines, "Phase C")
|
||||
if b_start < 0:
|
||||
return
|
||||
if b_end < 0:
|
||||
b_end = len(lines)
|
||||
|
||||
section_lines = lines[b_start:b_end]
|
||||
i = 0
|
||||
while i < len(section_lines):
|
||||
line = section_lines[i].strip()
|
||||
|
||||
# 边界子区域标题
|
||||
bm = BOUNDARY_HEADING.match(line)
|
||||
|
||||
# 检测表格
|
||||
if '|' in line:
|
||||
table_lines = []
|
||||
while i < len(section_lines) and '|' in section_lines[i]:
|
||||
table_lines.append(section_lines[i])
|
||||
i += 1
|
||||
rows = parse_table(table_lines)
|
||||
if rows:
|
||||
for row in rows:
|
||||
actors = self._parse_boundary_row(row)
|
||||
self.actors.extend(actors)
|
||||
continue
|
||||
|
||||
i += 1
|
||||
|
||||
def _parse_boundary_row(self, row: dict) -> list[ActorData]:
|
||||
"""解析边界表格行,处理范围格式如 '1-15 | LVL_BND_East_01~15'。"""
|
||||
name_col = row.get("Actor 名称", row.get("Actor名称", ""))
|
||||
name = clean_cell(name_col)
|
||||
|
||||
# 处理范围格式:名称中含 ~ 或编号含 -
|
||||
if '~' in name:
|
||||
return self._expand_range_row(row, name)
|
||||
|
||||
asset_key = clean_cell(row.get("资产", ""))
|
||||
location = parse_location(row.get("位置 (X,Y,Z)", row.get("位置", "")))
|
||||
rotation = parse_rotation(row.get("旋转 (Pitch,Yaw,Roll)", row.get("旋转", "(0,0,0)")))
|
||||
scale = parse_scale(row.get("缩放", "—"))
|
||||
material = clean_cell(row.get("材质", ""))
|
||||
|
||||
actor = ActorData(
|
||||
actor_name=name,
|
||||
asset_key=asset_key,
|
||||
category="BND",
|
||||
zone_id="GLOBAL",
|
||||
location=location,
|
||||
rotation=rotation,
|
||||
scale=scale,
|
||||
material=material,
|
||||
folder=self._get_folder("GLOBAL", "BND"),
|
||||
)
|
||||
return [actor]
|
||||
|
||||
def _expand_range_row(self, row: dict, name: str) -> list[ActorData]:
|
||||
"""展开范围格式行。如 'LVL_BND_East_01~15' → 15 个 Actor。"""
|
||||
asset_key = clean_cell(row.get("资产", ""))
|
||||
rotation = parse_rotation(row.get("旋转 (Pitch,Yaw,Roll)", row.get("旋转", "(0,0,0)")))
|
||||
scale = parse_scale(row.get("缩放", "—"))
|
||||
material = clean_cell(row.get("材质", ""))
|
||||
|
||||
# 解析名称范围:LVL_BND_East_01~15
|
||||
m = re.match(r'(.+?)_(\d+)~(\d+)', name)
|
||||
if not m:
|
||||
return []
|
||||
|
||||
prefix = m.group(1)
|
||||
start = int(m.group(2))
|
||||
end = int(m.group(3))
|
||||
|
||||
# 解析位置表达式 — 格式: (X, Y_expr, Z) n=start~end
|
||||
# 例: (8000, 200+400n, 0) n=0~14
|
||||
loc_col = row.get("位置 (X,Y,Z)", row.get("位置", ""))
|
||||
loc_col = clean_cell(loc_col)
|
||||
|
||||
# 提取 n 的范围
|
||||
n_match = re.search(r'n\s*=\s*(\d+)~(\d+)', loc_col)
|
||||
n_start = int(n_match.group(1)) if n_match else 0
|
||||
n_end = int(n_match.group(2)) if n_match else (end - start)
|
||||
# 去掉 n=xx~yy 后缀以便解析坐标
|
||||
loc_clean = re.sub(r'\s*n\s*=\s*\d+~\d+\s*', '', loc_col).strip()
|
||||
|
||||
# 提取三个坐标表达式
|
||||
coord_match = re.match(r'\((.+?)\s*,\s*(.+?)\s*,\s*(.+?)\)', loc_clean)
|
||||
if not coord_match:
|
||||
return []
|
||||
|
||||
x_expr = coord_match.group(1).strip()
|
||||
y_expr = coord_match.group(2).strip()
|
||||
z_expr = coord_match.group(3).strip()
|
||||
|
||||
actors = []
|
||||
for idx, n in enumerate(range(n_start, n_end + 1)):
|
||||
x = self._eval_coord_expr(x_expr, n)
|
||||
y = self._eval_coord_expr(y_expr, n)
|
||||
z = self._eval_coord_expr(z_expr, n)
|
||||
|
||||
actor_num = start + idx
|
||||
actor_name = f"{prefix}_{actor_num:02d}"
|
||||
actor = ActorData(
|
||||
actor_name=actor_name,
|
||||
asset_key=asset_key,
|
||||
category="BND",
|
||||
zone_id="GLOBAL",
|
||||
location=(x, y, z),
|
||||
rotation=rotation,
|
||||
scale=scale,
|
||||
material=material,
|
||||
folder=self._get_folder("GLOBAL", "BND"),
|
||||
)
|
||||
actors.append(actor)
|
||||
|
||||
return actors
|
||||
|
||||
def _eval_coord_expr(self, expr: str, n: int) -> float:
|
||||
"""计算坐标表达式。如 '200+400n' → 200+400*n。"""
|
||||
expr = expr.strip()
|
||||
# 简单情况:纯数字
|
||||
try:
|
||||
return float(expr)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# 含 n 的表达式:200+400n
|
||||
expr = expr.replace('n', f'*{n}')
|
||||
# 处理连续运算符:*+→+, *-→-
|
||||
expr = expr.replace('*+', '+').replace('*-', '-')
|
||||
|
||||
try:
|
||||
# 安全计算简单算术
|
||||
result = eval(expr) # 受限输入,仅数字和基本运算
|
||||
return float(result)
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Phase C — 逐区域搭建
|
||||
# ----------------------------------------------------------
|
||||
def _parse_phase_zones(self, lines: list[str]):
|
||||
"""解析 Phase C 逐区域搭建。"""
|
||||
c_start = self._find_section_start(lines, "逐区域搭建")
|
||||
c_end = self._find_section_start(lines, "Phase C — 全局物件")
|
||||
if c_start < 0:
|
||||
c_start = self._find_section_start(lines, "全局物件")
|
||||
if c_start < 0:
|
||||
return
|
||||
if c_end < 0:
|
||||
c_end = len(lines)
|
||||
|
||||
section_lines = lines[c_start:c_end]
|
||||
current_zone = ""
|
||||
current_category = ""
|
||||
|
||||
i = 0
|
||||
while i < len(section_lines):
|
||||
line = section_lines[i].strip()
|
||||
|
||||
# 检测区域标题
|
||||
zm = ZONE_HEADING.match(line)
|
||||
if zm:
|
||||
current_zone = zm.group(1)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# 检测分类标题
|
||||
cm = CATEGORY_HEADING.match(line)
|
||||
if cm:
|
||||
cat_name = cm.group(1)
|
||||
current_category = CATEGORY_MAP.get(cat_name, "")
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# 检测结构体子标题(如 "C1.1 结构体(Structure)")
|
||||
sub_match = re.match(r'####\s+C\d+\.\d+\s+(结构体|布景|布景物|交互物|交互物标记|收集品|收集品标记)', line)
|
||||
if sub_match:
|
||||
cat_name = sub_match.group(1)
|
||||
current_category = CATEGORY_MAP.get(cat_name, "")
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# 处理表格
|
||||
if '|' in line:
|
||||
table_lines = []
|
||||
while i < len(section_lines) and '|' in section_lines[i]:
|
||||
table_lines.append(section_lines[i])
|
||||
i += 1
|
||||
rows = parse_table(table_lines)
|
||||
if rows:
|
||||
for row in rows:
|
||||
actor = self._parse_zone_row(row, current_zone, current_category)
|
||||
if actor:
|
||||
self.actors.append(actor)
|
||||
continue
|
||||
|
||||
i += 1
|
||||
|
||||
def _parse_zone_row(self, row: dict, zone: str, category: str) -> Optional[ActorData]:
|
||||
"""解析区域内的 Actor 行。根据列内容判断表格类型。"""
|
||||
name = clean_cell(row.get("Actor 名称", row.get("Actor名称", "")))
|
||||
if not name:
|
||||
return None
|
||||
|
||||
# 判断表格类型:有"资产"列 → 结构体/布景;有"触发半径" → 交互物;有"稀有度" → 收集品
|
||||
has_asset = "资产" in row and clean_cell(row.get("资产", ""))
|
||||
has_trigger = "触发半径" in row or "触发半径 (cm)" in row
|
||||
has_rarity = "稀有度" in row
|
||||
|
||||
if has_trigger and not has_asset:
|
||||
# 交互物表
|
||||
return self._parse_interactable_row(row, zone)
|
||||
elif has_rarity and not has_asset:
|
||||
# 收集品表
|
||||
return self._parse_collectible_row(row, zone)
|
||||
else:
|
||||
# 结构体/布景表
|
||||
return self._parse_structure_or_prop_row(row, zone, category)
|
||||
|
||||
def _parse_structure_or_prop_row(self, row: dict, zone: str, category: str) -> Optional[ActorData]:
|
||||
"""解析结构体或布景表格行。"""
|
||||
name = clean_cell(row.get("Actor 名称", row.get("Actor名称", "")))
|
||||
asset_key = clean_cell(row.get("资产", ""))
|
||||
location = parse_location(
|
||||
row.get("位置 (X,Y,Z)", row.get("位置", ""))
|
||||
)
|
||||
|
||||
# 旋转:结构体表可能有,布景表可能没有
|
||||
rot_col = row.get("旋转 (Pitch,Yaw,Roll)", row.get("旋转", ""))
|
||||
rotation = parse_rotation(rot_col) if rot_col else (0.0, 0.0, 0.0)
|
||||
|
||||
# 缩放
|
||||
scale = parse_scale(row.get("缩放", "—"))
|
||||
|
||||
# 材质
|
||||
material = clean_cell(row.get("材质", ""))
|
||||
if material in ('—', '–', '-'):
|
||||
material = ""
|
||||
|
||||
# 说明
|
||||
note = clean_cell(row.get("说明", ""))
|
||||
|
||||
if not category:
|
||||
category = "STR"
|
||||
|
||||
return ActorData(
|
||||
actor_name=name,
|
||||
asset_key=asset_key,
|
||||
category=category,
|
||||
zone_id=zone,
|
||||
location=location,
|
||||
rotation=rotation,
|
||||
scale=scale,
|
||||
material=material,
|
||||
folder=self._get_folder(zone, category),
|
||||
note=note,
|
||||
)
|
||||
|
||||
def _parse_interactable_row(self, row: dict, zone: str) -> ActorData:
|
||||
"""解析交互物行。用占位 Cube 表示。"""
|
||||
name = clean_cell(row.get("Actor 名称", row.get("Actor名称", "")))
|
||||
location = parse_location(
|
||||
row.get("位置 (X,Y,Z)", row.get("位置", ""))
|
||||
)
|
||||
trigger = row.get("触发半径", row.get("触发半径 (cm)", "150"))
|
||||
try:
|
||||
trigger_radius = float(trigger)
|
||||
except (ValueError, TypeError):
|
||||
trigger_radius = 150.0
|
||||
|
||||
ref = clean_cell(row.get("引用", ""))
|
||||
interact_type = clean_cell(row.get("交互类型", ""))
|
||||
|
||||
return ActorData(
|
||||
actor_name=name,
|
||||
asset_key="Cube",
|
||||
category="INTR",
|
||||
zone_id=zone,
|
||||
location=location,
|
||||
scale=(0.3, 0.3, 0.3),
|
||||
material="MI_DevYellow",
|
||||
folder=self._get_folder(zone, "INTR"),
|
||||
trigger_radius=trigger_radius,
|
||||
interact_type=interact_type,
|
||||
source_ref=ref,
|
||||
note="交互物占位标记",
|
||||
)
|
||||
|
||||
def _parse_collectible_row(self, row: dict, zone: str) -> ActorData:
|
||||
"""解析收集品行。用占位 Sphere/Cube 表示。"""
|
||||
name = clean_cell(row.get("Actor 名称", row.get("Actor名称", "")))
|
||||
location = parse_location(
|
||||
row.get("位置 (X,Y,Z)", row.get("位置", ""))
|
||||
)
|
||||
rarity = clean_cell(row.get("稀有度", "普通"))
|
||||
ref = clean_cell(row.get("引用", ""))
|
||||
|
||||
# 稀有用金色,普通用白色
|
||||
material = "MI_DevYellow" if rarity == "稀有" else "MI_DevWhite"
|
||||
|
||||
return ActorData(
|
||||
actor_name=name,
|
||||
asset_key="CirclePlatform",
|
||||
category="COLL",
|
||||
zone_id=zone,
|
||||
location=location,
|
||||
scale=(0.03, 0.03, 0.03),
|
||||
material=material,
|
||||
folder=self._get_folder(zone, "COLL"),
|
||||
rarity=rarity,
|
||||
source_ref=ref,
|
||||
note="收集品占位标记",
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Phase C — 全局物件
|
||||
# ----------------------------------------------------------
|
||||
def _parse_phase_global_objects(self, lines: list[str]):
|
||||
"""解析全局物件(路灯、垃圾桶等)。"""
|
||||
g_start = self._find_section_start(lines, "全局物件")
|
||||
g_end = self._find_section_start(lines, "Phase D")
|
||||
if g_start < 0:
|
||||
return
|
||||
if g_end < 0:
|
||||
g_end = len(lines)
|
||||
|
||||
section_lines = lines[g_start:g_end]
|
||||
i = 0
|
||||
while i < len(section_lines):
|
||||
line = section_lines[i].strip()
|
||||
if '|' in line:
|
||||
table_lines = []
|
||||
while i < len(section_lines) and '|' in section_lines[i]:
|
||||
table_lines.append(section_lines[i])
|
||||
i += 1
|
||||
rows = parse_table(table_lines)
|
||||
if rows:
|
||||
for row in rows:
|
||||
actor = self._parse_global_row(row)
|
||||
if actor:
|
||||
self.actors.append(actor)
|
||||
continue
|
||||
i += 1
|
||||
|
||||
def _parse_global_row(self, row: dict) -> Optional[ActorData]:
|
||||
"""解析全局物件行。"""
|
||||
name = clean_cell(row.get("Actor 名称", row.get("Actor名称", "")))
|
||||
if not name:
|
||||
return None
|
||||
|
||||
asset_key = clean_cell(row.get("资产", ""))
|
||||
location = parse_location(row.get("位置 (X,Y,Z)", row.get("位置", "")))
|
||||
material = clean_cell(row.get("材质", ""))
|
||||
if material in ('—', '–', '-'):
|
||||
material = ""
|
||||
|
||||
# 从 Actor 名称推断类别
|
||||
category = "PROP"
|
||||
if "_LGT_" in name:
|
||||
category = "LGT"
|
||||
elif "_PROP_" in name:
|
||||
category = "PROP"
|
||||
|
||||
zone_id = self._infer_zone_from_name(name)
|
||||
|
||||
return ActorData(
|
||||
actor_name=name,
|
||||
asset_key=asset_key,
|
||||
category=category,
|
||||
zone_id=zone_id,
|
||||
location=location,
|
||||
material=material,
|
||||
folder=self._get_folder(zone_id, category),
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Phase D — 光照
|
||||
# ----------------------------------------------------------
|
||||
def _parse_phase_lighting(self, lines: list[str]):
|
||||
"""解析光照配置。"""
|
||||
d_start = self._find_section_start(lines, "Phase D")
|
||||
d_end = self._find_section_start(lines, "Phase E")
|
||||
if d_start < 0:
|
||||
return
|
||||
if d_end < 0:
|
||||
d_end = len(lines)
|
||||
|
||||
section_lines = lines[d_start:d_end]
|
||||
|
||||
# 全局光源:DirectionalLight + SkyLight
|
||||
self._parse_global_lights(section_lines)
|
||||
|
||||
# 路灯点光源
|
||||
self._parse_street_lights(section_lines)
|
||||
|
||||
def _parse_global_lights(self, lines: list[str]):
|
||||
"""解析全局光源表格。"""
|
||||
text = "\n".join(lines)
|
||||
# Directional Light 和 Sky Light
|
||||
if "Directional Light" in text or "DirectionalLight" in text:
|
||||
self.actors.append(ActorData(
|
||||
actor_name="LVL_Global_DirectionalLight",
|
||||
asset_key="",
|
||||
asset_type="directional_light",
|
||||
category="LGT",
|
||||
zone_id="GLOBAL",
|
||||
location=(0, 0, 500),
|
||||
rotation=(-45, 315, 0),
|
||||
folder=self._get_folder("GLOBAL", "LGT"),
|
||||
note="色温~5500K暖白;从西南照射;强度中等",
|
||||
))
|
||||
if "Sky Light" in text or "SkyLight" in text:
|
||||
self.actors.append(ActorData(
|
||||
actor_name="LVL_Global_SkyLight",
|
||||
asset_key="",
|
||||
asset_type="sky_light",
|
||||
category="LGT",
|
||||
zone_id="GLOBAL",
|
||||
location=(0, 0, 0),
|
||||
folder=self._get_folder("GLOBAL", "LGT"),
|
||||
note="强度低-中;环境补光",
|
||||
))
|
||||
|
||||
def _parse_street_lights(self, lines: list[str]):
|
||||
"""解析路灯点光源表格。"""
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i].strip()
|
||||
# 检测表格起始(含 | 的行)
|
||||
if '|' in line:
|
||||
table_lines = []
|
||||
while i < len(lines) and '|' in lines[i]:
|
||||
table_lines.append(lines[i])
|
||||
i += 1
|
||||
|
||||
# 检查表格内容是否包含 Point Light
|
||||
table_text = "\n".join(table_lines)
|
||||
if "Point" in table_text or "point_light" in table_text.lower():
|
||||
rows = parse_table(table_lines)
|
||||
for row in rows:
|
||||
name = clean_cell(row.get("Actor 名称", row.get("Actor名称", "")))
|
||||
loc_col = row.get("位置 (X,Y,Z)", row.get("位置", ""))
|
||||
location = parse_location(loc_col)
|
||||
if name:
|
||||
self.actors.append(ActorData(
|
||||
actor_name=name,
|
||||
asset_key="",
|
||||
asset_type="point_light",
|
||||
category="LGT",
|
||||
zone_id="Z3",
|
||||
location=location,
|
||||
folder=self._get_folder("GLOBAL", "LGT"),
|
||||
note="暖黄色,低强度",
|
||||
))
|
||||
continue
|
||||
i += 1
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Player Start
|
||||
# ----------------------------------------------------------
|
||||
def _add_player_start(self):
|
||||
"""添加 Player Start Actor。"""
|
||||
self.actors.append(ActorData(
|
||||
actor_name="LVL_PlayerStart",
|
||||
asset_key="",
|
||||
asset_type="player_start",
|
||||
category="PLAY",
|
||||
zone_id="GLOBAL",
|
||||
location=(500, 720, 0),
|
||||
rotation=(0, 0, 0),
|
||||
folder=self._get_folder("GLOBAL", "PLAY"),
|
||||
note="Z1 院内院门内侧约60cm,面向+Y(北)",
|
||||
))
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 辅助方法
|
||||
# ----------------------------------------------------------
|
||||
def _find_section_start(self, lines: list[str], keyword: str) -> int:
|
||||
"""找到包含关键词的标题行。"""
|
||||
for i, line in enumerate(lines):
|
||||
if keyword in line:
|
||||
return i
|
||||
return -1
|
||||
|
||||
def _infer_zone_from_name(self, name: str) -> str:
|
||||
"""从 Actor 名称推断区域 ID。"""
|
||||
m = re.match(r'LVL_(Z\d+)_', name)
|
||||
if m:
|
||||
return m.group(1)
|
||||
if 'BND' in name:
|
||||
return "GLOBAL"
|
||||
if 'Global' in name:
|
||||
return "GLOBAL"
|
||||
return "GLOBAL"
|
||||
|
||||
def _get_folder(self, zone_id: str, category: str) -> str:
|
||||
"""获取 Outliner 文件夹路径。"""
|
||||
from config.zone_config import get_folder_path
|
||||
return get_folder_path(zone_id, category)
|
||||
|
||||
def _build_summary(self) -> dict:
|
||||
"""构建解析摘要。"""
|
||||
total = len(self.actors)
|
||||
by_category = {}
|
||||
by_zone = {}
|
||||
asset_keys = set()
|
||||
|
||||
for a in self.actors:
|
||||
by_category[a.category] = by_category.get(a.category, 0) + 1
|
||||
by_zone[a.zone_id] = by_zone.get(a.zone_id, 0) + 1
|
||||
if a.asset_key:
|
||||
asset_keys.add(a.asset_key)
|
||||
|
||||
return {
|
||||
"total_actors": total,
|
||||
"by_category": by_category,
|
||||
"by_zone": by_zone,
|
||||
"unique_assets": len(asset_keys),
|
||||
"asset_list": sorted(asset_keys),
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
"""通用 Markdown 管道表格解析器。
|
||||
|
||||
将 Markdown 表格文本解析为 list[dict],每个字典代表一行数据。
|
||||
支持中文列名,处理列数不一致的情况。
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def parse_table(lines: list[str]) -> list[dict]:
|
||||
"""解析单个 Markdown 表格。
|
||||
|
||||
Args:
|
||||
lines: 表格的文本行列表(包含表头、分隔行和数据行)。
|
||||
|
||||
Returns:
|
||||
列表,每个元素是一行数据的字典,key 为表头文本。
|
||||
返回空列表如果输入不是有效表格。
|
||||
"""
|
||||
if not lines:
|
||||
return []
|
||||
|
||||
# 找到分隔行(包含 |---| 的行)
|
||||
sep_idx = -1
|
||||
for i, line in enumerate(lines):
|
||||
if re.match(r'\s*\|[\s\-:]+\|', line):
|
||||
sep_idx = i
|
||||
break
|
||||
|
||||
if sep_idx < 1:
|
||||
return [] # 没有有效的表头+分隔行
|
||||
|
||||
# 提取表头
|
||||
header_line = lines[sep_idx - 1]
|
||||
headers = _split_row(header_line)
|
||||
if not headers:
|
||||
return []
|
||||
|
||||
# 提取数据行(分隔行之后的所有 | 开头的行)
|
||||
data_rows = []
|
||||
for line in lines[sep_idx + 1:]:
|
||||
stripped = line.strip()
|
||||
if not stripped or not stripped.startswith('|'):
|
||||
break # 表格结束
|
||||
cells = _split_row(stripped)
|
||||
data_rows.append(cells)
|
||||
|
||||
# 构建结果
|
||||
result = []
|
||||
for row in data_rows:
|
||||
entry = {}
|
||||
for j, header in enumerate(headers):
|
||||
entry[header] = row[j] if j < len(row) else ""
|
||||
result.append(entry)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def parse_all_tables(text: str) -> list[tuple[list[str], list[dict]]]:
|
||||
"""解析文本中的所有 Markdown 表格。
|
||||
|
||||
Args:
|
||||
text: 完整的 Markdown 文本。
|
||||
|
||||
Returns:
|
||||
列表,每个元素为 (header, rows) 元组。
|
||||
header 为表头列表,rows 为 list[dict]。
|
||||
"""
|
||||
results = []
|
||||
lines = text.split('\n')
|
||||
i = 0
|
||||
|
||||
while i < len(lines):
|
||||
# 寻找潜在的表格起始行(包含 | 的行)
|
||||
if '|' in lines[i]:
|
||||
# 收集连续的表格行
|
||||
table_lines = []
|
||||
while i < len(lines) and '|' in lines[i]:
|
||||
table_lines.append(lines[i].strip())
|
||||
i += 1
|
||||
|
||||
# 尝试解析
|
||||
rows = parse_table(table_lines)
|
||||
if rows:
|
||||
# 提取表头
|
||||
for tl in table_lines:
|
||||
if re.match(r'\s*\|[\s\-:]+\|', tl):
|
||||
sep_idx = table_lines.index(tl)
|
||||
if sep_idx > 0:
|
||||
headers = _split_row(table_lines[sep_idx - 1])
|
||||
results.append((headers, rows))
|
||||
break
|
||||
continue
|
||||
i += 1
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def find_tables_in_section(text: str, section_start: int, section_end: int) -> list[list[dict]]:
|
||||
"""在文本的指定范围内找到所有表格。
|
||||
|
||||
Args:
|
||||
text: 完整文本。
|
||||
section_start: 区域起始行号。
|
||||
section_end: 区域结束行号。
|
||||
|
||||
Returns:
|
||||
表格列表,每个表格是 list[dict]。
|
||||
"""
|
||||
lines = text.split('\n')
|
||||
section_lines = lines[section_start:section_end]
|
||||
tables = []
|
||||
|
||||
i = 0
|
||||
while i < len(section_lines):
|
||||
if '|' in section_lines[i]:
|
||||
# 收集连续表格行
|
||||
table_lines = []
|
||||
while i < len(section_lines) and '|' in section_lines[i]:
|
||||
table_lines.append(section_lines[i])
|
||||
i += 1
|
||||
rows = parse_table(table_lines)
|
||||
if rows:
|
||||
tables.append(rows)
|
||||
continue
|
||||
i += 1
|
||||
|
||||
return tables
|
||||
|
||||
|
||||
def _split_row(line: str) -> list[str]:
|
||||
"""将 | 分隔的行拆分为单元格列表,去除首尾空格。"""
|
||||
# 去除首尾空白
|
||||
line = line.strip()
|
||||
# 按管道符分割
|
||||
if not line.startswith('|'):
|
||||
return []
|
||||
if not line.endswith('|'):
|
||||
# 有些行末尾可能没有 |
|
||||
parts = line.split('|')
|
||||
else:
|
||||
parts = line.split('|')
|
||||
|
||||
# 去除首尾空元素(管道符分割产生的)
|
||||
cells = [c.strip() for c in parts]
|
||||
# 去掉首尾空字符串
|
||||
while cells and cells[0] == '':
|
||||
cells.pop(0)
|
||||
while cells and cells[-1] == '':
|
||||
cells.pop()
|
||||
|
||||
return cells
|
||||
|
||||
|
||||
def parse_location(text: str) -> tuple[float, float, float]:
|
||||
"""解析位置字符串 '(X,Y,Z)' 为三元组。"""
|
||||
if not text:
|
||||
return (0.0, 0.0, 0.0)
|
||||
m = re.search(r'\(([-\d.]+)\s*[,,]\s*([-\d.]+)\s*[,,]\s*([-\d.]+)\)', text)
|
||||
if m:
|
||||
return (float(m.group(1)), float(m.group(2)), float(m.group(3)))
|
||||
return (0.0, 0.0, 0.0)
|
||||
|
||||
|
||||
def parse_rotation(text: str) -> tuple[float, float, float]:
|
||||
"""解析旋转字符串 '(P,Y,R)' 为三元组(度数)。"""
|
||||
return parse_location(text)
|
||||
|
||||
|
||||
def parse_scale(text: str) -> tuple[float, float, float]:
|
||||
"""解析缩放字符串 '(X,Y,Z)' 为三元组。'—' 或空返回 (1,1,1)。"""
|
||||
if not text or text.strip() in ('—', '–', '-', '无', ''):
|
||||
return (1.0, 1.0, 1.0)
|
||||
return parse_location(text)
|
||||
|
||||
|
||||
def clean_cell(text: str) -> str:
|
||||
"""清理单元格文本:去首尾空白、去反引号。"""
|
||||
if not text:
|
||||
return ""
|
||||
text = text.strip()
|
||||
# 去除 Markdown 反引号包裹
|
||||
if text.startswith('`') and text.endswith('`'):
|
||||
text = text[1:-1]
|
||||
return text.strip()
|
||||
@@ -0,0 +1,159 @@
|
||||
"""搭建报告生成器。
|
||||
|
||||
从解析数据和构建日志生成 Markdown 格式的搭建报告,
|
||||
格式与 docs/technical/关卡-v01-搭建报告.md 一致。
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def generate_report(
|
||||
level_data: dict,
|
||||
build_log: Optional[list[dict]] = None,
|
||||
output_path: str = "",
|
||||
) -> str:
|
||||
"""生成搭建报告 Markdown。
|
||||
|
||||
Args:
|
||||
level_data: 解析器输出的关卡数据(含 metadata/actors/summary)。
|
||||
build_log: 构建日志(含每个 Actor 的成功/失败状态)。可选。
|
||||
output_path: 报告输出路径。为空则只返回字符串。
|
||||
|
||||
Returns:
|
||||
Markdown 格式的搭建报告文本。
|
||||
"""
|
||||
actors = level_data.get("actors", [])
|
||||
summary = level_data.get("summary", {})
|
||||
metadata = level_data.get("metadata", {})
|
||||
|
||||
lines = []
|
||||
lines.append("# 白模搭建报告 — 自动生成")
|
||||
lines.append("")
|
||||
lines.append(f"> 生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
|
||||
lines.append(f"> 数据来源: {metadata.get('source', '未知')}")
|
||||
lines.append(f"> 场景尺寸: {metadata.get('scene_size', ['?','?'])[0]}cm × {metadata.get('scene_size', ['?','?'])[1]}cm")
|
||||
lines.append("")
|
||||
|
||||
# ---- 搭建概要 ----
|
||||
lines.append("## 一、搭建概要")
|
||||
lines.append("")
|
||||
|
||||
total = summary.get("total_actors", len(actors))
|
||||
by_category = summary.get("by_category", {})
|
||||
|
||||
lines.append(f"- **总 Actor 数**: {total}")
|
||||
for cat in ["STR", "PROP", "INTR", "COLL", "BND", "LGT", "PLAY"]:
|
||||
count = by_category.get(cat, 0)
|
||||
if count > 0:
|
||||
cat_name = {"STR": "结构体", "PROP": "布景", "INTR": "交互物",
|
||||
"COLL": "收集品", "BND": "边界", "LGT": "光源", "PLAY": "PlayerStart"}
|
||||
lines.append(f"- {cat_name.get(cat, cat)} ({cat}): {count}")
|
||||
|
||||
if build_log:
|
||||
ok = sum(1 for b in build_log if b.get("status") == "ok")
|
||||
failed = sum(1 for b in build_log if b.get("status") != "ok")
|
||||
lines.append(f"- 构建成功: {ok}")
|
||||
lines.append(f"- 构建失败: {failed}")
|
||||
|
||||
lines.append("")
|
||||
|
||||
# ---- 逐区域对照 ----
|
||||
lines.append("## 二、逐区域对照")
|
||||
lines.append("")
|
||||
|
||||
by_zone = summary.get("by_zone", {})
|
||||
zone_names = {
|
||||
"Z1": "家", "Z2": "小卖部", "Z3": "主街道",
|
||||
"Z4": "空地", "Z5": "巷子", "Z6": "水塘边", "Z7": "老居民楼",
|
||||
"GLOBAL": "全局",
|
||||
}
|
||||
|
||||
for zone_id in ["Z1", "Z2", "Z3", "Z4", "Z5", "Z6", "Z7", "GLOBAL"]:
|
||||
zone_actors = [a for a in actors if a.get("zone_id") == zone_id]
|
||||
if not zone_actors:
|
||||
continue
|
||||
|
||||
zone_name = zone_names.get(zone_id, zone_id)
|
||||
lines.append(f"### {zone_id} {zone_name}")
|
||||
lines.append("")
|
||||
lines.append(f"共 {len(zone_actors)} 个 Actor")
|
||||
lines.append("")
|
||||
lines.append("| # | Actor 名称 | 资产 | 位置 | 缩放 | 材质 | 状态 |")
|
||||
lines.append("|---|-----------|------|------|------|------|------|")
|
||||
|
||||
for j, a in enumerate(zone_actors):
|
||||
name = a.get("actor_name", "")
|
||||
asset = a.get("asset_key", "—")
|
||||
loc = a.get("location", [0, 0, 0])
|
||||
loc_str = f"({loc[0]:.0f},{loc[1]:.0f},{loc[2]:.0f})"
|
||||
scale = a.get("scale", [1, 1, 1])
|
||||
if scale == [1, 1, 1] or scale == (1, 1, 1):
|
||||
scale_str = "—"
|
||||
else:
|
||||
scale_str = f"({scale[0]:.2f},{scale[1]:.2f},{scale[2]:.2f})"
|
||||
mat = a.get("material", "—") or "—"
|
||||
|
||||
# 状态
|
||||
status = "✅"
|
||||
if build_log:
|
||||
log_entry = next((b for b in build_log if b.get("name") == name), None)
|
||||
if log_entry:
|
||||
status = "✅" if log_entry.get("status") == "ok" else f"❌ {log_entry.get('reason', '')}"
|
||||
|
||||
lines.append(f"| {j+1} | {name} | {asset} | {loc_str} | {scale_str} | {mat} | {status} |")
|
||||
|
||||
lines.append("")
|
||||
|
||||
# ---- 资产使用统计 ----
|
||||
lines.append("## 三、资产使用统计")
|
||||
lines.append("")
|
||||
|
||||
asset_usage = {}
|
||||
for a in actors:
|
||||
key = a.get("asset_key", "")
|
||||
if key:
|
||||
asset_usage[key] = asset_usage.get(key, 0) + 1
|
||||
|
||||
lines.append("| 资产名 | 使用次数 |")
|
||||
lines.append("|--------|---------|")
|
||||
for key in sorted(asset_usage.keys(), key=lambda k: -asset_usage[k]):
|
||||
lines.append(f"| {key} | {asset_usage[key]} |")
|
||||
lines.append("")
|
||||
|
||||
# ---- 缩放记录 ----
|
||||
lines.append("## 四、缩放记录")
|
||||
lines.append("")
|
||||
lines.append("| Actor 名称 | 资产 | 缩放 | 实际尺寸 (cm) |")
|
||||
lines.append("|-----------|------|------|-------------|")
|
||||
|
||||
for a in actors:
|
||||
scale = a.get("scale", [1, 1, 1])
|
||||
if scale != [1, 1, 1] and scale != (1, 1, 1):
|
||||
name = a.get("actor_name", "")
|
||||
asset_key = a.get("asset_key", "")
|
||||
# 计算实际尺寸
|
||||
from config.asset_mapping import MESH_ASSETS
|
||||
default_size = MESH_ASSETS.get(asset_key, {}).get("default_size", (100, 100, 100))
|
||||
if default_size:
|
||||
actual = (
|
||||
f"{default_size[0]*scale[0]:.0f}×"
|
||||
f"{default_size[1]*scale[1]:.0f}×"
|
||||
f"{default_size[2]*scale[2]:.0f}"
|
||||
)
|
||||
else:
|
||||
actual = "(默认尺寸未知)"
|
||||
lines.append(f"| {name} | {asset_key} | ({scale[0]:.2f},{scale[1]:.2f},{scale[2]:.2f}) | {actual} |")
|
||||
|
||||
lines.append("")
|
||||
|
||||
report = "\n".join(lines)
|
||||
|
||||
# 写入文件
|
||||
if output_path:
|
||||
Path(output_path).write_text(report, encoding="utf-8")
|
||||
print(f"报告已写入: {output_path}")
|
||||
|
||||
return report
|
||||
@@ -0,0 +1,103 @@
|
||||
"""离线资产验证模块。
|
||||
|
||||
检查解析数据中的资产引用是否有效:
|
||||
1. asset_key 是否在 asset_mapping 中定义
|
||||
2. 材质名是否在 material_mapping 中定义
|
||||
3. Content/ 目录下 .uasset 文件是否存在(可选)
|
||||
"""
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass
|
||||
|
||||
from config.asset_mapping import MESH_ASSETS
|
||||
from config.material_mapping import MATERIAL_PATHS
|
||||
|
||||
logger = logging.getLogger("level_builder.validator")
|
||||
|
||||
|
||||
@dataclass
|
||||
class ValidationResult:
|
||||
"""验证结果。"""
|
||||
total_actors: int = 0
|
||||
valid_actors: int = 0
|
||||
warnings: list[str] = None
|
||||
errors: list[str] = None
|
||||
missing_assets: set[str] = None
|
||||
missing_materials: set[str] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.warnings is None:
|
||||
self.warnings = []
|
||||
if self.errors is None:
|
||||
self.errors = []
|
||||
if self.missing_assets is None:
|
||||
self.missing_assets = set()
|
||||
if self.missing_materials is None:
|
||||
self.missing_materials = set()
|
||||
|
||||
@property
|
||||
def is_valid(self) -> bool:
|
||||
return len(self.errors) == 0
|
||||
|
||||
|
||||
def validate_actors(actors: list[dict], project_root: str = "") -> ValidationResult:
|
||||
"""验证 Actor 列表中的资产引用。
|
||||
|
||||
Args:
|
||||
actors: Actor 数据列表(来自解析器的 JSON 输出)。
|
||||
project_root: 项目根目录路径(用于文件存在性检查)。
|
||||
|
||||
Returns:
|
||||
ValidationResult 包含所有验证信息。
|
||||
"""
|
||||
result = ValidationResult(total_actors=len(actors))
|
||||
|
||||
for i, actor in enumerate(actors):
|
||||
name = actor.get("actor_name", f"<unnamed #{i}>")
|
||||
asset_key = actor.get("asset_key", "")
|
||||
material = actor.get("material", "")
|
||||
asset_type = actor.get("asset_type", "static_mesh")
|
||||
|
||||
# 跳过非 mesh 类型(光照、PlayerStart 等)
|
||||
if asset_type != "static_mesh":
|
||||
result.valid_actors += 1
|
||||
continue
|
||||
|
||||
# 检查 asset_key
|
||||
if not asset_key:
|
||||
result.warnings.append(f"{name}: 无 asset_key(可能是光照/特殊 Actor)")
|
||||
result.valid_actors += 1
|
||||
continue
|
||||
|
||||
if asset_key not in MESH_ASSETS:
|
||||
result.errors.append(f"{name}: 未知资产 '{asset_key}'")
|
||||
result.missing_assets.add(asset_key)
|
||||
continue
|
||||
|
||||
# 检查材质
|
||||
if material and material not in MATERIAL_PATHS:
|
||||
result.warnings.append(f"{name}: 未知材质 '{material}'")
|
||||
result.missing_materials.add(material)
|
||||
|
||||
# 检查 .uasset 文件存在性(如果提供了项目根目录)
|
||||
if project_root:
|
||||
mesh_info = MESH_ASSETS[asset_key]
|
||||
asset_path = mesh_info["path"]
|
||||
# /Game/BlockingStarterPack/Meshes/.../SM_Xxx.SM_Xxx → Content/BlockingStarterPack/Meshes/.../SM_Xxx.uasset
|
||||
fs_path = asset_path.replace("/Game/", "Content/").split(".")[0] + ".uasset"
|
||||
full_path = Path(project_root) / fs_path
|
||||
if not full_path.exists():
|
||||
result.warnings.append(f"{name}: 资产文件不存在 '{fs_path}'")
|
||||
|
||||
result.valid_actors += 1
|
||||
|
||||
# 汇总日志
|
||||
if result.missing_assets:
|
||||
logger.error(f"缺失资产定义: {sorted(result.missing_assets)}")
|
||||
if result.missing_materials:
|
||||
logger.warning(f"缺失材质定义: {sorted(result.missing_materials)}")
|
||||
logger.info(f"验证完成: {result.valid_actors}/{result.total_actors} 有效, "
|
||||
f"{len(result.errors)} 错误, {len(result.warnings)} 警告")
|
||||
|
||||
return result
|
||||
Reference in New Issue
Block a user