Compare commits

...

44 Commits

Author SHA1 Message Date
starmen1265 bc1cd45c38 Merge pull request 'config(git): 引入develop长期开发分支模型和git工作流自动化' (#38) from develop into main
Reviewed-on: http://micecola.top:8418/starmen1265/ChildHood/pulls/38
2026-06-11 22:52:50 +08:00
starmen1265 2342b63fc2 docs(technical): 交互系统技术方案 v1.0 重写:Effect/Action 数据驱动架构 2026-06-11 19:04:14 +08:00
starmen1265 ac7a3abda9 config(git): 引入develop长期开发分支模型和git工作流自动化 2026-06-11 15:24:38 +08:00
starmen1265 3b851a195e Merge pull request 'fix/asset-catalog-pipeline' (#37) from fix/asset-catalog-pipeline into main
Reviewed-on: http://micecola.top:8418/starmen1265/ChildHood/pulls/37
2026-06-11 14:28:48 +08:00
starmen1265 39a34577da merge: 合并main分支,解决冲突并统一交互输入架构 2026-06-11 14:26:44 +08:00
starmen1265 fd40217ab8 feat(interaction): 新增交互InputAction资产并配置IMC和BP绑定 2026-06-11 14:22:57 +08:00
starmen1265 85c2062318 feat(interaction): 新增UI Widget基类并集成到InteractionManager 2026-06-11 11:43:10 +08:00
starmen1265 5df451011c feat(interaction): Character和PlayerController集成交互系统 2026-06-11 11:25:30 +08:00
starmen1265 00ca7044c7 feat(interaction): 新增交互系统核心C++框架(6a-6f) 2026-06-11 11:25:15 +08:00
starmen1265 a4fd3d3092 chore(art): 新增深夏时光参考截图(地图、正午系列) 2026-06-11 10:52:16 +08:00
starmen1265 8ee21e5e80 docs(technical): 更新交互系统和收集系统技术方案 2026-06-11 10:52:14 +08:00
starmen1265 c4ea51b7e6 chore(memory): 更新策划Agent记忆索引 2026-06-11 10:52:13 +08:00
ai-pm caadd87d8f Merge pull request 'chore: �������������������ʲ������ű�' (#36) from chore/misc-updates into main 2026-06-11 00:55:28 +08:00
starmen1265 82fb409a9e chore: 更新审查记忆和输入资产创建脚本
- reviewer记忆: 新增P-009审查通过记录
- setup_input_assets.py: 重写为多方案容错架构(模板复制+Factory备选)

Task: P-009, P-011
2026-06-11 00:54:06 +08:00
starmen1265 0b060b0a74 Merge pull request 'feat(tools): 新增execution-to-guide执行案转搭建指南工具' (#35) from feature/execution-to-guide into main
Reviewed-on: http://micecola.top:8418/starmen1265/ChildHood/pulls/35
2026-06-11 00:52:18 +08:00
starmen1265 91b390d577 Merge branch 'main' into feature/execution-to-guide 2026-06-11 00:52:10 +08:00
starmen1265 6f8b599629 feat(tools): 新增execution-to-guide执行案转搭建指南工具
- execution_parser.py: 解析关卡执行案Markdown提取区域/结构体/布景/边界/光照
- asset_expander.py: BSP资产建议展开+绝对坐标计算+旋转推断
- guide_writer.py: 生成精确匹配build_guide_parser格式的搭建指南
- composite_writer.py: 组合资产清单+JSON模板生成
- converter.py: 主入口CLI工具
- 更新level-execution-template.md: 移除交互物/收集品章节,新增组合资产标记

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 00:44:46 +08:00
starmen1265 3d0b835593 Merge pull request 'feat(input): P-011 ��������InputAction�ʲ�������IMC/BP����' (#34) from feature/p011-input-assets into main
Reviewed-on: http://micecola.top:8418/starmen1265/ChildHood/pulls/34
2026-06-11 00:44:22 +08:00
starmen1265 f86ffb511e feat(input): 新增交互InputAction资产并更新IMC/BP绑定
- 新增IA_Interact(E键)、IA_Cancel(ESC)、IA_SwitchInteract(Tab)
- 更新IMC_Default添加三个交互按键映射
- 更新BP_ChildHoodCharacter绑定交互IA属性

Task: P-011
2026-06-11 00:42:16 +08:00
starmen1265 7d0bfc123b Merge pull request 'fix/asset-catalog-pipeline' (#30) from fix/asset-catalog-pipeline into main
Reviewed-on: http://micecola.top:8418/starmen1265/ChildHood/pulls/30
2026-06-10 18:48:19 +08:00
starmen1265 e63ae56021 Merge branch 'main' into fix/asset-catalog-pipeline 2026-06-10 18:47:56 +08:00
starmen1265 4c6c8db1a8 chore(asset-catalog): 新增工具输出数据 2026-06-10 18:45:33 +08:00
starmen1265 86967bb256 chore(art): 新增深夏时光黄昏系列参考截图 2026-06-10 18:45:29 +08:00
starmen1265 e982464cb3 chore(pm): 新增PM Agent记忆文件 2026-06-10 18:45:24 +08:00
starmen1265 ac8c67ffc6 chore(config): 简化权限配置为通配符规则 2026-06-10 18:45:18 +08:00
starmen1265 2f7d0fa2f8 Merge pull request 'fix(asset-catalog): 修复推理管线并新增README' (#29) from fix/asset-catalog-pipeline into main
Reviewed-on: http://micecola.top:8418/starmen1265/ChildHood/pulls/29
2026-06-10 18:42:33 +08:00
starmen1265 ca78e9c4cf Merge branch 'main' into fix/asset-catalog-pipeline 2026-06-10 18:42:18 +08:00
starmen1265 042bebe9d7 fix(asset-catalog): 修复推理管线并新增README
- 默认模型改为 glm-5.1,API地址改为 coding 套餐端点
- 默认批次大小 20→5,增加 max_tokens=4096
- 优化 prompt 模板,明确要求返回 {"results": [...]} 格式
- 修复全批失败时 ai_inference_metadata 的 KeyError
- 输出目录从 <project>/output/ 统一到 <tool>/outputs/
- 新增 README.md(Pipeline流程、环境变量、CLI参数说明)
2026-06-10 18:41:06 +08:00
starmen1265 17fbf9fe3f Merge pull request 'feature/player-controller-v01' (#28) from feature/player-controller-v01 into main
Reviewed-on: http://micecola.top:8418/starmen1265/ChildHood/pulls/28
2026-06-10 18:39:46 +08:00
starmen1265 810c3a030c Merge branch 'main' into feature/player-controller-v01 2026-06-10 18:39:39 +08:00
starmen1265 cbdf941da5 feat(input): 更新角色BP和控制器BP的输入配置
BP_ChildHoodCharacter: 绑定InteractAction/CancelAction/SwitchInteractAction
BP_ChildHoodPlayerController: DefaultMappingContexts配置IMC

Task: P-011
2026-06-10 18:03:51 +08:00
starmen1265 f74883701d fix(tools): 修复setup_input.bat中文编码问题
移除bat文件中的中文注释,改为纯ASCII避免cmd编码错误

Task: P-011
2026-06-10 17:49:28 +08:00
starmen1265 fe770daf77 feat(tools): 新增Input资产自动创建脚本
创建 tools/input-setup/ 目录,包含:
- setup_input_assets.py: UE5编辑器Python脚本,自动创建IA_Interact/IA_Cancel/IA_SwitchInteract并更新IMC_Default
- setup_input.bat: 命令行启动脚本

Task: P-011
2026-06-10 17:44:40 +08:00
starmen1265 3c69445d90 feat(player-controller): 实现v0.1角色控制系统
- 移除跳跃逻辑:删除JumpAction、DoJumpStart/End
- 调整CMC参数:MaxWalkSpeed=450, Acceleration=2250, Brake=1500, RotationRate=720
- 调整SpringArm:TargetArmLength=350, SocketOffset.Z=80, CameraLag=6, ProbeSize=12
- 添加Pitch限幅:ViewPitchMin=-30, ViewPitchMax=45, 初始俯角-15°
- 新增交互状态:bIsInteracting + SetInteracting/IsInteracting/IsRunning
- 新增交互IA:InteractAction/CancelAction/SwitchInteractAction(占位回调)
- DoMove添加交互中忽略移动输入检查

Task: P-011
2026-06-10 17:39:37 +08:00
starmen1265 f1932667aa Merge pull request 'feat(design): 城郊区域建筑与区域规划头脑风暴及场景更新' (#26) from feature/design-area-planning into main
Reviewed-on: http://micecola.top:8418/starmen1265/ChildHood/pulls/26
2026-06-10 16:06:03 +08:00
starmen1265 f2f4d0f382 Merge branch 'main' into feature/design-area-planning 2026-06-10 16:05:49 +08:00
starmen1265 2cf295f95d Merge pull request 'feat(world-sketcher): 新增本地服务器、标签显示和README重写' (#25) from feature/world-sketcher-improvements into main
Reviewed-on: http://micecola.top:8418/starmen1265/ChildHood/pulls/25
2026-06-10 16:05:20 +08:00
starmen1265 9b23cb1d24 feat(design): 城郊区域建筑与区域规划头脑风暴及场景更新
- 新增头脑风暴记录:灵感对比新发现+建筑区域规划共识
- 更新场景与世界观:新增5家店铺、4处小伙伴家、秘密基地等
- 主街从6个店铺扩展到10个+4设施,可进入室内从5-6个扩展到~12个
- 新增废品回收点、储蓄所、棋牌室、公交站牌等设施
- 更新策划Agent记忆
2026-06-10 15:52:32 +08:00
starmen1265 c509223e08 feat(world-sketcher): 新增本地服务器、标签显示和README重写
- 新增 server.py + start.bat: 本地保存/加载服务器
- sketch.html: 切换层级时自动绘制元素标签
- L1 显示区域名称+地形标高,L2 显示建筑名称+高度
- 保存优先POST到服务器,加载优先从output/读取
- 命名弹窗区分'地形标高'和'建筑高度'
- README 全面重写:双模式启动、文件结构、API端点、高度语义
2026-06-10 15:49:12 +08:00
starmen1265 b8f27221b2 Merge pull request 'feat(asset-catalog): 简化分析工具+新增汇总和AI推理脚本' (#24) from feature/asset-catalog-simplify into main
Reviewed-on: http://micecola.top:8418/starmen1265/ChildHood/pulls/24
2026-06-10 00:52:21 +08:00
starmen1265 7a2436a612 feat(asset-catalog): 简化分析工具+新增汇总和AI推理脚本
- analyze_meshes: 移除失败的 LineTrace 和六面分析,只保留基础属性
  (包围盒、锚点、复杂度、Socket、材质槽位、自动Tag)
- build_database: 新增离线汇总脚本,将 meshes/*.json 扁平化为
  统一的 asset_database.json
- ai_inference: 新增 GLM API 批量推理脚本,按类别分批推断语义信息
  (朝向、表面高度、功能描述、交互属性、降级方案)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 00:50:56 +08:00
starmen1265 a951009dae chore(world-sketcher): 新增迈皋桥场景布局草图输出
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 00:50:02 +08:00
starmen1265 a20eec8d57 feat(ai-art): 新增API易生图脚本和连通性测试
- 新增 image_gen.py:支持文生图/风格校准/双参考融合三种模式
- 新增 .env.example:API易平台配置模板
- 更新 .gitignore:排除 .env 密钥文件
- 首张连通性测试图已生成(GPT-Image-2)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 00:48:29 +08:00
starmen1265 bb34641bfe feat(world-sketcher): 增加高度标注和连通关系,AI分析改为读JSON
- sketch.html: 双击命名弹窗增加高度(m)输入和连通多选
- sketch.html: 新增defaultHeightForType()按画笔类型设默认高度
- sketch.html: 连通关系双向同步,JSON输出增加height_m/connections
- sketch.html: restoreScene恢复新字段,保存/加载流程不受影响
- SKILL.md: 步骤3从vision分析PNG改为读取JSON结构化数据
- SKILL.md: 布局解读文档模板增加高度和连通列
- README.md: 画笔图例增加默认高度,更新JSON示例和AI流程说明

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 00:06:05 +08:00
216 changed files with 127779 additions and 1292 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
# 策划 Agent Memory Index
- [千禧年怀旧文化灵感](nostalgia-culture-inspirations.md) — 电视剧/广播体操/广场舞/电话热线游戏的灵感记录与共识
- [千禧年怀旧文化灵感](nostalgia-culture-inspirations.md) — 电视剧/广播体操/广场舞/电话热线/零食/小孩游戏/乡下活动灵感汇总
- [建筑与区域规划](building-area-plan.md) — 城市主场景完整建筑清单、可进入室内、新增设施共识
- [v0.1执行案状态](v01-execution-status.md) — 三份执行案已通过二次质量门审查,流水线可推进至④程序设计
@@ -0,0 +1,37 @@
---
name: building-area-plan
description: 城市主场景建筑与区域规划共识,包含完整建筑清单、可进入室内、新增设施
metadata:
type: project
---
## 城市主场景建筑清单(2026-06-10 确认)
来源:头脑风暴-2026-06-10-建筑与区域规划.md
### 关键变更(vs 原场景设计)
- 小伙伴家:原"全部不可进入" → 4家可进入(简化室内)
- 包机房:合并进街机店后屋
- 废品回收:固定摊位 + 流动收废品车
- 秘密基地:多处散布(工地围挡后、平房区死角等)
- 新增店铺:粮油店、服装店/裁缝铺、鞋店(主街东侧)、储蓄所(西侧)
- 新增设施:棋牌室(纯氛围)、公交站牌(纯装饰)、公共厕所、自行车棚、信报箱
### 主街布局(东西排列)
东→:学校操场 | 小卖部 | 粮油店 | 书店 | 服装店 | 鞋店 · 公交站 · 棋牌室 | 储蓄所 | 录像出租 | 网吧 | 街机店 ←西
### 可进入室内(~12个)
完整:主角家、合并小卖部、书店、街机店、网吧、录像出租点
简化:死党A家(小区内)、死党B家(平房区)、老板儿子家(小卖部后屋)、女班长家(主角家同楼)
### 不做的元素
菜地(留乡下)、装修、游泳(河边不下水)
### How to apply:
- 场景与世界观.md 已更新为最新清单
- 新增店铺和设施在后续版本逐步添加(见版本映射)
- 概念草案不变(冻结),变更记录在系统设计案中
@@ -54,3 +54,34 @@ metadata:
- 广播体操和广场舞适合作为场景氛围事件
- 迈皋桥考据直接用于场景设计的"半城半乡"氛围塑造
- 华电子弟学校、工厂宿舍区、菜地、老街都是高还原度的场景元素
---
## 2026-06-10 灵感补充
来源:头脑风暴-2026-06-10-建筑与区域规划.md
### 重点新发现(⭐⭐⭐)
1. **钻狗洞的秘密基地** — 多处散布(工地围挡后、平房区死角),高探索叙事价值
2. **叠纸飞机** — 纸张收集+折叠选择+飞行结果三层玩法
3. **不知道名字的小哥哥** — 一次性邂逅NPC,极高情感潜力微型叙事线
4. **体力系统+家庭动态事件** — 吃饭补充体力→不吃炒饭→换速冻水饺→给饭钱
5. **爷爷奶奶家亲戚小孩** — 乡下NPC角色体系(之前乡下NPC完全空白)
### 新零食/小吃(具体品牌品种)
大大泡泡糖、秀逗(超酸糖)、无花果丝、老鼠屎(盐津葡萄)、跳跳糖、烤鱿鱼、烤里脊
### 新小孩游戏
双人拍手/互推游戏、猜在哪个手里、跑步比赛、叠纸飞机
### 新人物/社交
- 北方来南方的同学(与"转学生"角色互补)
- 等着去别人家(串门文化)
### 新乡下活动
钓鱼(鱼竿)、游泳、挤牛奶、喂鸡掏鸡蛋、菜地浇水、打水漂
+1
View File
@@ -2,3 +2,4 @@
- [UE5.7 Python API](ue57-python-api.md) — 实测 API 差异(set_actor_rotation、new_level、CWD、import 路径、Rotator 关键字参数)
- [V01 Bugfix](level-builder-bugfix-v01.md) — 8个白模关卡问题诊断修复(搭建指南6+解析器双重解析+构建器静默fallback)
- [BSP Pivot Types](bsp-pivot-types.md) — 38种 BSP 资产实测锚点+自动补偿机制(角落/底边中心/底面中心)
- [交互系统架构](interaction-effect-architecture.md) — v1.0 Effect/Action 数据驱动架构(Manager在GI、AInfo Tick、门派生、Prompt独立)
@@ -0,0 +1,31 @@
---
name: interaction-effect-architecture
description: 交互系统 v1.0 Effect/Action 数据驱动架构决策记录
metadata:
type: project
---
## 交互系统架构决策(v1.0 重构,2026-06-11)
### 核心架构:Effect/Action 数据驱动
交互物不关心"交互后发生什么",只配置 Effect(由 Action 组成)交给 EffectManager 执行。
**Why:** v0.1 用 switch(Type) 硬编码,新增交互类型需改代码。v1.0 配数据即可。
### Manager 归属
- FInteractionManager / FEffectManager 是普通 C++ 类,不是 UObject
- 由 ASystemDriver (AInfo) 持有,随 World 生灭
- 不依赖 UE Subsystem 生命周期,显式控制创建顺序和 Tick 顺序
- 所有 UObject 引用用 TWeakObjectPtr,回调用 TFunction
- GameInstance 不持有任何交互系统对象,只有 UPersistenceSubsystem 跨 World
### 分层规则
- 表现层只读 Manager 缓存(getter),不碰 Component
- Manager 在选择变化时从 Component 读一次并缓存
- 逻辑事件仅用于 "A 影响 B"(如 Consume → Persistence)
### 协调方式
- Character 输入回调通过 ASystemDriver::Get(World) 访问两个 Manager
- Driver::BeginPlay 中 TFunction 连线 EffectManager.OnEffectComplete → Manager.OnInteractionFinished
- PromptComponent 通过 Driver 获取 Manager,只读缓存
@@ -0,0 +1,12 @@
---
name: subagent-permissions
description: 后台子 Agent 需要预授权 Edit/Write 权限才能修改文件
metadata:
type: feedback
---
后台子 Agent(game-dev 等)无法弹出交互式权限确认,缺少 Edit/Write 授权时会直接被拒绝文件修改。
**Why:** 首次启动 game-dev 后台 Agent 实现 P-011 时,因 settings.json 缺少 Edit/Write 权限,Agent 只读了技术文档但无法修改源码。
**How to apply:** 在 `.claude/settings.json` 的 `permissions.allow` 中添加 `"Edit"` 和 `"Write"`(2026-06-10 已添加)。如果后续新增需要后台运行的工具,同样需要预授权对应权限。
@@ -2,3 +2,4 @@
- [Gitea Token Permission](gitea-token-permission.md) — reviewer token 仅可评论,标签操作需用 PM token
- [P-006 Review](p006-review-note.md) — Issue #17 白模搭建审查通过记录
- [P-009 Review](p009-review-note.md) — Issue #20 收集系统程序设计审查通过记录
@@ -0,0 +1,19 @@
---
name: p009-review-note
description: Issue #20 P-009 收集系统程序设计审查通过记录
metadata:
type: project
---
## P-009 收集系统程序设计审查 (2026-06-10)
- Issue: #20
- 审查类型: 程序设计(质量门 2)
- 产出文件: `docs/technical/收集系统-技术方案.md`
- 结果: **通过**
### 审查要点
- 技术方案文档路径实际为中文文件名 `收集系统-技术方案.md`(非 Issue 中标注的 `collection-system-tech.md`)
- 执行案文件名也是中文:`收集系统-执行案.md`(非 `collection-system-execution.md`)
- 6 个检查维度全部通过:执行案覆盖度、美术资源需求、架构清晰度、数据流、技术风险、工作量与拆分建议
- 标签操作:移除了 gate:art-fail(30) 和 status:in-review(14),添加了 review:pass(33)
+28
View File
@@ -243,6 +243,34 @@ GameConfig/ # 数值配置表
---
## Git 工作流(develop 分支模型)
项目使用 `develop` 长期开发分支。所有 agent 在 develop 上工作,迭代结束时由 PM 发起 PR 合并回 main。
### 开始工作
```bash
bash .claude/scripts/git-workflow.sh ready
```
### 提交文档
```bash
bash .claude/scripts/git-workflow.sh commit docs <scope> "中文描述"
```
示例:`bash .claude/scripts/git-workflow.sh commit docs design "交互系统执行案"`
### 推送
```bash
bash .claude/scripts/git-workflow.sh push
```
### 完成工作后
1. 推送文档到 develop
2. 通过 gitea-collab 在对应 Issue 下评论汇报进度
3. 更新 Issue 标签为 `status:in-review`
4. 启动 `game-reviewer` 子代理
---
## 审查交接
完成工作并发布 Issue 评论后,启动审查流程:
+36
View File
@@ -221,6 +221,42 @@ skills:
---
## Git 工作流(develop 分支模型)
项目使用 `develop` 长期开发分支。所有 agent 在 develop 上工作,迭代结束时由 PM 发起 PR 合并回 main。
### 开始开发
```bash
bash .claude/scripts/git-workflow.sh ready
```
确认在 develop 分支上且代码最新。
### 提交代码
```bash
bash .claude/scripts/git-workflow.sh commit <type> <scope> "中文描述"
```
- ⚠️ 只提交自己修改的文件,避免提交其他 agent 的 WIP
- 蓝图(.uasset)和 C++ 分开提交
- commit message subject ≤ 50 字符
### 推送
```bash
bash .claude/scripts/git-workflow.sh push
```
### 完成开发后
1. 推送代码到 develop
2. 通过 gitea-collab 在对应 Issue 下评论汇报进度
3. 更新 Issue 标签为 `status:in-review`
4. 启动 `game-reviewer` 子代理
### 开发中发现 main 有更新
```bash
bash .claude/scripts/git-workflow.sh catchup
```
---
## 代码规范
- C++ 和蓝图遵循 UE5 编码规范
+30
View File
@@ -103,6 +103,36 @@ skills:
---
## Git 工作流(develop 分支模型)
项目使用 `develop` 长期开发分支,所有 agent 在 develop 上工作。
### PM 日常
```bash
# 会话开始时同步
bash .claude/scripts/git-workflow.sh ready
```
### 迭代完成 → 发布
当所有 Issue 都 review:pass 且用户确认发布时:
```bash
# 创建 develop → main 的 PR
bash .claude/scripts/git-workflow.sh release "迭代名称"
# 或手动:
# node .claude/skills/gitea-pm/gitea.js --role pm pr create --base main --head develop --title "标题" --body "描述"
```
通知用户在 Gitea 审查并合并 PR。
### 用户合并后同步
```bash
bash .claude/scripts/git-workflow.sh post-release
```
自动同步 main 并将 merge commit 同步回 develop。
---
## 决策框架
### 你可以自主决定:
+47 -10
View File
@@ -6,23 +6,30 @@
## 分支策略
**轻量 Feature Branch** — 单人开发,`main` 永远可玩可编译。
**develop 长期开发分支模型** — 单人开发 + 多 Agent 协作。
```
main ─────────── 永远可玩可编译,受保护,禁止直接推送
│
│ PR (Gitea) ← 里程碑/迭代结束时,PM 发起
│
└── develop ──── 长期分支,所有 agent 在此工作
```
| 分支 | 生命期 | 说明 |
|---|---|---|
| `main` | 永久 | 稳定版本,受保护 |
| `develop` | 永久 | 日常开发分支,所有 agent 共用 |
**辅助分支(可选,用于实验):**
| 分支 | 前缀 | 生命期 | 说明 |
|---|---|---|---|
| 主线 | `main` | 永久 | 稳定版本,不直接在上面开发 |
| 功能 | `feature/` | 合并后删 | 新系统、新功能 |
| 修复 | `fix/` | 合并后删 | Bug 修复 |
| 原型 | `prototype/` | 实验完即删 | 技术验证,随便写 |
| 发布 | `release/` | 永久 | 从 main 切出,版本标记 |
**命名规则:** `<类型>/<小写连字符描述>`
```
feature/player-movement fix/camera-clipping release/v0.1
```
**流程:**
1. 从 `main` 拉分支 → 2. 开发并提交 → 3. 测试通过后合并回 `main` → 4. 删除分支
1. 所有开发在 `develop` 上进行 → 2. 迭代完成时创建 PR → 3. 审查通过后合并回 `main`
---
@@ -100,3 +107,33 @@ Host gitea micecola.top
**内网地址**(家里用):`ssh://sc-gitea@192.168.1.22:2223/starmen1265/ChildHood.git`
完整规范见 → [GIT-GUIDE.md](GIT-GUIDE.md)
---
## 自动化工作流
`git-workflow.sh` 脚本封装了常用 git 操作:
```bash
# 开始工作:确保在 develop 上且最新
bash .claude/scripts/git-workflow.sh ready
# 提交修改
bash .claude/scripts/git-workflow.sh commit <type> <scope> "中文描述"
# 推送到远程
bash .claude/scripts/git-workflow.sh push
# 查看状态
bash .claude/scripts/git-workflow.sh status
```
### PM 发布流程
```bash
# 创建 develop → main PR
bash .claude/scripts/git-workflow.sh release "迭代名称"
# 用户在 Gitea 合并后同步
bash .claude/scripts/git-workflow.sh post-release
```
+36 -60
View File
@@ -7,81 +7,57 @@
## 一、分支策略
采用**轻量级 Feature Branch 模型**,避免企业级复杂流程,保持 `main` 分支随时可玩。
采用 **develop 长期开发分支模型**,适合单人开发 + 多 AI Agent 协作场景。所有 agent 在 `develop` 上工作,迭代结束时通过 PR 合并回 `main`。
### 1.1 分支总览
```
main ──────────────────────────────────────── 稳定主线
│
├── feature/player-movement ───→ 合并后删除
├── feature/inventory-system ──→ 合并后删除
├── fix/camera-clipping ───────→ 合并后删除
├── prototype/procedural-gen ──→ 实验完即删
└── release/v0.1 ──────────────→ 永久保留
main ──────────── 永远可玩可编译,受保护,禁止直接推送
│
│ PR (Gitea) ← 里程碑/迭代结束时,PM 发起
│
└── develop ──── 长期分支,所有 agent 的日常工作分支
│
├── 程序1 提交代码
├── 策划 提交文档
├── 程序2 提交代码
└── 迭代就绪 → PR develop→main
```
### 1.2 分支类型
| 分支 | 前缀 | 用途 | 合并目标 | 生命周期 |
|---|---|---|---|---|
| 主线 | `main` | 稳定可玩版本,每次提交都能编译运行 | — | 永久 |
| 功能 | `feature/` | 新系统、新功能开发 | `main` | 完成后删除 |
| 修复 | `fix/` | Bug 修复 | `main` | 完成后删除 |
| 原型 | `prototype/` | 技术验证、玩法实验 | 合并或丢弃 | 实验完即删 |
| 发布 | `release/` | 版本发布标记,只从 `main` 切出 | — | 永久保留 |
| 分支 | 用途 | 生命周期 |
|---|---|---|
| `main` | 稳定可玩版本,受保护 | 永久 |
| `develop` | 日常开发,所有 agent 共用 | 永久 |
| `prototype/` | 技术验证、玩法实验 | 实验完即删 |
| `release/` | 版本发布标记,只从 main 切出 | 永久保留 |
### 1.3 命名规范
格式:`<类型>/<简短描述>`,全小写,用连字符分隔单词。
### 1.3 工作流程
```
# ✅ 好的命名
feature/player-movement
feature/inventory-system
feature/dialogue-tree
fix/camera-clipping-through-walls
fix/enemy-ai-stuck-on-navmesh
prototype/procedural-dungeon
release/v0.1
1. 开始工作(agent 自动执行)
bash .claude/scripts/git-workflow.sh ready
# ❌ 不好的命名
my-new-feature
fix-bug
test
feature/AddPlayerMovement
2. 开发并提交
bash .claude/scripts/git-workflow.sh commit <type> <scope> "描述"
3. 推送到远程
bash .claude/scripts/git-workflow.sh push
4. 迭代完成,PM 创建 PR
bash .claude/scripts/git-workflow.sh release "迭代名称"
5. 用户在 Gitea 合并后同步
bash .claude/scripts/git-workflow.sh post-release
```
### 1.4 工作流程
### 1.4 核心原则
```
1. 开始新功能
git checkout main
git pull
git checkout -b feature/xxx
2. 开发过程中(随时提交)
git add .
git commit -m "wip(xxx): 进度描述"
3. 功能完成,测试通过
git checkout main
git merge feature/xxx
git branch -d feature/xxx # 删除已合并分支
4. 到达里程碑
git checkout main
git tag -a v0.1.0 -m "第一个可玩原型"
git checkout -b release/v0.1
git push origin release/v0.1
```
### 1.5 核心原则
- **`main` 永远可玩**:坏了就留在 feature 分支继续改,不要合并进 main
- **功能分支粒度**:一个分支做一件事,不要在一个分支里同时做多个系统
- **原型不怕乱**:`prototype/` 分支随便写,验证完再决定合并还是丢弃
- **定期清理**:已合并的 feature/fix 分支及时删除,避免分支堆积
- **`main` 永远可玩**:通过 develop → main PR 合并,不直接推送
- **develop 是工作台**:所有 agent 在 develop 上并行工作
- **不同文件不冲突**:各 agent 修改不同文件,可真正并行
- **同一文件需协调**:两个 agent 修改同一文件时需顺序执行
---
+12 -29
View File
@@ -2,6 +2,8 @@
> 本模板供策划产出关卡执行案时使用。关卡执行案是场景设计案的"空间精度版本"——将氛围描述转化为带有坐标、尺寸和资产建议的结构化搭建指南,供程序在 UE5 编辑器中搭建白模。
>
> **注意**:交互物和收集品的放置**不在本模板范围内**,由交互系统/收集系统后续在白盒关卡上叠加。
>
> 文件命名:`关卡-v<XX>-<名称>-执行案.md`(如 `关卡-v01-城郊街区-执行案.md`)
> 存放位置:`docs/design/<阶段>/`(与其他执行案同目录)
@@ -131,30 +133,18 @@
| Zx-P02 | | | | | |
| ... | | | | | |
#### 3.x.4 可交互物体标记
#### 3.x.4 组合资产标记
<!-- 引用交互系统执行案中的交互物,此处只标注空间位置 -->
<!-- 需要多个 Primitives 拼搭的物件(无直接 BSP 匹配)。如无此类物件可删除本节。 -->
| 交互编号 | 物件名称 | 交互类型 | 相对原点偏移 (ΔX,ΔY,ΔZ) | 触发半径 (cm) | 引用 |
|---------|---------|---------|----------------------|-------------|------|
| Zx-I01 | <!-- 如 旧冰箱 --> | <!-- 如 观察 --> | (200,400,10) | <!-- 如 150 --> | 交互系统-执行案 #n |
| Zx-I02 | | | | | |
| ... | | | | | |
> 交互类型参照交互系统执行案中的分类(观察/拾取/操作/对话等)。
> "引用"列填写交互系统执行案中对应交互物的编号,确保跨文档一致。
#### 3.x.5 收集品标记
<!-- 引用收集系统执行案中的收集品,此处只标注空间位置 -->
| 收集编号 | 物品名称 | 稀有度 | 相对原点偏移 (ΔX,ΔY,ΔZ) | 引用 |
|---------|---------|--------|----------------------|------|
| Zx-C01 | <!-- 如 彩色弹珠 --> | <!-- 如 普通/稀有 --> | (500,200,0) | 收集系统-执行案 #n |
| 编号 | 物件名称 | 期望尺寸 (约, cm) | 推荐拼搭方案 | 说明 |
|------|---------|-----------------|-------------|------|
| Zx-C01 | <!-- 如 旧收音机 --> | <!-- 如 20x15x12 --> | <!-- 如 Cube(0.2,0.15,0.12) + Cylinder天线 --> | <!-- 如 放在柜台上 --> |
| Zx-C02 | | | | |
| ... | | | | | |
> 稀有度影响拾取物的视觉效果标识(普通=白色光,稀有=金色光),具体表现由收集系统技术方案定义。
> 组合资产将在搭建指南生成阶段展开为多个 Primitive Actor,并在组合资产清单中记录拼搭方案。
> 用户确认后的组合资产方案将回馈到 asset-catalog 模板库,供后续关卡直接引用。
---
@@ -183,14 +173,6 @@
| G-E02 | | | | | |
| ... | | | | | |
### 4.3 收集品位置汇总
| # | 物品名称 | 所在区域 | 绝对坐标 (X,Y,Z) | 稀有度 | 引用 |
|---|---------|---------|-----------------|--------|------|
| 1 | | | | | 收集系统-执行案 #n |
| 2 | | | | | |
| ... | | | | | |
---
## 五、场景边界与遮挡
@@ -225,10 +207,10 @@
### 策划填写要点
1. **先确定区域坐标布局**(第二章),确保所有区域在总尺寸内不重叠、流线合理
2. **逐区域填写**(第三章),按"结构体 → 布景 → 交互物 → 收集品"的顺序
2. **逐区域填写**(第三章),按"结构体 → 布景 → 组合资产"的顺序
3. **BSP 资产建议是方向性指引**:写资产类别名和组合方式即可(如 "Wall_400x300 × 2"),不需要写文件路径
4. **尺寸以 cm 为单位**,对应 UE5 的 1 uu = 1 cm
5. **交叉引用**:交互物和收集品必须引用对应系统执行案中的编号,保持跨文档一致
5. **组合资产**:无法用单个 BSP 资产匹配的物件,在 3.x.4 中记录拼搭方案
### 不要填写的内容
@@ -237,6 +219,7 @@
- ❌ Blueprint 类名
- ❌ 精确的硬编码参数值(标注"建议值,可调"并附范围)
- ❌ 代码或脚本
- ❌ 交互物和收集品位置(由系统后续叠加)
### 与场景设计案的关系
+326
View File
@@ -0,0 +1,326 @@
#!/usr/bin/env bash
# ═══════════════════════════════════════════════════════════════
# git-workflow.sh — ChildHood 项目 Git 工作流自动化
#
# 基于 develop 长期开发分支模型。
# 所有 agent 在 develop 上工作,迭代结束时通过 PR 合并回 main。
#
# 用法: bash .claude/scripts/git-workflow.sh <command> [args...]
#
# 命令:
# ready 确保在 develop 上且最新
# commit <type> <scope> "<msg>" 格式化提交
# push 推送 develop 到远程
# submit <issue-number> 一键 commit + push(从 Issue 拉取信息)
# release [milestone-name] 创建 develop→main 的 PR
# post-release 合并后同步 main 和 develop
# catchup 将 develop 合并最新 main 的内容
# status 显示当前分支和状态
# ═══════════════════════════════════════════════════════════════
set -euo pipefail
# ─── 配置 ───
DEVELOP_BRANCH="develop"
MAIN_BRANCH="main"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
GITEA_PM_CLI="node $PROJECT_ROOT/.claude/skills/gitea-pm/gitea.js"
GITEA_COLLAB_CLI="node $PROJECT_ROOT/.claude/skills/gitea-collab/gitea.js"
# ─── 颜色 ───
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
info() { echo -e "${BLUE}ℹ️ $*${NC}"; }
ok() { echo -e "${GREEN}✅ $*${NC}"; }
warn() { echo -e "${YELLOW}⚠️ $*${NC}"; }
fail() { echo -e "${RED}❌ $*${NC}"; exit 1; }
# ─── 当前分支 ───
current_branch() { git rev-parse --abbrev-ref HEAD; }
# ─── 检查是否有未提交的修改 ───
has_changes() { [ -n "$(git status --porcelain)" ]; }
# ─── 检测 UE5 二进制文件 ───
has_ue5_binaries() {
git diff --cached --name-only 2>/dev/null | grep -qE '\.(uasset|umap)$'
}
# ═══════════════════════════════════════════════════════════════
# 命令实现
# ═══════════════════════════════════════════════════════════════
cmd_ready() {
info "准备工作环境..."
local branch
branch=$(current_branch)
# 如果不在 develop 上,切换过去
if [ "$branch" != "$DEVELOP_BRANCH" ]; then
if has_changes; then
warn "有未提交的修改,暂存中..."
git stash push --include-untracked -m "auto-stash-$(date +%s)"
fi
git checkout "$DEVELOP_BRANCH" 2>/dev/null || {
# 本地没有 develop 分支,从 main 创建
info "本地没有 $DEVELOP_BRANCH 分支,从 $MAIN_BRANCH 创建..."
git checkout "$MAIN_BRANCH"
git pull origin "$MAIN_BRANCH"
git checkout -b "$DEVELOP_BRANCH"
}
fi
# 拉取最新 develop
if git remote get-url origin &>/dev/null; then
info "拉取远程 $DEVELOP_BRANCH..."
git pull origin "$DEVELOP_BRANCH" 2>/dev/null || {
# 远程没有 develop,首次推送
warn "远程没有 $DEVELOP_BRANCH 分支,将首次推送"
}
fi
local commit_hash
commit_hash=$(git rev-parse --short HEAD)
ok "已在 $DEVELOP_BRANCH 分支上 (${commit_hash})"
}
cmd_commit() {
local type="${1:-}"
local scope="${2:-}"
local message="${3:-}"
if [ -z "$type" ] || [ -z "$scope" ] || [ -z "$message" ]; then
fail "用法: git-workflow.sh commit <type> <scope> \"<message>\"\n 示例: git-workflow.sh commit feat player \"实现角色跳跃\""
fi
# 校验分支
if [ "$(current_branch)" != "$DEVELOP_BRANCH" ]; then
warn "当前不在 $DEVELOP_BRANCH 分支上 (当前: $(current_branch))"
read -rp "继续提交? [y/N] " confirm
[[ "$confirm" != [yY] ]] && { info "已取消"; return; }
fi
# 暂存所有修改
if ! has_changes; then
warn "没有检测到修改"
return
fi
git add -A
# UE5 二进制文件检测
if has_ue5_binaries; then
warn "检测到 .uasset/.umap 文件,请确保 commit message 清楚描述了变更内容"
fi
# 格式化 commit message
local commit_msg="${type}(${scope}): ${message}"
# subject ≤ 50 字符检查(仅消息部分)
local msg_len=${#commit_msg}
if [ "$msg_len" -gt 72 ]; then
warn "提交消息较长 ($msg_len 字符),建议精简"
fi
git commit -m "$commit_msg"
ok "已提交: $commit_msg"
}
cmd_push() {
local branch
branch=$(current_branch)
info "推送 $branch 到远程..."
# 设置上游分支(首次)
if ! git rev-parse --abbrev-ref "@{upstream}" &>/dev/null; then
git push -u origin "$branch"
else
git push origin "$branch"
fi
ok "已推送 $branch 到远程"
}
cmd_submit() {
local issue_number="${1:-}"
if [ -z "$issue_number" ]; then
fail "用法: git-workflow.sh submit <issue-number>"
fi
info "提交工作 (Issue #$issue_number)..."
# 从 Issue 拉取标题作为 commit 参考
local issue_title=""
issue_title=$($GITEA_COLLAB_CLI --role dev issue get "$issue_number" --json 2>/dev/null | grep -o '"title":"[^"]*"' | head -1 | sed 's/"title":"//;s/"//') || true
# 提交
if has_changes; then
if [ -n "$issue_title" ]; then
info "Issue 标题: $issue_title"
fi
warn "有未提交的修改,请先运行: git-workflow.sh commit <type> <scope> \"<message>\""
return
fi
# 推送
cmd_push
ok "工作已提交到 $DEVELOP_BRANCH (关联 Issue #$issue_number)"
}
cmd_release() {
local milestone_name="${1:-}"
info "准备发布 PR ($DEVELOP_BRANCH → $MAIN_BRANCH)..."
# 确保在 develop 上
if [ "$(current_branch)" != "$DEVELOP_BRANCH" ]; then
fail "请先切换到 $DEVELOP_BRANCH 分支"
fi
# 确保本地最新
git pull origin "$DEVELOP_BRANCH"
# 获取里程碑名称
if [ -z "$milestone_name" ]; then
# 尝试从当前里程碑获取
milestone_name=$($GITEA_PM_CLI --role pm milestone list --state open --json 2>/dev/null | grep -o '"title":"[^"]*"' | head -1 | sed 's/"title":"//;s/"//') || "迭代发布"
if [ -z "$milestone_name" ]; then
milestone_name="迭代发布"
fi
fi
local pr_title="Merge develop → main: ${milestone_name}"
# 构造 PR body(汇总已关闭的 Issue)
local pr_body="## 发布合并\n\n将 \`${DEVELOP_BRANCH}\` 分支的成果合并到 \`${MAIN_BRANCH}\`。\n\n### 里程碑\n${milestone_name}"
# 创建 PR
info "创建 PR: $pr_title"
local pr_result
pr_result=$($GITEA_PM_CLI --role pm pr create \
--base "$MAIN_BRANCH" \
--head "$DEVELOP_BRANCH" \
--title "$pr_title" \
--body "$(echo -e "$pr_body")" \
--json 2>&1) || {
fail "创建 PR 失败: $pr_result"
}
local pr_number
pr_number=$(echo "$pr_result" | grep -o '"number":[0-9]*' | head -1 | sed 's/"number"://') || true
if [ -n "$pr_number" ]; then
ok "PR #$pr_number 已创建: $pr_title"
info "请在 Gitea 审查并合并: http://micecola.top:8418/starmen1265/ChildHood/pulls/$pr_number"
else
ok "PR 已创建,请查看 Gitea"
fi
}
cmd_post_release() {
info "合并后同步..."
# 同步 main
info "同步 $MAIN_BRANCH..."
git checkout "$MAIN_BRANCH"
git pull origin "$MAIN_BRANCH"
ok "$MAIN_BRANCH 已更新"
# 切回 develop 并合并 main(保持 develop 包含 merge commit)
info "同步 $DEVELOP_BRANCH..."
git checkout "$DEVELOP_BRANCH"
git merge "$MAIN_BRANCH" --no-edit
git push origin "$DEVELOP_BRANCH"
ok "$DEVELOP_BRANCH 已同步(包含 $MAIN_BRANCH 的 merge commit)"
local commit_hash
commit_hash=$(git rev-parse --short HEAD)
ok "完成,当前在 $DEVELOP_BRANCH (${commit_hash})"
}
cmd_catchup() {
info "将 develop 跟上最新的 $MAIN_BRANCH..."
if [ "$(current_branch)" != "$DEVELOP_BRANCH" ]; then
fail "请先切换到 $DEVELOP_BRANCH 分支"
fi
git fetch origin
git merge "origin/$MAIN_BRANCH" --no-edit
ok "$DEVELOP_BRANCH 已合并最新 $MAIN_BRANCH"
}
cmd_status() {
local branch
branch=$(current_branch)
local commit_hash
commit_hash=$(git rev-parse --short HEAD)
local changed
changed=$(git status --porcelain | wc -l | tr -d ' ')
echo -e "${BLUE}═══ Git 工作流状态 ═══${NC}"
echo " 当前分支: ${branch} (${commit_hash})"
if [ "$branch" = "$DEVELOP_BRANCH" ]; then
echo -e " ${GREEN}✓ 在 develop 开发分支上${NC}"
elif [ "$branch" = "$MAIN_BRANCH" ]; then
echo -e " ${YELLOW}⚠ 在 main 分支上,请切换到 develop 开始工作${NC}"
else
echo -e " ${YELLOW}⚠ 在非标准分支上: $branch${NC}"
fi
echo " 未提交修改: ${changed} 个文件"
if [ "$changed" -gt 0 ]; then
echo ""
git status --short
fi
# 远程同步状态
if git rev-parse "@{upstream}" &>/dev/null; then
local ahead behind
ahead=$(git rev-list --count "@{upstream}..HEAD" 2>/dev/null || echo "0")
behind=$(git rev-list --count "HEAD..@{upstream}" 2>/dev/null || echo "0")
echo " 远程状态: ahead ${ahead}, behind ${behind}"
fi
}
# ═══════════════════════════════════════════════════════════════
# 入口
# ═══════════════════════════════════════════════════════════════
case "${1:-}" in
ready) cmd_ready ;;
commit) shift; cmd_commit "$@" ;;
push) cmd_push ;;
submit) shift; cmd_submit "$@" ;;
release) shift; cmd_release "$@" ;;
post-release) cmd_post_release ;;
catchup) cmd_catchup ;;
status) cmd_status ;;
*)
echo "git-workflow.sh — ChildHood 项目 Git 工作流自动化"
echo ""
echo "用法: bash .claude/scripts/git-workflow.sh <command> [args...]"
echo ""
echo "命令:"
echo " ready 确保在 develop 上且最新"
echo " commit <type> <scope> \"<msg>\" 格式化提交"
echo " push 推送 develop 到远程"
echo " submit <issue-number> 推送并关联 Issue"
echo " release [milestone-name] 创建 develop→main PR"
echo " post-release 合并后同步 main 和 develop"
echo " catchup 将 develop 合并最新 main"
echo " status 显示当前分支和状态"
echo ""
echo "工作流模型: develop 长期开发分支 → 迭代完成时 PR 合并回 main"
;;
esac
+4 -4
View File
@@ -1,14 +1,14 @@
{
"permissions": {
"allow": [
"Bash(dir:*)",
"Bash(git:*)",
"mcp__web-reader__webReader",
"mcp__zread__get_repo_structure",
"mcp__web-search-prime__webSearchPrime",
"WebSearch",
"Read:*",
"Bash(\"C:/Program Files/Epic Games/UE_5.7/Engine/Build/BatchFiles/Build.bat\":*)"
"Read",
"Edit",
"Write",
"Bash"
]
},
"hooks": {
+37 -1
View File
@@ -148,7 +148,43 @@ node .claude/skills/gitea-collab/gitea.js --role reviewer issue labels <number>
---
## 7. 汇报模板
## 7. PR 查询
非 PM 角色可以查看 PR 状态(创建和合并由 PM 负责):
```bash
# 列出 PR
node .claude/skills/gitea-collab/gitea.js --role <角色名> pr list
# 查看 PR 详情
node .claude/skills/gitea-collab/gitea.js --role <角色名> pr get <number>
```
---
## 8. Git 工作流
所有 agent 在 `develop` 长期开发分支上工作:
```bash
# 开始工作时:确保在 develop 上
bash .claude/scripts/git-workflow.sh ready
# 提交代码/文档
bash .claude/scripts/git-workflow.sh commit <type> <scope> "中文描述"
# 推送到远程
bash .claude/scripts/git-workflow.sh push
# 查看当前状态
bash .claude/scripts/git-workflow.sh status
```
详见 `.claude/refs/GIT-GUIDE-LITE.md` 的"自动化工作流"章节。
---
## 9. 汇报模板
### 策划汇报
+21
View File
@@ -222,6 +222,27 @@ class GiteaAPI {
});
}
// ====== Pull Requests ======
listPullRequests(params = {}) {
return this.request('GET', '/pulls', null, { state: 'open', limit: 50, ...params });
}
getPullRequest(number) {
return this.request('GET', `/pulls/${number}`);
}
createPullRequest(data) {
return this.request('POST', '/pulls', data);
}
mergePullRequest(number, data = {}) {
return this.request('POST', `/pulls/${number}/merge`, {
Do: data.style || 'merge',
...data,
});
}
// ====== 辅助方法 ======
/** 通过名称查找标签,返回 ID(不存在则返回 null) */
+83
View File
@@ -520,6 +520,84 @@ cmd.pipeline = {
},
};
// ──────── pr ────────
cmd.pr = {
async list(api, opts) {
const params = {};
if (opts.options.state) params.state = opts.options.state;
if (opts.options.base) params.base = opts.options.base;
if (opts.options.head) params.head = opts.options.head;
const prs = await api.listPullRequests(params);
out(prs);
if (!opts.options.json) {
outText(`\n🔀 Pull Requests (${prs.length || 0})\n`);
if (!prs || !prs.length) { outText(' (无)'); return; }
table(
['#', '标题', '状态', 'Base', 'Head', '合并'],
prs.map(p => [
p.number,
truncate(p.title, 35),
p.state,
p.base?.ref || '—',
p.head?.ref || '—',
p.merged ? '✅' : '—',
])
);
}
},
async get(api, opts) {
const num = opts.rest[0];
if (!num) { fail('用法: pr get <number>'); return; }
const pr = await api.getPullRequest(num);
out(pr);
if (!opts.options.json) {
outText(`\n🔀 PR #${pr.number}: ${pr.title}`);
outText(` 状态: ${pr.state}`);
outText(` ${pr.base?.ref} ← ${pr.head?.ref}`);
outText(` 合并: ${pr.merged ? '✅ 已合并' : '❌ 未合并'}`);
if (pr.body) {
outText(`\n--- Body ---\n${pr.body.slice(0, 500)}${pr.body.length > 500 ? '…' : ''}`);
}
}
},
async create(api, opts) {
const base = opts.options.base || 'main';
const head = opts.options.head;
const title = opts.options.title;
const body = opts.options.body || '';
if (!head || !title) {
fail('用法: pr create --head <分支> --title "标题" [--base main] [--body "描述"]');
return;
}
const pr = await api.createPullRequest({ base, head, title, body });
out(pr);
if (!opts.options.json) {
ok(`PR #${pr.number} 已创建: ${pr.title}`);
outText(` ${pr.html_url}`);
}
},
async merge(api, opts) {
const num = opts.rest[0];
if (!num) { fail('用法: pr merge <number> [--style merge|squash|rebase]'); return; }
const style = opts.options.style || 'merge';
const pr = await api.getPullRequest(num);
if (pr.merged) { fail(`PR #${num} 已合并,无需重复操作`); return; }
if (pr.mergeable === false) { fail(`PR #${num} 存在冲突,无法自动合并。请先解决冲突。`); return; }
const result = await api.mergePullRequest(num, { style });
out(result);
if (!opts.options.json) ok(`PR #${num} 已合并 (${style})`);
},
};
// ──────── test ────────
cmd.test = {
@@ -682,6 +760,11 @@ Gitea CLI — 项目管理工具
pipeline status 查看当前流水线进度
pipeline update <#> 更新 Pipeline 字段 --field <阶段|当前迭代> --value <值>
pr list 列出 Pull Requests [--state open|closed|all] [--base <分支>] [--head <分支>]
pr get <#> 查看 PR 详情
pr create 创建 PR --head <分支> --title "标题" [--base main] [--body "描述"]
pr merge <#> 合并 PR [--style merge|squash|rebase]
test 验证所有 API 接口
角色自动映射:
+30 -1
View File
@@ -218,7 +218,36 @@ Pipeline Tracker 是一个 Meta Issue,仅记录**无法从 Issue/Milestone 推
---
## 8. 标签体系
## 8. PR 操作(发布合并)
迭代完成时,PM 负责创建 develop → main 的 PR:
```bash
# 列出当前 PR
node .claude/skills/gitea-pm/gitea.js --role pm pr list
# 创建发布 PR
bash .claude/scripts/git-workflow.sh release "迭代名称"
# 或手动创建
node .claude/skills/gitea-pm/gitea.js --role pm pr create \
--base main --head develop \
--title "Merge develop → main: 迭代名称" \
--body "发布描述"
# 查看 PR 详情
node .claude/skills/gitea-pm/gitea.js --role pm pr get <number>
# 合并 PR(用户审查确认后)
node .claude/skills/gitea-pm/gitea.js --role pm pr merge <number> --style merge
# 合并后同步
bash .claude/scripts/git-workflow.sh post-release
```
---
## 9. 标签体系
从 `gitea-config.json` 的 `labels` 字段定义。`label init` 命令从此配置创建标签。
+21
View File
@@ -222,6 +222,27 @@ class GiteaAPI {
});
}
// ====== Pull Requests ======
listPullRequests(params = {}) {
return this.request('GET', '/pulls', null, { state: 'open', limit: 50, ...params });
}
getPullRequest(number) {
return this.request('GET', `/pulls/${number}`);
}
createPullRequest(data) {
return this.request('POST', '/pulls', data);
}
mergePullRequest(number, data = {}) {
return this.request('POST', `/pulls/${number}/merge`, {
Do: data.style || 'merge', // merge | squash | rebase
...data,
});
}
// ====== 辅助方法 ======
/** 通过名称查找标签,返回 ID(不存在则返回 null) */
+86
View File
@@ -520,6 +520,87 @@ cmd.pipeline = {
},
};
// ──────── pr ────────
cmd.pr = {
async list(api, opts) {
const params = {};
if (opts.options.state) params.state = opts.options.state;
if (opts.options.base) params.base = opts.options.base;
if (opts.options.head) params.head = opts.options.head;
const prs = await api.listPullRequests(params);
out(prs);
if (!opts.options.json) {
outText(`\n🔀 Pull Requests (${prs.length || 0})\n`);
if (!prs || !prs.length) { outText(' (无)'); return; }
table(
['#', '标题', '状态', 'Base', 'Head', '合并'],
prs.map(p => [
p.number,
truncate(p.title, 35),
p.state,
p.base?.ref || '—',
p.head?.ref || '—',
p.merged ? '✅' : '—',
])
);
}
},
async get(api, opts) {
const num = opts.rest[0];
if (!num) { fail('用法: pr get <number>'); return; }
const pr = await api.getPullRequest(num);
out(pr);
if (!opts.options.json) {
outText(`\n🔀 PR #${pr.number}: ${pr.title}`);
outText(` 状态: ${pr.state}`);
outText(` ${pr.base?.ref} ← ${pr.head?.ref}`);
outText(` 合并: ${pr.merged ? '✅ 已合并' : '❌ 未合并'}`);
outText(` 创建: ${pr.created_at?.slice(0, 10)}`);
outText(` 更新: ${pr.updated_at?.slice(0, 10)}`);
if (pr.body) {
outText(`\n--- Body ---\n${pr.body.slice(0, 500)}${pr.body.length > 500 ? '…' : ''}`);
}
}
},
async create(api, opts) {
const base = opts.options.base || 'main';
const head = opts.options.head;
const title = opts.options.title;
const body = opts.options.body || '';
if (!head || !title) {
fail('用法: pr create --head <分支> --title "标题" [--base main] [--body "描述"]');
return;
}
const pr = await api.createPullRequest({ base, head, title, body });
out(pr);
if (!opts.options.json) {
ok(`PR #${pr.number} 已创建: ${pr.title}`);
outText(` ${pr.html_url}`);
}
},
async merge(api, opts) {
const num = opts.rest[0];
if (!num) { fail('用法: pr merge <number> [--style merge|squash|rebase]'); return; }
const style = opts.options.style || 'merge';
// 先检查 PR 状态
const pr = await api.getPullRequest(num);
if (pr.merged) { fail(`PR #${num} 已合并,无需重复操作`); return; }
if (pr.mergeable === false) { fail(`PR #${num} 存在冲突,无法自动合并。请先解决冲突。`); return; }
const result = await api.mergePullRequest(num, { style });
out(result);
if (!opts.options.json) ok(`PR #${num} 已合并 (${style})`);
},
};
// ──────── test ────────
cmd.test = {
@@ -682,6 +763,11 @@ Gitea CLI — 项目管理工具
pipeline status 查看当前流水线进度
pipeline update <#> 更新 Pipeline 字段 --field <阶段|当前迭代> --value <值>
pr list 列出 Pull Requests [--state open|closed|all] [--base <分支>] [--head <分支>]
pr get <#> 查看 PR 详情
pr create 创建 PR --head <分支> --title "标题" [--base main] [--body "描述"]
pr merge <#> 合并 PR [--style merge|squash|rebase]
test 验证所有 API 接口
角色自动映射:
+27 -16
View File
@@ -42,14 +42,14 @@ tools/world-sketcher/
## 画笔颜色 → 材质映射
| 画笔 | 颜色 | 地形 | UE 材质 |
|------|------|------|---------|
| 道路 Road | `#909090` 灰 | 道路 | MI_DevGray |
| 建筑 Building | `#E07020` 橙 | 建筑物 | MI_DevOrange |
| 水域 Water | `#40A0D0` 天蓝 | 河流/水塘 | MI_DevSkyBlue |
| 植被 Greenery | `#40B040` 绿 | 树木/草地 | MI_DevGreen |
| 不可通行 Blocked | `#A07040` 棕 | 山体/围墙 | MI_DevBrown |
| 区域 Zone | `#D04040` 红 | 区域划分 | MI_DevRed |
| 画笔 | 颜色 | 地形 | UE 材质 | 默认高度 |
|------|------|------|---------|---------|
| 道路 Road | `#909090` 灰 | 道路 | MI_DevGray | — |
| 建筑 Building | `#E07020` 橙 | 建筑物 | MI_DevOrange | 3m |
| 水域 Water | `#40A0D0` 天蓝 | 河流/水塘 | MI_DevSkyBlue | — |
| 植被 Greenery | `#40B040` 绿 | 树木/草地 | MI_DevGreen | 2m |
| 不可通行 Blocked | `#A07040` 棕 | 山体/围墙 | MI_DevBrown | 3m |
| 区域 Zone | `#D04040` 红 | 区域划分 | MI_DevRed | 3m |
---
@@ -71,12 +71,15 @@ tools/world-sketcher/
5. 进入 L2 后用建筑、道路等画笔细化区域内部
6. 完成后点"保存全部",将文件保存到 `output/` 目录
### 步骤 3:AI 分析草图
### 步骤 3:AI 读取布局数据
1. 读取 PNG 草图(使用 vision 分析)
2. 读取 JSON 伴随数据(scale、elements、层级关系)
3. 交叉参考场景设计案 `场景-v<XX>-<名称>.md` 的氛围描述
4. 产出布局解读文档
**数据源以 JSON 为准,不依赖 PNG 像素分析。**
1. 读取 JSON 布局数据(`*-scene.json`):elements(含 `height_m`、`connections`)、regions(含 `bounds_world_m`)、层级关系
2. 交叉参考场景设计案 `场景-v<XX>-<名称>.md` 的氛围描述,将 JSON 区域与场景设计案的区域描述建立映射
3. 如需视觉确认,可参考预览 PNG(非必须,仅用于核对区域形状)
> **说明**:PNG 文件仍保存在 ZIP 中用于保存/加载画布,但 AI 分析流程以结构化 JSON 为准。
### 步骤 4:产出布局解读文档
@@ -99,8 +102,8 @@ tools/world-sketcher/
## L1 场景总览
### 识别区域
| 区域编号 | 名称 | 推测坐标范围 (cm) | 推测尺寸 | 地形类型 | 标识色建议 |
|---------|------|-------------------|---------|---------|-----------|
| 区域编号 | 名称 | 推测坐标范围 (cm) | 推测尺寸 | 地形类型 | 高度 (m) | 连通到 | 标识色建议 |
|---------|------|-------------------|---------|---------|---------|--------|-----------|
### 识别道路/河流
| 编号 | 名称 | 类型 | 走向 | 长度 |
@@ -111,7 +114,7 @@ tools/world-sketcher/
## L2 区域详图(如有)
### <区域名>
| 编号 | 名称 | 类型 | 推测坐标 | 尺寸 |
| 编号 | 名称 | 类型 | 推测坐标 | 尺寸 | 高度 (m) | 连通到 |
## 待确认问题
1. ...
@@ -120,6 +123,12 @@ tools/world-sketcher/
- ...
```
**数据来源说明**:
- 区域坐标、尺寸:来自 JSON 中 `regions[].bounds_world_m`,乘以 100 转为 cm
- 高度:来自 JSON 中 `elements[].height_m`,直接采用(无需 AI 推断)
- 连通关系:来自 JSON 中 `elements[].connections`,直接采用(无需 AI 推断)
- 地形类型和标识色:来自 JSON 中 `elements[].type` → brush_legend 映射
### 步骤 5:用户确认
将解读文档呈现给用户,等待用户确认或修正。确认后,该文档作为关卡执行案的前置输入。
@@ -143,3 +152,5 @@ tools/world-sketcher/
- 坐标系与 `zone_config.py` 一致:原点西南角,X+ 东,Y+ 北,单位 cm
- AI 解读时如有不确定之处,**必须**列入"待确认问题",不要自行假设
- L3 建筑内部本期不实现,仅预留 `has_interior` 标记
- **高度 (`height_m`)**:由用户在双击命名时输入,AI 直接采用,不做推断
- **连通关系 (`connections`)**:由用户在双击命名时勾选,双向同步,AI 直接采用
+5
View File
@@ -111,6 +111,11 @@ scripts/.gitea-tokens.json
__pycache__/
*.pyc
# ==============================
# AI 生图工具 — API 密钥
# ==============================
ai-art-research/.env
# ==============================
# 其他
# ==============================
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -25,6 +25,7 @@ public class ChildHood : ModuleRules
PublicIncludePaths.AddRange(new string[] {
"ChildHood",
"ChildHood/Interaction",
"ChildHood/Variant_Platforming",
"ChildHood/Variant_Platforming/Animation",
"ChildHood/Variant_Combat",
+96 -30
View File
@@ -11,60 +11,79 @@
#include "EnhancedInputSubsystems.h"
#include "InputActionValue.h"
#include "ChildHood.h"
#include "Interaction/InteractionManager.h"
AChildHoodCharacter::AChildHoodCharacter()
{
// Set size for collision capsule
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
// Don't rotate when the controller rotates. Let that just affect the camera.
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
// Configure character movement
GetCharacterMovement()->bOrientRotationToMovement = true;
GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f);
// Note: For faster iteration times these variables, and many more, can be tweaked in the Character Blueprint
// instead of recompiling to adjust them
GetCharacterMovement()->JumpZVelocity = 500.f;
GetCharacterMovement()->AirControl = 0.35f;
GetCharacterMovement()->MaxWalkSpeed = 500.f;
GetCharacterMovement()->MinAnalogWalkSpeed = 20.f;
GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;
GetCharacterMovement()->BrakingDecelerationFalling = 1500.0f;
// Configure character movement — v0.1 player controller tech design parameters
auto* CMC = GetCharacterMovement();
CMC->bOrientRotationToMovement = true; // 角色朝移动方向旋转
CMC->RotationRate = FRotator(0.0f, 720.0f, 0.0f); // 转向速度 720°/s(远快于鼠标 150°/s)
CMC->MaxWalkSpeed = 450.0f; // 跑步速度 4.5 m/s
CMC->MaxAcceleration = 2250.0f; // 起步加速 0.2s (450/0.2)
CMC->BrakingDecelerationWalking = 1500.0f; // 停止减速 0.3s (450/0.3)
CMC->BrakingDecelerationFalling = 1500.0f;
CMC->MinAnalogWalkSpeed = 20.f;
// Create a camera boom (pulls in towards the player if there is a collision)
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 400.0f;
CameraBoom->TargetArmLength = 350.0f; // 摄像机距离 3.5m
CameraBoom->SocketOffset = FVector(0.f, 0.f, 80.f); // 摄像机高度 0.8m(头部上方)
CameraBoom->bUsePawnControlRotation = true;
// 摄像机软跟随
CameraBoom->bEnableCameraLag = true;
CameraBoom->CameraLagSpeed = 6.0f;
CameraBoom->bEnableCameraRotationLag = false;
// 摄像机碰撞处理
CameraBoom->bDoCollisionTest = true;
CameraBoom->ProbeSize = 12.0f;
CameraBoom->ProbeChannel = ECC_Camera;
// Create a follow camera
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
FollowCamera->bUsePawnControlRotation = false;
// Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character)
// are set in the derived blueprint asset named ThirdPersonCharacter (to avoid direct content references in C++)
// 创建交互管理器组件
InteractionManager = CreateDefaultSubobject<UInteractionManager>(TEXT("InteractionManager"));
}
void AChildHoodCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
// Set up action bindings
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent)) {
// Jumping
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
// Moving
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AChildHoodCharacter::Move);
// Looking (gamepad + mouse)
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AChildHoodCharacter::Look);
EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &AChildHoodCharacter::Look);
// Looking
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AChildHoodCharacter::Look);
// Interaction — delegates to InteractionManager
if (InteractAction)
{
EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Started, this, &AChildHoodCharacter::OnInteract);
}
if (CancelAction)
{
EnhancedInputComponent->BindAction(CancelAction, ETriggerEvent::Started, this, &AChildHoodCharacter::OnCancel);
}
if (SwitchInteractAction)
{
EnhancedInputComponent->BindAction(SwitchInteractAction, ETriggerEvent::Started, this, &AChildHoodCharacter::OnSwitchInteract);
}
}
else
{
@@ -90,8 +109,42 @@ void AChildHoodCharacter::Look(const FInputActionValue& Value)
DoLook(LookAxisVector.X, LookAxisVector.Y);
}
void AChildHoodCharacter::OnInteract(const FInputActionValue& Value)
{
if (UInteractionManager* IM = GetInteractionManager())
{
// E 键双重功能:Free 发起交互,Active 结束交互
IM->BeginInteract();
IM->CancelInteract();
}
}
void AChildHoodCharacter::OnCancel(const FInputActionValue& Value)
{
if (UInteractionManager* IM = GetInteractionManager())
{
IM->CancelInteract();
}
}
void AChildHoodCharacter::OnSwitchInteract(const FInputActionValue& Value)
{
if (UInteractionManager* IM = GetInteractionManager())
{
float DirectionValue = Value.Get<float>();
int32 Direction = (DirectionValue >= 0.f) ? 1 : -1;
IM->SwitchTarget(Direction);
}
}
void AChildHoodCharacter::DoMove(float Right, float Forward)
{
// 交互中屏蔽移动
if (bIsInteracting)
{
return;
}
if (GetController() != nullptr)
{
// find out which way is forward
@@ -101,10 +154,10 @@ void AChildHoodCharacter::DoMove(float Right, float Forward)
// get forward vector
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
// get right vector
// get right vector
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
// add movement
// add movement
AddMovementInput(ForwardDirection, Forward);
AddMovementInput(RightDirection, Right);
}
@@ -120,14 +173,27 @@ void AChildHoodCharacter::DoLook(float Yaw, float Pitch)
}
}
void AChildHoodCharacter::DoJumpStart()
// ============================================================================
// 交互系统方法
// ============================================================================
UInteractionManager* AChildHoodCharacter::GetInteractionManager() const
{
// signal the character to jump
Jump();
return InteractionManager;
}
void AChildHoodCharacter::DoJumpEnd()
void AChildHoodCharacter::SetInteracting(bool bValue)
{
// signal the character to stop jumping
StopJumping();
bIsInteracting = bValue;
}
bool AChildHoodCharacter::IsInteracting() const
{
return bIsInteracting;
}
bool AChildHoodCharacter::IsRunning() const
{
return GetVelocity().SizeSquared() > 100.f // 速度 > 10 cm/s 阈值
&& GetCharacterMovement()->IsMovingOnGround();
}
+47 -15
View File
@@ -10,6 +10,7 @@
class USpringArmComponent;
class UCameraComponent;
class UInputAction;
class UInteractionManager;
struct FInputActionValue;
DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All);
@@ -30,12 +31,8 @@ class AChildHoodCharacter : public ACharacter
/** Follow camera */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
UCameraComponent* FollowCamera;
protected:
/** Jump Input Action */
UPROPERTY(EditAnywhere, Category="Input")
UInputAction* JumpAction;
protected:
/** Move Input Action */
UPROPERTY(EditAnywhere, Category="Input")
@@ -49,10 +46,45 @@ protected:
UPROPERTY(EditAnywhere, Category="Input")
UInputAction* MouseLookAction;
/** Interact Input Action (E key) — delegates to interaction system */
UPROPERTY(EditAnywhere, Category="Input")
UInputAction* InteractAction;
/** Cancel Input Action (ESC key) — delegates to interaction system */
UPROPERTY(EditAnywhere, Category="Input")
UInputAction* CancelAction;
/** Switch Interact Target Input Action (Tab / Mouse Wheel) — delegates to interaction system */
UPROPERTY(EditAnywhere, Category="Input")
UInputAction* SwitchInteractAction;
/** 交互管理器组件 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Interaction", meta = (AllowPrivateAccess = "true"))
UInteractionManager* InteractionManager;
/** Whether the character is currently in an interaction */
bool bIsInteracting = false;
public:
/** Constructor */
AChildHoodCharacter();
AChildHoodCharacter();
/** 获取交互管理器组件 */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Interaction")
UInteractionManager* GetInteractionManager() const;
/** Set interaction state — called by interaction system */
UFUNCTION(BlueprintCallable, Category = "Interaction")
void SetInteracting(bool bValue);
/** Query: is the character currently interacting? */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Interaction")
bool IsInteracting() const;
/** Query: is the character currently running (moving on ground)? */
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Interaction")
bool IsRunning() const;
protected:
@@ -67,6 +99,15 @@ protected:
/** Called for looking input */
void Look(const FInputActionValue& Value);
/** Called for interact input (E) — delegates to InteractionManager */
void OnInteract(const FInputActionValue& Value);
/** Called for cancel input (ESC) — delegates to InteractionManager */
void OnCancel(const FInputActionValue& Value);
/** Called for switch interact target input (Tab/Wheel) — delegates to InteractionManager */
void OnSwitchInteract(const FInputActionValue& Value);
public:
/** Handles move inputs from either controls or UI interfaces */
@@ -77,14 +118,6 @@ public:
UFUNCTION(BlueprintCallable, Category="Input")
virtual void DoLook(float Yaw, float Pitch);
/** Handles jump pressed inputs from either controls or UI interfaces */
UFUNCTION(BlueprintCallable, Category="Input")
virtual void DoJumpStart();
/** Handles jump pressed inputs from either controls or UI interfaces */
UFUNCTION(BlueprintCallable, Category="Input")
virtual void DoJumpEnd();
public:
/** Returns CameraBoom subobject **/
@@ -93,4 +126,3 @@ public:
/** Returns FollowCamera subobject **/
FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
};
@@ -0,0 +1,30 @@
// ChildHood Project - 跨关卡持久数据持有者 实现
#include "ChildHoodGameInstance.h"
bool UChildHoodGameInstance::IsItemCollected(FName ItemId) const
{
return CollectedItemIDs.Contains(ItemId);
}
void UChildHoodGameInstance::MarkItemCollected(FName ItemId)
{
CollectedItemIDs.Add(ItemId);
}
bool UChildHoodGameInstance::IsDoorOpen(FName DoorId) const
{
const bool* Found = DoorStates.Find(DoorId);
return Found ? *Found : false;
}
void UChildHoodGameInstance::SetDoorOpen(FName DoorId, bool bIsOpen)
{
DoorStates.Add(DoorId, bIsOpen);
}
void UChildHoodGameInstance::ResetAll()
{
CollectedItemIDs.Empty();
DoorStates.Empty();
}
+56
View File
@@ -0,0 +1,56 @@
// ChildHood Project - 跨关卡持久数据持有者
// 对应技术方案: docs/technical/交互系统-技术方案.md 第12节(跨场景持久化设计)
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "ChildHoodGameInstance.generated.h"
/**
* UChildHoodGameInstance
*
* 跨关卡持久数据持有者。UE5 的 OpenLevel() 会销毁整个旧 World(包括
* Character 及其所有 UActorComponent),因此需要将持久数据与运行时行为分离。
*
* v0.1 为单场景原型,此 GameInstance 以空数据初始化。
* 后续多场景版本只需在 Project Settings 中将 GameInstance Class 设为此类即可。
*/
UCLASS()
class UChildHoodGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
// === 物品消耗状态 ===
/** 查询指定物品是否已被收集(跨场景持久) */
bool IsItemCollected(FName ItemId) const;
/** 标记指定物品为已收集(写入持久层) */
void MarkItemCollected(FName ItemId);
// === 门状态 ===
/** 查询指定门的开关状态(跨场景持久),未记录的门默认为关闭 */
bool IsDoorOpen(FName DoorId) const;
/** 设置指定门的开关状态(写入持久层) */
void SetDoorOpen(FName DoorId, bool bIsOpen);
// === 重置 ===
/** 重置所有持久数据(新游戏时调用) */
void ResetAll();
private:
/** 已收集物品ID集合 */
UPROPERTY()
TSet<FName> CollectedItemIDs;
/** 门开关状态映射(true=打开,false=关闭) */
UPROPERTY()
TMap<FName, bool> DoorStates;
};
@@ -13,6 +13,19 @@ void AChildHoodPlayerController::BeginPlay()
{
Super::BeginPlay();
// C3: 设置摄像机俯仰角限制(垂直旋转 +45°/-30°)
if (PlayerCameraManager)
{
PlayerCameraManager->ViewPitchMin = -30.0f; // 向下看最多 30°
PlayerCameraManager->ViewPitchMax = 45.0f; // 向上看最多 45°
}
// C1: 初始俯角 -15°(摄像机默认略向下看向角色)
if (GetControlRotation().Pitch == 0.f)
{
SetControlRotation(FRotator(-15.0f, GetControlRotation().Yaw, GetControlRotation().Roll));
}
// only spawn touch controls on local player controllers
if (ShouldUseTouchControls() && IsLocalPlayerController())
{
@@ -0,0 +1,47 @@
// ChildHood Project - 通用交互物 Actor 基类 实现
#include "Interaction/InteractableActor.h"
#include "Interaction/InteractableComponent.h"
#include "Components/StaticMeshComponent.h"
// ============================================================================
// 构造函数
// ============================================================================
AInteractableActor::AInteractableActor()
{
PrimaryActorTick.bCanEverTick = false;
// 创建 Mesh 为根组件
MeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComp"));
RootComponent = MeshComp;
MeshComp->SetCollisionProfileName(TEXT("BlockAll"));
// 创建交互组件(UActorComponent 不需要 SetupAttachment,数据驱动型组件)
InteractableComp = CreateDefaultSubobject<UInteractableComponent>(TEXT("InteractableComp"));
}
// ============================================================================
// 交互回调
// ============================================================================
void AInteractableActor::OnInteractStarted_Implementation(UInteractableComponent* Comp)
{
if (!Comp || !MeshComp) return;
const FInteractableData& Data = Comp->GetInteractableData();
// 门类型:根据开关状态旋转 Mesh
if (Data.Type == EInteractType::Operate && Data.OperateSubType == EOperateSubType::Door)
{
FRotator CurrentRot = MeshComp->GetRelativeRotation();
float TargetYaw = Comp->IsDoorOpen() ? 90.0f : 0.0f;
MeshComp->SetRelativeRotation(FRotator(CurrentRot.Pitch, TargetYaw, CurrentRot.Roll));
}
}
void AInteractableActor::OnInteractEnded_Implementation(UInteractableComponent* Comp)
{
// 默认空实现,子类可覆盖
}
@@ -0,0 +1,69 @@
// ChildHood Project - 通用交互物 Actor 基类
// 对应技术方案: docs/technical/交互系统-技术方案.md 第3节(类设计)
// 职责:持有 Mesh + InteractableComponent,提供可被子类覆盖的交互回调
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "InteractableActor.generated.h"
class UInteractableComponent;
/**
* AInteractableActor
*
* 通用交互物 Actor 基类。场景中的每个交互物(冰箱、门、NPC 等)
* 均为该类的实例或其子类的实例。
*
* 组成:
* - UStaticMeshComponent* MeshComp(根组件,承载可视模型)
* - UInteractableComponent* InteractableComp(交互逻辑与数据)
*
* 子类可覆盖 OnInteractStarted / OnInteractEnded 以实现类型特定行为,
* 如门开关时的 Mesh 旋转、拾取物的消失效果等。
*/
UCLASS()
class AInteractableActor : public AActor
{
GENERATED_BODY()
public:
AInteractableActor();
// === 组件访问 ===
/** 获取静态网格组件 */
UFUNCTION(BlueprintCallable, Category = "Interact")
UStaticMeshComponent* GetMeshComp() const { return MeshComp; }
/** 获取交互组件 */
UFUNCTION(BlueprintCallable, Category = "Interact")
UInteractableComponent* GetInteractableComp() const { return InteractableComp; }
// === 可被子类覆盖的交互回调 ===
/**
* 交互开始时调用(门开关切换、状态恢复时也调用)。
* 默认实现:门类型根据开关状态对 Mesh 做 Yaw 旋转(开门+90度/关门0度)。
*/
UFUNCTION(BlueprintNativeEvent, Category = "Interact")
void OnInteractStarted(UInteractableComponent* Comp);
virtual void OnInteractStarted_Implementation(UInteractableComponent* Comp);
/** 交互结束时调用。默认为空实现,子类可覆盖。 */
UFUNCTION(BlueprintNativeEvent, Category = "Interact")
void OnInteractEnded(UInteractableComponent* Comp);
virtual void OnInteractEnded_Implementation(UInteractableComponent* Comp);
protected:
/** 交互物的可视模型 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* MeshComp;
/** 交互物的交互组件(数据 + 检测 + 状态) */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
UInteractableComponent* InteractableComp;
};
@@ -0,0 +1,221 @@
// ChildHood Project - 可交互物组件 实现
#include "Interaction/InteractableComponent.h"
#include "Interaction/InteractableActor.h"
#include "Interaction/InteractionManager.h"
#include "ChildHoodGameInstance.h"
#include "ChildHood.h"
#include "Components/SphereComponent.h"
#include "Engine/World.h"
#include "GameFramework/Character.h"
#include "Kismet/GameplayStatics.h"
// ============================================================================
// 构造函数
// ============================================================================
UInteractableComponent::UInteractableComponent()
{
// 不需要 Tick,检测由碰撞事件驱动
PrimaryComponentTick.bCanEverTick = false;
}
// ============================================================================
// 生命周期
// ============================================================================
void UInteractableComponent::BeginPlay()
{
Super::BeginPlay();
AActor* Owner = GetOwner();
if (!Owner)
{
UE_LOG(LogChildHood, Warning, TEXT("InteractableComponent: 无 Owner,跳过初始化"));
return;
}
// --- 创建检测球 ---
DetectionSphere = NewObject<USphereComponent>(this, USphereComponent::StaticClass(), TEXT("DetectionSphere"));
if (DetectionSphere)
{
DetectionSphere->SetupAttachment(Owner->GetRootComponent());
DetectionSphere->RegisterComponent();
DetectionSphere->SetSphereRadius(Data.TriggerRadius);
DetectionSphere->SetCollisionProfileName(TEXT("OverlapAllDynamic"));
// 关闭物理模拟,仅用于 Overlap 检测
DetectionSphere->SetGenerateOverlapEvents(true);
DetectionSphere->SetCanEverAffectNavigation(false);
// 绑定碰撞事件
DetectionSphere->OnComponentBeginOverlap.AddDynamic(this, &UInteractableComponent::OnDetectionOverlapBegin);
DetectionSphere->OnComponentEndOverlap.AddDynamic(this, &UInteractableComponent::OnDetectionOverlapEnd);
}
else
{
UE_LOG(LogChildHood, Error, TEXT("InteractableComponent: DetectionSphere 创建失败"));
}
// --- 恢复跨场景持久状态 ---
if (UChildHoodGameInstance* GI = GetWorld()->GetGameInstance<UChildHoodGameInstance>())
{
RestorePersistentState(GI);
}
UE_LOG(LogChildHood, Log, TEXT("InteractableComponent Init: %s (Type=%s, Radius=%f)"),
Data.DisplayName.IsEmpty() ? TEXT("(unnamed)") : *Data.DisplayName,
*UEnum::GetValueAsString(Data.Type),
Data.TriggerRadius);
}
// ============================================================================
// 提示文本
// ============================================================================
FString UInteractableComponent::GetPromptText() const
{
FString Verb = Data.InteractVerb;
// 门类型:根据当前开关状态选择动词
if (Data.Type == EInteractType::Operate && Data.OperateSubType == EOperateSubType::Door)
{
Verb = bDoorIsOpen ? Data.InteractVerbAlt : Data.InteractVerb;
}
return FString::Printf(TEXT("[E] %s %s"), *Verb, *Data.DisplayName);
}
// ============================================================================
// 消耗管理
// ============================================================================
void UInteractableComponent::Consume()
{
bIsConsumed = true;
// 禁用检测球,不再触发 Overlap
if (DetectionSphere)
{
DetectionSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
// 写入持久层
if (UWorld* World = GetWorld())
{
if (UChildHoodGameInstance* GI = World->GetGameInstance<UChildHoodGameInstance>())
{
GI->MarkItemCollected(Data.ItemId);
}
}
UE_LOG(LogChildHood, Log, TEXT("InteractableComponent 已消耗: %s"), *Data.ItemId.ToString());
}
// ============================================================================
// 门状态管理
// ============================================================================
void UInteractableComponent::ToggleDoor()
{
bDoorIsOpen = !bDoorIsOpen;
UE_LOG(LogChildHood, Log, TEXT("InteractableComponent 门状态切换: %s → %s"),
*Data.ItemId.ToString(),
bDoorIsOpen ? TEXT("开") : TEXT("关"));
}
// ============================================================================
// 位置查询
// ============================================================================
FVector UInteractableComponent::GetWorldLocation() const
{
AActor* Owner = GetOwner();
return Owner ? Owner->GetActorLocation() : FVector::ZeroVector;
}
// ============================================================================
// 跨场景持久状态恢复
// ============================================================================
void UInteractableComponent::RestorePersistentState(UChildHoodGameInstance* GI)
{
if (!GI) return;
// oneShot 物品:已消耗 → 隐藏 + 禁用检测
if (Data.bOneShot && GI->IsItemCollected(Data.ItemId))
{
bIsConsumed = true;
if (DetectionSphere)
{
DetectionSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
if (AInteractableActor* Owner = Cast<AInteractableActor>(GetOwner()))
{
Owner->SetActorHiddenInGame(true);
}
UE_LOG(LogChildHood, Log, TEXT("RestorePersistentState: 已消耗物品隐藏: %s"), *Data.ItemId.ToString());
}
// 门:恢复开关状态
if (Data.Type == EInteractType::Operate && Data.OperateSubType == EOperateSubType::Door)
{
bDoorIsOpen = GI->IsDoorOpen(Data.ItemId);
// 通知 Actor 应用视觉状态(Mesh 旋转)
if (AInteractableActor* Owner = Cast<AInteractableActor>(GetOwner()))
{
Owner->OnInteractStarted(this);
}
UE_LOG(LogChildHood, Log, TEXT("RestorePersistentState: 门状态恢复: %s → %s"),
*Data.ItemId.ToString(),
bDoorIsOpen ? TEXT("开") : TEXT("关"));
}
}
// ============================================================================
// 碰撞回调
// ============================================================================
void UInteractableComponent::OnDetectionOverlapBegin(UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult& SweepResult)
{
if (!OtherActor) return;
// 只响应 Character(玩家)的进入
ACharacter* Character = Cast<ACharacter>(OtherActor);
if (!Character) return;
// 已消耗的交互物不再注册
if (bIsConsumed) return;
UE_LOG(LogChildHood, Verbose, TEXT("检测到角色进入范围: %s ← %s"),
*Data.DisplayName, *OtherActor->GetName());
// 获取角色的 InteractionManager 并注册自己
if (UInteractionManager* IM = Character->FindComponentByClass<UInteractionManager>())
{
IM->RegisterInteractable(this);
}
}
void UInteractableComponent::OnDetectionOverlapEnd(UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex)
{
if (!OtherActor) return;
ACharacter* Character = Cast<ACharacter>(OtherActor);
if (!Character) return;
UE_LOG(LogChildHood, Verbose, TEXT("角色离开范围: %s ← %s"),
*Data.DisplayName, *OtherActor->GetName());
// 获取角色的 InteractionManager 并注销自己
if (UInteractionManager* IM = Character->FindComponentByClass<UInteractionManager>())
{
IM->UnregisterInteractable(this);
}
}
@@ -0,0 +1,101 @@
// ChildHood Project - 可交互物组件
// 对应技术方案: docs/technical/交互系统-技术方案.md 第3节(类设计)+ 第12节(跨场景持久化)
// 职责:单个交互物配置数据、触发区域、运行时状态、跨场景持久状态恢复
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "InteractionTypes.h"
#include "InteractableComponent.generated.h"
class UChildHoodGameInstance;
class USphereComponent;
/**
* UInteractableComponent
*
* 挂载在 AInteractableActor(或其子类)上的组件。
* 持有该交互物的全部配置数据(FInteractableData)、运行时触发检测球、
* 消耗/门状态管理,以及跨场景持久状态恢复。
*/
UCLASS(ClassGroup=(Interaction), meta=(BlueprintSpawnableComponent))
class UInteractableComponent : public UActorComponent
{
GENERATED_BODY()
public:
UInteractableComponent();
// === 配置数据 ===
/** 该交互物的全部配置数据,编辑器中按实例独立配置 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact")
FInteractableData Data;
// === 查询接口 ===
/** 获取配置数据引用(只读) */
const FInteractableData& GetInteractableData() const { return Data; }
/**
* 获取提示文本,格式:"[E] 动作词 名称"
* 门类型根据当前开关状态选择动词
*/
FString GetPromptText() const;
/** 该交互物是否已被消耗(oneShot 模式) */
bool IsConsumed() const { return bIsConsumed; }
/** 标记为已消耗,禁用检测球,写入持久层 */
void Consume();
// === 门状态管理 ===
/** 切换门开关状态 */
void ToggleDoor();
/** 门是否处于打开状态 */
bool IsDoorOpen() const { return bDoorIsOpen; }
/** 获取交互物在世界空间的位置 */
FVector GetWorldLocation() const;
/** 从 GameInstance 恢复跨场景持久状态(BeginPlay 中调用) */
void RestorePersistentState(UChildHoodGameInstance* GI);
protected:
virtual void BeginPlay() override;
private:
/** 运行时创建的检测球,用于触发玩家进入/离开交互范围 */
UPROPERTY()
USphereComponent* DetectionSphere;
/** oneShot 物品是否已被消耗 */
bool bIsConsumed = false;
/** 门是否处于打开状态(仅 Operate+Door 类型使用) */
bool bDoorIsOpen = false;
// === 碰撞回调 ===
/** 玩家进入检测范围 */
UFUNCTION()
void OnDetectionOverlapBegin(UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult& SweepResult);
/** 玩家离开检测范围 */
UFUNCTION()
void OnDetectionOverlapEnd(UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex);
};
@@ -0,0 +1,897 @@
// ChildHood Project - 交互管理器核心组件 实现
// 对应技术方案: docs/technical/交互系统-技术方案.md 第6节(行为规则实现)
#include "Interaction/InteractionManager.h"
#include "Interaction/InteractableComponent.h"
#include "Interaction/InteractableActor.h"
#include "Interaction/PushCameraActor.h"
#include "Interaction/InteractionPromptWidget.h"
#include "Interaction/NarrativeTextWidget.h"
#include "ChildHoodCharacter.h"
#include "ChildHoodGameInstance.h"
#include "ChildHood.h"
#include "GameFramework/Character.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/SphereComponent.h"
#include "Engine/World.h"
#include "TimerManager.h"
#include "Kismet/GameplayStatics.h"
#include "Blueprint/WidgetBlueprintLibrary.h"
// 定义交互系统日志类别
DECLARE_LOG_CATEGORY_EXTERN(LogInteraction, Log, All);
DEFINE_LOG_CATEGORY(LogInteraction);
// ============================================================================
// 构造函数
// ============================================================================
UInteractionManager::UInteractionManager()
{
// 默认关闭 Tick,首个交互物注册时开启
PrimaryComponentTick.bCanEverTick = true;
PrimaryComponentTick.bStartWithTickEnabled = false;
}
// ============================================================================
// 生命周期
// ============================================================================
void UInteractionManager::BeginPlay()
{
Super::BeginPlay();
// Spawn 推镜用临时摄像机 Actor(使用专用的 APushCameraActor)
// 随 Character 生命周期,不反复创建/销毁
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = GetOwner();
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
APushCameraActor* SpawnedCamera = GetWorld()->SpawnActor<APushCameraActor>(APushCameraActor::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams);
PushCameraActor = SpawnedCamera;
if (PushCameraActor)
{
PushCameraActor->SetActorHiddenInGame(true);
}
UE_LOG(LogInteraction, Log, TEXT("InteractionManager: BeginPlay 完成, PushCameraActor=%s"),
PushCameraActor ? *PushCameraActor->GetName() : TEXT("nullptr"));
// --- 创建 UI Widget ---
APlayerController* PC = GetWorld()->GetFirstPlayerController();
if (PC)
{
// 创建提示 UI
if (PromptWidgetClass)
{
PromptWidget = CreateWidget<UInteractionPromptWidget>(PC, PromptWidgetClass);
if (PromptWidget)
{
PromptWidget->AddToViewport(10);
}
}
else
{
UE_LOG(LogInteraction, Warning, TEXT("InteractionManager: PromptWidgetClass 未设置,提示 UI 不可用"));
}
// 创建文本底框 UI
if (NarrativeWidgetClass)
{
NarrativeWidget = CreateWidget<UNarrativeTextWidget>(PC, NarrativeWidgetClass);
if (NarrativeWidget)
{
NarrativeWidget->AddToViewport(20);
}
}
else
{
UE_LOG(LogInteraction, Warning, TEXT("InteractionManager: NarrativeWidgetClass 未设置,文本 UI 不可用"));
}
}
}
// ============================================================================
// Tick 驱动
// ============================================================================
void UInteractionManager::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (State == EInteractionState::Free)
{
// Free 状态下更新提示 UI 的屏幕位置
if (PromptWidget && PromptWidget->IsVisible() &&
NearbyInteractables.Num() > 0 && NearbyInteractables.IsValidIndex(SelectedIndex))
{
UInteractableComponent* Selected = NearbyInteractables[SelectedIndex];
if (Selected)
{
UpdatePromptScreenPosition(Selected);
}
}
return;
}
switch (Phase)
{
case EInteractPhase::Enter:
TickEnterPhase(DeltaTime);
break;
case EInteractPhase::AnimLock:
TickAnimLockPhase(DeltaTime);
break;
case EInteractPhase::Active:
// 等待玩家输入,无需 Tick 处理
break;
case EInteractPhase::Exit:
TickExitPhase(DeltaTime);
break;
case EInteractPhase::None:
default:
break;
}
}
// ============================================================================
// 公开方法
// ============================================================================
void UInteractionManager::BeginInteract()
{
// 前置检查:必须 Free 状态
if (State != EInteractionState::Free)
{
UE_LOG(LogInteraction, Verbose, TEXT("BeginInteract: 拒绝,当前状态不是 Free (State=%d)"),
static_cast<int32>(State));
return;
}
// 前置检查:必须有选中的交互物
if (NearbyInteractables.Num() == 0 || !NearbyInteractables.IsValidIndex(SelectedIndex))
{
UE_LOG(LogInteraction, Verbose, TEXT("BeginInteract: 拒绝,没有选中的交互物"));
return;
}
UInteractableComponent* Target = NearbyInteractables[SelectedIndex];
// 前置检查:交互物不能已被消耗
if (!Target || Target->IsConsumed())
{
UE_LOG(LogInteraction, Verbose, TEXT("BeginInteract: 拒绝,交互物已被消耗"));
return;
}
// 进入交互
State = EInteractionState::Interacting;
Phase = EInteractPhase::Enter;
CurrentInteractable = Target;
// 通知 Character 进入交互状态(屏蔽移动)
AChildHoodCharacter* Char = Cast<AChildHoodCharacter>(GetOwner());
if (Char)
{
Char->SetInteracting(true);
}
// 隐藏提示 UI
HidePrompt();
// 重置 Enter 阶段计时器
TurnElapsed = 0.0f;
WaitElapsed = 0.0f;
bPushStarted = false;
const FInteractableData& Data = Target->GetInteractableData();
UE_LOG(LogInteraction, Log, TEXT("BeginInteract: 开始交互 '%s' (Type=%d)"),
*Data.DisplayName, static_cast<int32>(Data.Type));
}
void UInteractionManager::CancelInteract()
{
// 前置检查:必须 Interacting 状态
if (State != EInteractionState::Interacting)
{
return;
}
// AnimLock 期间不可取消
if (Phase == EInteractPhase::AnimLock)
{
UE_LOG(LogInteraction, Verbose, TEXT("CancelInteract: 拒绝,AnimLock 阶段不可取消"));
return;
}
UE_LOG(LogInteraction, Log, TEXT("CancelInteract: 取消/结束交互"));
// 隐藏叙事文本
HideNarrativeText();
// 进入 Exit 阶段
Phase = EInteractPhase::Exit;
// 相机回位
const FInteractableData* Data = CurrentInteractable ? &CurrentInteractable->GetInteractableData() : nullptr;
float ReturnDuration = (Data && Data->bUseCameraPush) ? Data->CameraReturnDuration : 0.3f;
ReturnCamera(ReturnDuration);
// 清除可能残留的计时器
GetWorld()->GetTimerManager().ClearTimer(AnimEventTimerHandle);
GetWorld()->GetTimerManager().ClearTimer(AnimEndTimerHandle);
// 设置退出定时器,回位动画播完后回到 Free
GetWorld()->GetTimerManager().SetTimer(ExitTimerHandle, this, &UInteractionManager::OnExitTimerComplete, ReturnDuration, false);
}
void UInteractionManager::SwitchTarget(int32 Direction)
{
int32 Num = NearbyInteractables.Num();
if (Num == 0)
{
return;
}
// Interacting 状态下忽略切换
if (State != EInteractionState::Free)
{
return;
}
SelectedIndex = (SelectedIndex + Direction + Num) % Num;
UpdatePromptUI();
UE_LOG(LogInteraction, Log, TEXT("SwitchTarget: 切换到 %d/%d"), SelectedIndex, Num);
}
void UInteractionManager::RegisterInteractable(UInteractableComponent* Interactable)
{
if (!Interactable)
{
return;
}
// 去重检查
if (NearbyInteractables.Contains(Interactable))
{
return;
}
NearbyInteractables.Add(Interactable);
// 按水平距离排序(DistSquared2D,避免 sqrt)
AActor* Owner = GetOwner();
if (Owner)
{
FVector OwnerLoc = Owner->GetActorLocation();
NearbyInteractables.Sort([OwnerLoc](const UInteractableComponent& A, const UInteractableComponent& B)
{
float DistA = FVector::DistSquared2D(OwnerLoc, A.GetWorldLocation());
float DistB = FVector::DistSquared2D(OwnerLoc, B.GetWorldLocation());
return DistA < DistB;
});
}
UpdateSelection();
UpdatePromptUI();
// 首个交互物注册时开启 Tick
if (NearbyInteractables.Num() == 1)
{
SetComponentTickEnabled(true);
}
UE_LOG(LogInteraction, Log, TEXT("RegisterInteractable: 注册 '%s', 列表大小=%d"),
*Interactable->GetInteractableData().DisplayName, NearbyInteractables.Num());
}
void UInteractionManager::UnregisterInteractable(UInteractableComponent* Interactable)
{
if (!Interactable)
{
return;
}
bool bRemoved = NearbyInteractables.Remove(Interactable) > 0;
if (!bRemoved)
{
return;
}
// 如果移除的是当前正在交互的交互物,清空引用
if (CurrentInteractable == Interactable)
{
CurrentInteractable = nullptr;
}
if (NearbyInteractables.Num() > 0)
{
UpdateSelection();
UpdatePromptUI();
}
else
{
// 列表为空,隐藏提示
HidePrompt();
// 最后一个注销时关闭 Tick
SetComponentTickEnabled(false);
}
UE_LOG(LogInteraction, Log, TEXT("UnregisterInteractable: 注销 '%s', 列表大小=%d"),
*Interactable->GetInteractableData().DisplayName, NearbyInteractables.Num());
}
// ============================================================================
// Enter 阶段 Tick
// ============================================================================
void UInteractionManager::TickEnterPhase(float DeltaTime)
{
if (!CurrentInteractable)
{
UE_LOG(LogInteraction, Warning, TEXT("TickEnterPhase: CurrentInteractable 为空,强制取消"));
CancelInteract();
return;
}
ACharacter* Char = Cast<ACharacter>(GetOwner());
if (!Char)
{
return;
}
const FInteractableData& Data = CurrentInteractable->GetInteractableData();
// --- 1. 转向插值 ---
TurnElapsed += DeltaTime;
FVector CharLoc = Char->GetActorLocation();
FVector TargetLoc = CurrentInteractable->GetWorldLocation();
FVector DirToTarget = (TargetLoc - CharLoc);
DirToTarget.Z = 0.0f;
FRotator TargetRot = DirToTarget.Rotation();
// 使用 RInterpTo 进行平滑插值,InterpSpeed = 1/TurnDuration
float InterpSpeed = (TurnDuration > 0.f) ? (1.f / TurnDuration) : 10.f;
FRotator NewRot = FMath::RInterpTo(Char->GetActorRotation(), TargetRot, DeltaTime, InterpSpeed);
Char->SetActorRotation(NewRot);
// --- 2. 推镜(一次性发起) ---
if (Data.bUseCameraPush && !bPushStarted)
{
FVector PushPos = CalculatePushPosition(CurrentInteractable);
PushCamera(PushPos, Data.CameraPushDuration);
bPushStarted = true;
}
// --- 3. 判断完成 ---
bool bTurnComplete = TurnElapsed >= TurnDuration;
bool bPushComplete = !Data.bUseCameraPush || bPushStarted;
if (bTurnComplete && bPushComplete)
{
// 推镜后需要额外等待 PushWaitDelay
if (Data.bUseCameraPush && Data.PushWaitDelay > 0.f)
{
WaitElapsed += DeltaTime;
if (WaitElapsed >= Data.PushWaitDelay)
{
TransitionFromEnter();
}
}
else
{
TransitionFromEnter();
}
}
}
// ============================================================================
// AnimLock 阶段 Tick(v0.1 由计时器驱动,此处为空)
// ============================================================================
void UInteractionManager::TickAnimLockPhase(float DeltaTime)
{
// v0.1: AnimLock 由 AnimEventTimer 和 AnimEndTimer 驱动,无需 Tick 处理
}
// ============================================================================
// Exit 阶段 Tick(v0.1 由定时器驱动回 Free)
// ============================================================================
void UInteractionManager::TickExitPhase(float DeltaTime)
{
// v0.1: Exit 阶段由 ExitTimerHandle 定时器驱动回到 Free
// 此处可做相机回位进度检查等,简单方案留空
}
// ============================================================================
// Enter 完成后类型分发
// ============================================================================
void UInteractionManager::TransitionFromEnter()
{
if (!CurrentInteractable)
{
return;
}
const FInteractableData& Data = CurrentInteractable->GetInteractableData();
UE_LOG(LogInteraction, Log, TEXT("TransitionFromEnter: 类型=%d 分发"), static_cast<int32>(Data.Type));
switch (Data.Type)
{
case EInteractType::Examine:
Phase = EInteractPhase::Active;
ShowNarrativeText(PickRandomNarrative(Data.NarrativeTexts));
break;
case EInteractType::Pickup:
Phase = EInteractPhase::AnimLock;
EnterAnimLockPhase();
break;
case EInteractType::Operate:
if (Data.OperateSubType == EOperateSubType::Door)
{
Phase = EInteractPhase::AnimLock;
EnterAnimLockPhase();
}
else if (Data.OperateSubType == EOperateSubType::Search)
{
Phase = EInteractPhase::AnimLock;
EnterAnimLockPhase();
}
break;
case EInteractType::Talk:
Phase = EInteractPhase::Active;
ShowNarrativeText(PickRandomNarrative(Data.NarrativeTexts));
break;
default:
UE_LOG(LogInteraction, Warning, TEXT("TransitionFromEnter: 未知交互类型 %d"), static_cast<int32>(Data.Type));
// 未知类型直接回 Free
CancelInteract();
break;
}
}
// ============================================================================
// AnimLock 阶段
// ============================================================================
void UInteractionManager::EnterAnimLockPhase()
{
if (!CurrentInteractable)
{
return;
}
const FInteractableData& Data = CurrentInteractable->GetInteractableData();
// 计算总动画时长:有蒙太奇用蒙太奇长度,否则用配置的 AnimDuration
float TotalAnimTime = Data.AnimDuration;
if (Data.InteractMontage)
{
TotalAnimTime = Data.InteractMontage->GetPlayLength();
if (TotalAnimTime <= 0.f)
{
TotalAnimTime = Data.AnimDuration;
}
// 播放蒙太奇
ACharacter* Char = Cast<ACharacter>(GetOwner());
if (Char && Char->GetMesh())
{
Char->GetMesh()->GetAnimInstance()->Montage_Play(Data.InteractMontage);
UE_LOG(LogInteraction, Log, TEXT("EnterAnimLockPhase: 播放蒙太奇, 时长=%.2f"), TotalAnimTime);
}
}
else
{
UE_LOG(LogInteraction, Log, TEXT("EnterAnimLockPhase: 无蒙太奇,使用计时器, 时长=%.2f"), TotalAnimTime);
}
// 动画事件触发计时器(如 60% 时物品消失)
float EventTime = TotalAnimTime * Data.AnimEventTimepoint;
GetWorld()->GetTimerManager().SetTimer(AnimEventTimerHandle, this, &UInteractionManager::ExecuteAnimEvent, EventTime, false);
// 动画结束计时器
GetWorld()->GetTimerManager().SetTimer(AnimEndTimerHandle, this, &UInteractionManager::OnAnimLockComplete, TotalAnimTime, false);
bAnimEventFired = false;
}
// ============================================================================
// 动画事件触发回调
// ============================================================================
void UInteractionManager::ExecuteAnimEvent()
{
if (!CurrentInteractable)
{
return;
}
bAnimEventFired = true;
const FInteractableData& Data = CurrentInteractable->GetInteractableData();
UE_LOG(LogInteraction, Log, TEXT("ExecuteAnimEvent: 类型=%d, 名称='%s'"),
static_cast<int32>(Data.Type), *Data.DisplayName);
switch (Data.Type)
{
case EInteractType::Pickup:
{
// 标记消耗 → 隐藏 Actor → 通知物品系统
CurrentInteractable->Consume();
// 隐藏交互物 Actor
if (AInteractableActor* OwnerActor = Cast<AInteractableActor>(CurrentInteractable->GetOwner()))
{
OwnerActor->SetActorHiddenInGame(true);
}
// 广播拾取事件
OnItemPickedUp.Broadcast(Data.ItemId, Data.DisplayName, FName(TEXT("杂物")));
UE_LOG(LogInteraction, Log, TEXT("ExecuteAnimEvent: Pickup 拾取完成 '%s'"), *Data.DisplayName);
}
break;
case EInteractType::Operate:
{
if (Data.OperateSubType == EOperateSubType::Door)
{
// 切换门状态
CurrentInteractable->ToggleDoor();
// 持久化门状态到 GameInstance
if (UChildHoodGameInstance* GI = GetWorld()->GetGameInstance<UChildHoodGameInstance>())
{
GI->SetDoorOpen(Data.ItemId, CurrentInteractable->IsDoorOpen());
}
// 通知 Actor 处理 Mesh 旋转等视觉反馈
if (AInteractableActor* OwnerActor = Cast<AInteractableActor>(CurrentInteractable->GetOwner()))
{
OwnerActor->OnInteractStarted(CurrentInteractable);
}
UE_LOG(LogInteraction, Log, TEXT("ExecuteAnimEvent: Door 门状态切换, 开=%s"),
CurrentInteractable->IsDoorOpen() ? TEXT("true") : TEXT("false"));
}
// Search 类型的结果判定在 OnAnimLockComplete 中处理
}
break;
default:
break;
}
}
// ============================================================================
// 动画结束回调
// ============================================================================
void UInteractionManager::OnAnimLockComplete()
{
if (!CurrentInteractable)
{
return;
}
const FInteractableData& Data = CurrentInteractable->GetInteractableData();
UE_LOG(LogInteraction, Log, TEXT("OnAnimLockComplete: 类型=%d, 名称='%s'"),
static_cast<int32>(Data.Type), *Data.DisplayName);
switch (Data.Type)
{
case EInteractType::Pickup:
{
// 直接回到 Free(无 Active/Exit,无相机/UI 需清理)
AChildHoodCharacter* Char = Cast<AChildHoodCharacter>(GetOwner());
if (Char)
{
Char->SetInteracting(false);
}
State = EInteractionState::Free;
Phase = EInteractPhase::None;
CurrentInteractable = nullptr;
// 从检测列表移除已消耗的交互物
// Consume 已在 ExecuteAnimEvent 中调用,但 UnregisterInteractable 应由 DetectionSphere 禁用触发
// 这里主动从列表移除,确保提示 UI 正确更新
if (NearbyInteractables.Num() > 0)
{
UpdateSelection();
UpdatePromptUI();
}
UE_LOG(LogInteraction, Log, TEXT("OnAnimLockComplete: Pickup 完成,回到 Free"));
}
break;
case EInteractType::Operate:
{
if (Data.OperateSubType == EOperateSubType::Door)
{
// 直接回到 Free
AChildHoodCharacter* Char = Cast<AChildHoodCharacter>(GetOwner());
if (Char)
{
Char->SetInteracting(false);
}
State = EInteractionState::Free;
Phase = EInteractPhase::None;
CurrentInteractable = nullptr;
// 更新提示(门动词可能变化)
UpdateSelection();
UpdatePromptUI();
UE_LOG(LogInteraction, Log, TEXT("OnAnimLockComplete: Door 完成,回到 Free"));
}
else if (Data.OperateSubType == EOperateSubType::Search)
{
// 翻找结果判定:v0.1 必定有物品
CurrentInteractable->Consume();
OnItemFound.Broadcast(Data.ItemId, Data.DisplayName, FName(TEXT("杂物")));
UE_LOG(LogInteraction, Log, TEXT("OnAnimLockComplete: Search 翻找完成,获得物品 '%s'"), *Data.DisplayName);
// 进入 Active 阶段等待玩家按 E/ESC 关闭
Phase = EInteractPhase::Active;
// 有物品时不显示文本底框(架构预留)
}
}
break;
default:
break;
}
}
// ============================================================================
// UI 方法(v0.1 用日志替代)
// ============================================================================
void UInteractionManager::UpdateSelection()
{
// 排序后选中索引重置为 0(最近的)
if (AActor* Owner = GetOwner())
{
FVector OwnerLoc = Owner->GetActorLocation();
NearbyInteractables.Sort([OwnerLoc](const UInteractableComponent& A, const UInteractableComponent& B)
{
float DistA = FVector::DistSquared2D(OwnerLoc, A.GetWorldLocation());
float DistB = FVector::DistSquared2D(OwnerLoc, B.GetWorldLocation());
return DistA < DistB;
});
}
SelectedIndex = 0;
}
void UInteractionManager::UpdatePromptUI()
{
if (NearbyInteractables.Num() == 0 || !NearbyInteractables.IsValidIndex(SelectedIndex))
{
HidePrompt();
return;
}
UInteractableComponent* Selected = NearbyInteractables[SelectedIndex];
if (!Selected)
{
HidePrompt();
return;
}
FString PromptText = Selected->GetPromptText();
if (PromptWidget)
{
PromptWidget->SetPromptText(PromptText);
PromptWidget->Show();
UpdatePromptScreenPosition(Selected);
}
UE_LOG(LogInteraction, Verbose, TEXT("UpdatePromptUI: %s"), *PromptText);
}
void UInteractionManager::HidePrompt()
{
if (PromptWidget)
{
PromptWidget->Hide();
}
}
void UInteractionManager::ShowNarrativeText(const FString& Text)
{
if (Text.IsEmpty())
{
UE_LOG(LogInteraction, Warning, TEXT("ShowNarrativeText: text is empty"));
return;
}
if (NarrativeWidget)
{
NarrativeWidget->SetNarrativeText(Text);
NarrativeWidget->Show();
}
UE_LOG(LogInteraction, Verbose, TEXT("ShowNarrativeText: %s"), *Text);
}
void UInteractionManager::HideNarrativeText()
{
if (NarrativeWidget)
{
NarrativeWidget->Hide();
}
}
// ============================================================================
// 摄像机控制
// ============================================================================
void UInteractionManager::PushCamera(FVector Target, float Duration)
{
if (!PushCameraActor)
{
UE_LOG(LogInteraction, Warning, TEXT("PushCamera: PushCameraActor 为空"));
return;
}
// 计算朝向交互物的旋转
FVector LookTarget = CurrentInteractable ? CurrentInteractable->GetWorldLocation() : Target;
FRotator LookAtRot = (LookTarget - Target).Rotation();
PushCameraActor->SetActorHiddenInGame(false);
PushCameraActor->SetActorLocationAndRotation(Target, LookAtRot);
// 切换 ViewTarget
APlayerController* PC = GetWorld()->GetFirstPlayerController();
if (PC)
{
PC->SetViewTargetWithBlend(PushCameraActor, Duration, VTBlend_EaseOut);
UE_LOG(LogInteraction, Log, TEXT("PushCamera: 推镜到 %s, 时长=%.2f"), *Target.ToString(), Duration);
}
}
void UInteractionManager::ReturnCamera(float Duration)
{
AActor* Owner = GetOwner();
if (!Owner)
{
return;
}
APlayerController* PC = GetWorld()->GetFirstPlayerController();
if (PC)
{
// 回位到 Character
PC->SetViewTargetWithBlend(Owner, Duration, VTBlend_EaseIn);
UE_LOG(LogInteraction, Log, TEXT("ReturnCamera: 相机回位, 时长=%.2f"), Duration);
}
// 隐藏推镜 Actor
if (PushCameraActor)
{
PushCameraActor->SetActorHiddenInGame(true);
}
}
// ============================================================================
// 推镜位置计算
// ============================================================================
FVector UInteractionManager::CalculatePushPosition(UInteractableComponent* Interactable)
{
if (!Interactable)
{
return FVector::ZeroVector;
}
const FInteractableData& Data = Interactable->GetInteractableData();
FVector InteractPos = Interactable->GetWorldLocation();
FVector CharPos = GetOwner() ? GetOwner()->GetActorLocation() : FVector::ZeroVector;
FVector Dir = (InteractPos - CharPos);
Dir.Z = 0.0f;
Dir.Normalize();
// 交互物前方(面向角色的一侧),推镜距离偏移
FVector TargetPos = InteractPos - Dir * Data.CameraPushDistance;
// 高度略低于交互物中心(微仰视)
TargetPos.Z = InteractPos.Z * 0.8f;
return TargetPos;
}
// ============================================================================
// 叙事文本随机选取
// ============================================================================
FString UInteractionManager::PickRandomNarrative(const TArray<FString>& Texts)
{
if (Texts.Num() == 0)
{
return TEXT("");
}
if (Texts.Num() == 1)
{
LastNarrativeText = Texts[0];
return Texts[0];
}
FString Candidate;
int32 Attempts = 0;
do
{
Candidate = Texts[FMath::RandRange(0, Texts.Num() - 1)];
Attempts++;
} while (Candidate == LastNarrativeText && Attempts < 10);
LastNarrativeText = Candidate;
return Candidate;
}
// ============================================================================
// Exit 阶段定时器回调
// ============================================================================
void UInteractionManager::OnExitTimerComplete()
{
UE_LOG(LogInteraction, Log, TEXT("OnExitTimerComplete: Exit 阶段结束,回到 Free"));
// 通知 Character 解除交互状态
AChildHoodCharacter* Char = Cast<AChildHoodCharacter>(GetOwner());
if (Char)
{
Char->SetInteracting(false);
}
// 回到 Free 状态
State = EInteractionState::Free;
Phase = EInteractPhase::None;
CurrentInteractable = nullptr;
// 如果仍在检测范围内,重新显示提示
if (NearbyInteractables.Num() > 0)
{
UpdateSelection();
UpdatePromptUI();
}
}
// ============================================================================
// 提示 UI 屏幕位置更新
// ============================================================================
void UInteractionManager::UpdatePromptScreenPosition(UInteractableComponent* Interactable)
{
if (!Interactable || !PromptWidget) return;
APlayerController* PC = GetWorld()->GetFirstPlayerController();
if (!PC) return;
// 交互物世界位置 + 高度偏移 → 屏幕坐标
FVector WorldLoc = Interactable->GetWorldLocation() + FVector(0.f, 0.f, PromptHeightOffset);
FVector2D ScreenPos;
if (UGameplayStatics::ProjectWorldToScreen(PC, WorldLoc, ScreenPos, true))
{
PromptWidget->SetScreenPosition(ScreenPos);
}
}
@@ -0,0 +1,225 @@
// ChildHood Project - 交互管理器核心组件
// 对应技术方案: docs/technical/交互系统-技术方案.md 第3节(类设计)+ 第6节(行为规则实现)
// 职责:交互全流程状态机、检测选择、类型分发、摄像机/转向/UI 控制
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "InteractionTypes.h"
#include "InteractionManager.generated.h"
class UInteractableComponent;
class APushCameraActor;
class AChildHoodCharacter;
class UInteractionPromptWidget;
class UNarrativeTextWidget;
/**
* UInteractionManager
*
* 挂载在 AChildHoodCharacter 上的组件,负责交互全流程管理:
* - 层级状态机驱动(Free / Interacting + 子阶段)
* - 附近交互物检测列表排序与选中管理
* - 四种交互类型分发(观察/拾取/操作/对话)
* - 角色自动转向插值
* - 摄像机推镜/回位控制
* - UI 提示与文本显示控制(v0.1 先用日志)
*/
UCLASS(ClassGroup=(Interaction), meta=(BlueprintSpawnableComponent))
class UInteractionManager : public UActorComponent
{
GENERATED_BODY()
public:
UInteractionManager();
// === 公开方法 ===
/** 发起交互(E键触发),前置检查通过后进入 Enter 阶段 */
UFUNCTION(BlueprintCallable, Category = "Interaction")
void BeginInteract();
/** 取消/结束交互(ESC/E键在Active阶段触发),AnimLock期间不可取消 */
UFUNCTION(BlueprintCallable, Category = "Interaction")
void CancelInteract();
/** 切换选中交互物(Tab/滚轮),Direction: +1下一个 / -1上一个 */
UFUNCTION(BlueprintCallable, Category = "Interaction")
void SwitchTarget(int32 Direction);
/** 注册交互物到检测列表(由 InteractableComponent 的碰撞回调触发) */
UFUNCTION(BlueprintCallable, Category = "Interaction")
void RegisterInteractable(UInteractableComponent* Interactable);
/** 从检测列表注销交互物(离开范围或消耗时触发) */
UFUNCTION(BlueprintCallable, Category = "Interaction")
void UnregisterInteractable(UInteractableComponent* Interactable);
// === 委托 ===
/** 物品拾取事件 — 通知物品系统(Pickup 类型 ExecuteAnimEvent 中广播) */
UPROPERTY(BlueprintAssignable, Category = "Interaction|Events")
FMLocalItemEvent OnItemPickedUp;
/** 物品获得事件 — 通知收集系统(Search 类型 OnAnimLockComplete 中广播) */
UPROPERTY(BlueprintAssignable, Category = "Interaction|Events")
FMLocalItemEvent OnItemFound;
// === 可调参数 ===
/** 角色转向时长(秒) */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction|Tuning")
float TurnDuration = 0.2f;
/** 提示 UI 淡入时长(秒) */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction|Tuning")
float PromptFadeInDuration = 0.2f;
/** 提示 UI 淡出时长(秒) */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction|Tuning")
float PromptFadeOutDuration = 0.2f;
protected:
virtual void BeginPlay() override;
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
private:
// === 状态机 ===
/** 交互系统顶层状态 */
EInteractionState State = EInteractionState::Free;
/** Interacting 子阶段 */
EInteractPhase Phase = EInteractPhase::None;
// === 检测与选择 ===
/** 当前附近的交互物列表,按水平距离排序 */
UPROPERTY()
TArray<UInteractableComponent*> NearbyInteractables;
/** 当前选中的交互物索引 */
int32 SelectedIndex = 0;
/** 当前正在交互的交互物(交互过程中锁定) */
UPROPERTY()
UInteractableComponent* CurrentInteractable = nullptr;
// === 摄像机控制 ===
/** 推镜用临时摄像机 Actor(BeginPlay 中 Spawn) */
UPROPERTY()
TObjectPtr<APushCameraActor> PushCameraActor;
/** 推镜是否已发起(Enter阶段一次性标志) */
bool bPushStarted = false;
// === 计时器 ===
/** 转向已用时间 */
float TurnElapsed = 0.0f;
/** 推镜后等待已用时间 */
float WaitElapsed = 0.0f;
/** 动画事件计时器(如60%时物品消失) */
FTimerHandle AnimEventTimerHandle;
/** 动画结束计时器 */
FTimerHandle AnimEndTimerHandle;
/** 退出阶段回位计时器 */
FTimerHandle ExitTimerHandle;
/** 动画事件是否已触发 */
bool bAnimEventFired = false;
// === UI 配置 ===
/** 提示 UI 蓝图类(需在编辑器中指定为 WBP_InteractionPrompt) */
UPROPERTY(EditAnywhere, Category = "Interaction|UI")
TSubclassOf<UInteractionPromptWidget> PromptWidgetClass;
/** 文本底框 UI 蓝图类(需在编辑器中指定为 WBP_NarrativeText) */
UPROPERTY(EditAnywhere, Category = "Interaction|UI")
TSubclassOf<UNarrativeTextWidget> NarrativeWidgetClass;
/** 提示 UI 高度偏移(交互物上方的世界空间 Z 偏移,cm) */
UPROPERTY(EditAnywhere, Category = "Interaction|UI")
float PromptHeightOffset = 80.0f;
// === UI 实例 ===
/** 交互提示 Widget 实例 */
UPROPERTY()
TObjectPtr<UInteractionPromptWidget> PromptWidget;
/** 文本底框 Widget 实例 */
UPROPERTY()
TObjectPtr<UNarrativeTextWidget> NarrativeWidget;
// === 文本选择 ===
/** 上次显示的叙事文本(用于不重复选取) */
FString LastNarrativeText;
// === 内部方法 ===
/** 更新选中交互物(排序后 SelectedIndex = 0) */
void UpdateSelection();
/** 更新提示 UI 内容和位置 */
void UpdatePromptUI();
/** 隐藏提示 UI */
void HidePrompt();
/** 更新提示 UI 的屏幕空间位置(跟随交互物世界位置投影) */
void UpdatePromptScreenPosition(UInteractableComponent* Interactable);
/** Enter 阶段 Tick:转向插值 + 推镜 + 等待 */
void TickEnterPhase(float DeltaTime);
/** AnimLock 阶段 Tick(v0.1 由计时器驱动,此处可为空) */
void TickAnimLockPhase(float DeltaTime);
/** Exit 阶段 Tick(v0.1 由定时器驱动回 Free) */
void TickExitPhase(float DeltaTime);
/** Enter 阶段完成后,根据交互类型分发到下一阶段 */
void TransitionFromEnter();
/** 进入 AnimLock 阶段:播放蒙太奇或启动计时器 */
void EnterAnimLockPhase();
/** 动画事件触发点回调(如拾取物消失、门状态切换) */
void ExecuteAnimEvent();
/** 动画结束回调 */
void OnAnimLockComplete();
/** 显示叙事文本(v0.1 用日志输出) */
void ShowNarrativeText(const FString& Text);
/** 隐藏叙事文本(v0.1 用日志输出) */
void HideNarrativeText();
/** 推镜:移动 PushCameraActor 并切换 ViewTarget */
void PushCamera(FVector Target, float Duration);
/** 相机回位:切换回 Character 的 FollowCamera */
void ReturnCamera(float Duration);
/** 计算推镜目标位置 */
FVector CalculatePushPosition(UInteractableComponent* Interactable);
/** 随机选取叙事文本,排除上次 */
FString PickRandomNarrative(const TArray<FString>& Texts);
/** Exit 阶段定时器回调:回到 Free 状态 */
void OnExitTimerComplete();
};
@@ -0,0 +1,67 @@
// ChildHood Project - 交互提示 UI 基类 实现
#include "Interaction/InteractionPromptWidget.h"
#include "Components/TextBlock.h"
void UInteractionPromptWidget::NativeOnInitialized()
{
Super::NativeOnInitialized();
// 初始隐藏
SetVisibility(ESlateVisibility::Hidden);
}
void UInteractionPromptWidget::SetPromptText(const FString& Text)
{
if (PromptTextBlock)
{
PromptTextBlock->SetText(FText::FromString(Text));
}
}
void UInteractionPromptWidget::SetScreenPosition(const FVector2D& Position)
{
// 使用 RenderTranslation 偏移 Widget 位置
// 蓝图中 CanvasPanel 的 Anchors 应设置为屏幕左上角
// 此处直接设置 RenderTranslation 来移动位置
SetRenderTranslation(Position);
}
void UInteractionPromptWidget::Show()
{
if (bIsVisible) return;
bIsVisible = true;
SetVisibility(ESlateVisibility::HitTestInvisible);
if (FadeInAnim)
{
PlayAnimation(FadeInAnim, 0.0f, 1, EUMGSequencePlayMode::Forward, 1.0f);
}
}
void UInteractionPromptWidget::Hide()
{
if (!bIsVisible) return;
if (FadeOutAnim)
{
PlayAnimation(FadeOutAnim, 0.0f, 1, EUMGSequencePlayMode::Forward, 1.0f);
// 动画播完后隐藏(简单方案:直接设置定时器隐藏)
// 动画时长由蓝图中的 FadeOutAnim 决定,这里用固定 0.2s
FTimerHandle TempHandle;
if (UWorld* World = GetWorld())
{
World->GetTimerManager().SetTimer(TempHandle, [this]()
{
bIsVisible = false;
SetVisibility(ESlateVisibility::Hidden);
}, 0.2f, false);
}
}
else
{
bIsVisible = false;
SetVisibility(ESlateVisibility::Hidden);
}
}
@@ -0,0 +1,65 @@
// ChildHood Project - 交互提示 UI 基类
// 对应技术方案: docs/technical/交互系统-技术方案.md 第6节(提示UI显示/隐藏)
// 职责:屏幕空间悬浮提示 "[E] 动作词 名称",跟随交互物位置
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "InteractionPromptWidget.generated.h"
class UTextBlock;
/**
* UInteractionPromptWidget
*
* 交互提示 UI 的 C++ 基类。蓝图子类(WBP_InteractionPrompt)继承此类,
* 在 UMG 编辑器中布局可视组件,并确保组件名与 BindWidget 匹配。
*
* 绑定要求:
* - TextBlock 名为 "PromptTextBlock" — 显示提示文字
* - WidgetAnimation 名为 "FadeInAnim" — 淡入动画(0.2s)
* - WidgetAnimation 名为 "FadeOutAnim" — 淡出动画(0.2s)
*/
UCLASS(Abstract, Blueprintable)
class UInteractionPromptWidget : public UUserWidget
{
GENERATED_BODY()
public:
/** 设置提示文字内容 */
UFUNCTION(BlueprintCallable, Category = "Interaction|Prompt")
void SetPromptText(const FString& Text);
/** 设置屏幕空间位置(由 InteractionManager 每帧调用) */
UFUNCTION(BlueprintCallable, Category = "Interaction|Prompt")
void SetScreenPosition(const FVector2D& Position);
/** 显示提示(播放淡入动画) */
UFUNCTION(BlueprintCallable, Category = "Interaction|Prompt")
void Show();
/** 隐藏提示(播放淡出动画) */
UFUNCTION(BlueprintCallable, Category = "Interaction|Prompt")
void Hide();
protected:
/** 提示文字 TextBlock,蓝图子类必须有一个同名 TextBlock */
UPROPERTY(meta = (BindWidget))
UTextBlock* PromptTextBlock;
/** 淡入动画(0.2s Opacity 0→1),蓝图子类必须有同名 WidgetAnimation */
UPROPERTY(meta = (BindWidgetAnim), Transient)
UWidgetAnimation* FadeInAnim;
/** 淡出动画(0.2s Opacity 1→0),蓝图子类必须有同名 WidgetAnimation */
UPROPERTY(meta = (BindWidgetAnim), Transient)
UWidgetAnimation* FadeOutAnim;
/** 是否正在显示中 */
bool bIsVisible = false;
virtual void NativeOnInitialized() override;
};
@@ -0,0 +1,178 @@
// ChildHood Project - 交互系统数据类型定义
// 对应技术方案: docs/technical/交互系统-技术方案.md 第5节(核心数据结构)+ 第12节(跨场景持久化)
#pragma once
#include "CoreMinimal.h"
#include "InteractionTypes.generated.h"
// ============================================================================
// 枚举
// ============================================================================
/** 交互类型 */
UENUM(BlueprintType)
enum class EInteractType : uint8
{
Examine UMETA(DisplayName = "观察"),
Pickup UMETA(DisplayName = "拾取"),
Operate UMETA(DisplayName = "操作"),
Talk UMETA(DisplayName = "对话")
};
/** 操作子类型(仅 Operate 类型有效) */
UENUM(BlueprintType)
enum class EOperateSubType : uint8
{
Door UMETA(DisplayName = "门(开关切换)"),
Search UMETA(DisplayName = "翻找")
};
/** 交互系统顶层状态 */
UENUM(BlueprintType)
enum class EInteractionState : uint8
{
Free UMETA(DisplayName = "自由状态"),
Interacting UMETA(DisplayName = "交互中"),
SubSystem UMETA(DisplayName = "子系统(v0.1预留)"),
Cutscene UMETA(DisplayName = "过场(v0.1预留)")
};
/** Interacting 子阶段 */
UENUM(BlueprintType)
enum class EInteractPhase : uint8
{
None UMETA(DisplayName = "无"),
Enter UMETA(DisplayName = "进入(转向+推镜)"),
AnimLock UMETA(DisplayName = "动画锁定"),
Active UMETA(DisplayName = "活跃(等待输入)"),
Exit UMETA(DisplayName = "退出(回位)")
};
/** 交互结束后动作 */
UENUM(BlueprintType)
enum class EPostAction : uint8
{
Return UMETA(DisplayName = "回到Free"),
Minigame UMETA(DisplayName = "小游戏(v0.1预留)"),
Dialogue UMETA(DisplayName = "对话(v0.1预留)"),
SceneChange UMETA(DisplayName = "场景切换(v0.1预留)")
};
// ============================================================================
// 配置数据结构
// ============================================================================
/** 可交互物配置数据 — 每个交互物实例独立配置 */
USTRUCT(BlueprintType)
struct FInteractableData
{
GENERATED_BODY()
// === 基本信息 ===
/** 唯一ID(如 "fridge_home"),用于持久化存储和物品系统对接 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Basic")
FName ItemId;
/** 显示名称(如 "旧冰箱"),用于提示UI */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Basic")
FString DisplayName;
/** 交互类型 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Basic")
EInteractType Type = EInteractType::Examine;
/** 操作子类型,仅 Type==Operate 时可见可编辑 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Basic",
meta = (EditCondition = "Type == EInteractType::Operate", EditConditionHides))
EOperateSubType OperateSubType = EOperateSubType::Door;
// === 触发 ===
/** 触发半径(cm),默认1.5m */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Trigger",
meta = (ClampMin = "50.0", ClampMax = "500.0"))
float TriggerRadius = 150.0f;
/** 是否一次性消耗(消耗后不再可交互) */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Trigger")
bool bOneShot = false;
// === 提示文本 ===
/** 动作词(如 "查看"、"拾取"、"打开") */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Prompt")
FString InteractVerb;
/** 备选动词(如 "关上"),门打开时使用,仅 Operate+Door 时可见 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Prompt",
meta = (EditCondition = "Type == EInteractType::Operate && OperateSubType == EOperateSubType::Door"))
FString InteractVerbAlt;
// === 叙事/对话文本 ===
/** 文本列表,随机选取,不重复上次 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Text")
TArray<FString> NarrativeTexts;
// === 摄像机推镜 ===
/** 是否使用摄像机推镜 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Camera")
bool bUseCameraPush = false;
/** 推镜距离(cm),仅 bUseCameraPush 时可编辑 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Camera",
meta = (EditCondition = "bUseCameraPush", ClampMin = "10.0", ClampMax = "300.0"))
float CameraPushDistance = 80.0f;
/** 推镜过渡时长(秒),仅 bUseCameraPush 时可编辑 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Camera",
meta = (EditCondition = "bUseCameraPush", ClampMin = "0.1", ClampMax = "2.0"))
float CameraPushDuration = 0.5f;
/** 相机回位时长(秒),仅 bUseCameraPush 时可编辑 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Camera",
meta = (EditCondition = "bUseCameraPush", ClampMin = "0.1", ClampMax = "2.0"))
float CameraReturnDuration = 0.3f;
/** 推镜完成后到文本显示的等待时间(秒),仅 bUseCameraPush 时可编辑 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Camera",
meta = (EditCondition = "bUseCameraPush", ClampMin = "0.0", ClampMax = "1.0"))
float PushWaitDelay = 0.2f;
// === 动画 ===
/** 交互动画蒙太奇(可选,为空时用计时器模拟) */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Anim")
TObjectPtr<UAnimMontage> InteractMontage;
/** 无蒙太奇时的动画持续时间(秒) */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Anim",
meta = (ClampMin = "0.1", ClampMax = "5.0"))
float AnimDuration = 1.0f;
/** 动画事件触发点(0-1比例),如0.6表示动画播放至60%时触发物品消失 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Anim",
meta = (ClampMin = "0.0", ClampMax = "1.0"))
float AnimEventTimepoint = 0.6f;
// === 结束动作 ===
/** 交互结束后的动作 */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interact|Post")
EPostAction PostAction = EPostAction::Return;
};
// ============================================================================
// 委托
// ============================================================================
/** 物品拾取/获得事件 — 通知物品系统和收集系统 */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(
FMLocalItemEvent,
FName, ItemId,
const FString&, DisplayName,
const FName&, Category
);
@@ -0,0 +1,57 @@
// ChildHood Project - 文本底框 UI 基类 实现
#include "Interaction/NarrativeTextWidget.h"
#include "Components/TextBlock.h"
void UNarrativeTextWidget::NativeOnInitialized()
{
Super::NativeOnInitialized();
// 初始隐藏
SetVisibility(ESlateVisibility::Hidden);
}
void UNarrativeTextWidget::SetNarrativeText(const FString& Text)
{
if (NarrativeTextBlock)
{
NarrativeTextBlock->SetText(FText::FromString(Text));
}
}
void UNarrativeTextWidget::Show()
{
if (bIsVisible) return;
bIsVisible = true;
SetVisibility(ESlateVisibility::HitTestInvisible);
if (FadeInAnim)
{
PlayAnimation(FadeInAnim, 0.0f, 1, EUMGSequencePlayMode::Forward, 1.0f);
}
}
void UNarrativeTextWidget::Hide()
{
if (!bIsVisible) return;
if (FadeOutAnim)
{
PlayAnimation(FadeOutAnim, 0.0f, 1, EUMGSequencePlayMode::Forward, 1.0f);
FTimerHandle TempHandle;
if (UWorld* World = GetWorld())
{
World->GetTimerManager().SetTimer(TempHandle, [this]()
{
bIsVisible = false;
SetVisibility(ESlateVisibility::Hidden);
}, 0.2f, false);
}
}
else
{
bIsVisible = false;
SetVisibility(ESlateVisibility::Hidden);
}
}
@@ -0,0 +1,66 @@
// ChildHood Project - 文本底框 UI 基类
// 对应技术方案: docs/technical/交互系统-技术方案.md 第6节(通用文本显示UI)
// 职责:半透明底框 + 一句话叙事/对话文本,带淡入淡出
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "NarrativeTextWidget.generated.h"
class UTextBlock;
/**
* UNarrativeTextWidget
*
* 文本底框 UI 的 C++ 基类。蓝图子类(WBP_NarrativeText)继承此类,
* 在 UMG 编辑器中布局可视组件,并确保组件名与 BindWidget 匹配。
*
* 绑定要求:
* - TextBlock 名为 "NarrativeTextBlock" — 显示叙事/对话文字
* - WidgetAnimation 名为 "FadeInAnim" — 淡入动画(0.2s)
* - WidgetAnimation 名为 "FadeOutAnim" — 淡出动画(0.2s)
*
* 布局要求:
* - Anchors: Horizontal Center, Vertical ~0.6(屏幕中心偏下)
* - Border: 半透明黑色背景(~60%),圆角,SizeToContent
* - TextBlock: 白色文字,居中,自动换行
*/
UCLASS(Abstract, Blueprintable)
class UNarrativeTextWidget : public UUserWidget
{
GENERATED_BODY()
public:
/** 设置叙事文本内容 */
UFUNCTION(BlueprintCallable, Category = "Interaction|Narrative")
void SetNarrativeText(const FString& Text);
/** 显示文本(播放淡入动画) */
UFUNCTION(BlueprintCallable, Category = "Interaction|Narrative")
void Show();
/** 隐藏文本(播放淡出动画) */
UFUNCTION(BlueprintCallable, Category = "Interaction|Narrative")
void Hide();
protected:
/** 叙事文字 TextBlock,蓝图子类必须有一个同名 TextBlock */
UPROPERTY(meta = (BindWidget))
UTextBlock* NarrativeTextBlock;
/** 淡入动画(0.2s),蓝图子类必须有同名 WidgetAnimation */
UPROPERTY(meta = (BindWidgetAnim), Transient)
UWidgetAnimation* FadeInAnim;
/** 淡出动画(0.2s),蓝图子类必须有同名 WidgetAnimation */
UPROPERTY(meta = (BindWidgetAnim), Transient)
UWidgetAnimation* FadeOutAnim;
/** 是否正在显示中 */
bool bIsVisible = false;
virtual void NativeOnInitialized() override;
};
@@ -0,0 +1,19 @@
// ChildHood Project - 推镜用临时摄像机 实现
#include "Interaction/PushCameraActor.h"
#include "Camera/CameraComponent.h"
// ============================================================================
// 构造函数
// ============================================================================
APushCameraActor::APushCameraActor()
{
PrimaryActorTick.bCanEverTick = false;
// 创建摄像机组件
CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComp"));
// 不需要根组件的碰撞
SetRootComponent(CameraComp);
}
@@ -0,0 +1,33 @@
// ChildHood Project - 推镜用临时摄像机
// 对应技术方案: docs/technical/交互系统-技术方案.md 第3节(类设计)
// 职责:极简 Actor,仅持有一个 UCameraComponent,供 SetViewTargetWithBlend 使用
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "PushCameraActor.generated.h"
class UCameraComponent;
/**
* APushCameraActor
*
* 交互系统推镜时使用的临时摄像机 Actor。
* 由 InteractionManager 在 BeginPlay 中 Spawn,随 Character 生命周期。
* InteractionManager 在推镜时设置其 Location/Rotation,
* 然后通过 PlayerController::SetViewTargetWithBlend 切换 ViewTarget。
*/
UCLASS()
class APushCameraActor : public AActor
{
GENERATED_BODY()
public:
APushCameraActor();
/** 推镜摄像机组件 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UCameraComponent* CameraComp;
};
+13
View File
@@ -0,0 +1,13 @@
# API易 平台配置
# 复制此文件为 .env 并填入你的 API Key
# cp .env.example .env
# API 令牌(必填)— 在 https://api.apiyi.com/token 创建
APIYI_API_KEY=sk-your-api-key-here
# API 地址(通常不需要修改)
APIYI_BASE_URL=https://api.apiyi.com/v1
# 默认生图模型
# 可选: gpt-image-2, gemini-3-1-flash-image-preview, dall-e-3, flux-pro-v1.1
APIYI_MODEL=gpt-image-2
+454
View File
@@ -0,0 +1,454 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ChildHood AI Art Generation Tool
通过 API易 平台调用 AI 生图模型
Usage:
python image_gen.py --test # 测试 API 连通性
python image_gen.py --generate "千禧年中国城郊的小卖部门口" # 文生图(自动加风格前缀)
python image_gen.py --generate "a red apple" --no-style # 纯文生图(不加风格前缀)
python image_gen.py --calibrate # 风格校准(5 个测试物体)
python image_gen.py --fuse --style style.png --content photo.jpg # 双参考融合
python image_gen.py --fuse --style style.png --content photo.jpg --prompt "自定义 prompt"
Requirements:
pip install openai python-dotenv
"""
import argparse
import base64
import io
import json
import os
import sys
import urllib.request
# Windows 控制台 UTF-8 支持
if sys.platform == "win32":
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
import os
import sys
import urllib.request
from datetime import datetime
from pathlib import Path
# ---------------------------------------------------------------------------
# 路径配置
# ---------------------------------------------------------------------------
SCRIPT_DIR = Path(__file__).resolve().parent # ai-art-research/tools/
RESEARCH_DIR = SCRIPT_DIR.parent # ai-art-research/
DOTENV_PATH = RESEARCH_DIR / ".env"
OUTPUT_BASE = RESEARCH_DIR / "生成结果"
# ---------------------------------------------------------------------------
# 加载 .env
# ---------------------------------------------------------------------------
if DOTENV_PATH.exists():
from dotenv import load_dotenv
load_dotenv(DOTENV_PATH)
API_KEY = os.getenv("APIYI_API_KEY", "")
BASE_URL = os.getenv("APIYI_BASE_URL", "https://api.apiyi.com/v1")
DEFAULT_MODEL = os.getenv("APIYI_MODEL", "gpt-image-2")
# ---------------------------------------------------------------------------
# 风格 Prompt 种子(来自工作流方案 v0.1)
# ---------------------------------------------------------------------------
STYLE_SEED_EN = (
"3D low-poly game art, flat cel-shaded with solid color blocks, "
"bright warm-cool balanced saturated palette, soft diffused lighting "
"with subtle shadows, simplified geometric forms with clean edges, "
"low-angle third-person perspective, nostalgic warm summer atmosphere"
)
# 风格校准测试物体(中文名, 英文 prompt)
CALIBRATION_OBJECTS = [
("乌鸦", "a crow standing on the ground"),
("茶杯", "a teacup on a wooden table"),
("老人", "an old man standing in a village street"),
("山湖", "a mountain lake with trees"),
("苹果", "a red apple on the ground"),
]
# 双参考融合默认 prompt
DEFAULT_FUSION_PROMPT = """\
图1 是我的风格参考(一张游戏截图)。图2 是我的内容参考(一张现实照片)。
请生成一张新图片,用图1的美术风格来呈现图2中的场景内容。
风格要求:
- 高度简化的低多边形几何形体,干净利落的边缘
- 平面色块着色,不用渐变或写实纹理
- 鲜艳明亮的暖冷平衡色调(暖黄、天蓝、翠绿为主)
- 柔和散射光,阴影淡而柔和
- 微低角度第三人称视角
- 温暖怀旧的夏日午后氛围
内容要求:
- 保留图2中的建筑类型、布局、空间关系
- 但把所有建筑元素替换为千禧年中国城郊的风格
- 色调保持温暖怀旧"""
# ---------------------------------------------------------------------------
# 工具函数
# ---------------------------------------------------------------------------
def get_client() -> "OpenAI":
"""创建 OpenAI 客户端"""
if not API_KEY:
print("❌ 未找到 API Key")
print(f" 请在 {DOTENV_PATH} 中设置: APIYI_API_KEY=sk-xxx")
sys.exit(1)
from openai import OpenAI
return OpenAI(api_key=API_KEY, base_url=BASE_URL)
def _extract_image_bytes(img_obj) -> bytes:
"""从 API 响应中提取图片字节数据"""
# 优先 base64
b64 = getattr(img_obj, "b64_json", None)
if b64:
return base64.b64decode(b64)
# 其次 URL
url = getattr(img_obj, "url", None)
if url:
print(" ⬇️ 正在下载图片...")
return urllib.request.urlopen(url).read()
raise ValueError("响应中未找到图片数据(无 b64_json 也无 url)")
def _sanitize_filename(s: str, max_len: int = 40) -> str:
"""清理文件名中的非法字符"""
result = "".join(c if c.isalnum() or c in "._-" else "_" for c in s)
return result[:max_len].rstrip("_")
def save_result(image_bytes: bytes, metadata: dict, subdir: str = "") -> Path:
"""保存图片和元数据,返回图片路径"""
ts = datetime.now().strftime("%y%m%d-%H%M%S")
model_slug = metadata.get("model", "unknown").replace("/", "-").replace(".", "-")
out_dir = OUTPUT_BASE / subdir if subdir else OUTPUT_BASE
out_dir.mkdir(parents=True, exist_ok=True)
prompt_tag = _sanitize_filename(metadata.get("prompt", "gen"))
filename = f"{ts}_{model_slug}_{prompt_tag}.png"
img_path = out_dir / filename
img_path.write_bytes(image_bytes)
meta_path = img_path.with_suffix(".meta.json")
meta_path.write_text(json.dumps(metadata, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"✅ 已保存: {img_path.name}")
return img_path
# ---------------------------------------------------------------------------
# 功能:测试连通性
# ---------------------------------------------------------------------------
def cmd_test(client):
"""测试 API 易连通性,生成一张简单测试图"""
print("🔗 测试 API易 连通性...")
print(f" Base URL: {BASE_URL}")
print(f" Model: {DEFAULT_MODEL}")
print(f" API Key: {API_KEY[:8]}...{API_KEY[-4:]}")
print()
prompt = f"{STYLE_SEED_EN}, a simple red cube on a white background, test image"
print(f" Prompt: {prompt[:80]}...")
response = client.images.generate(
model=DEFAULT_MODEL,
prompt=prompt,
size="1024x1024",
quality="low",
n=1,
)
image_bytes = _extract_image_bytes(response.data[0])
save_result(image_bytes, {
"model": DEFAULT_MODEL,
"prompt": prompt,
"mode": "connection_test",
"size": "1024x1024",
"quality": "low",
"timestamp": datetime.now().isoformat(),
}, subdir="测试")
print()
print("✅ API 连通性测试通过!")
# ---------------------------------------------------------------------------
# 功能:文生图
# ---------------------------------------------------------------------------
def cmd_generate(client, prompt: str, use_style: bool, size: str, quality: str,
model: str, subdir: str):
"""文生图"""
model = model or DEFAULT_MODEL
full_prompt = f"{STYLE_SEED_EN}, {prompt}" if use_style else prompt
print(f"🎨 文生图")
print(f" 模型: {model}")
print(f" 尺寸: {size} 质量: {quality}")
print(f" 风格前缀: {'是' if use_style else '否'}")
print(f" Prompt: {full_prompt[:100]}{'...' if len(full_prompt) > 100 else ''}")
print()
response = client.images.generate(
model=model,
prompt=full_prompt,
size=size,
quality=quality,
n=1,
)
image_bytes = _extract_image_bytes(response.data[0])
save_result(image_bytes, {
"model": model,
"prompt": full_prompt,
"mode": "text_to_image",
"style_prefix_used": use_style,
"size": size,
"quality": quality,
"timestamp": datetime.now().isoformat(),
}, subdir=subdir)
# ---------------------------------------------------------------------------
# 功能:风格校准
# ---------------------------------------------------------------------------
def cmd_calibrate(client, model: str):
"""风格校准 — 5 个测试物体"""
model = model or DEFAULT_MODEL
subdir = "风格校准"
print("🎯 开始风格校准测试")
print(f" 模型: {model}")
print(f" 风格种子: {STYLE_SEED_EN[:60]}...")
print(f" 测试物体: {', '.join(zh for zh, _ in CALIBRATION_OBJECTS)}")
print()
success = 0
for zh_name, en_prompt in CALIBRATION_OBJECTS:
full_prompt = f"{STYLE_SEED_EN}, {en_prompt}"
print(f" 🎨 生成: {zh_name} ...")
try:
response = client.images.generate(
model=model,
prompt=full_prompt,
size="1024x1024",
quality="medium",
n=1,
response_format="b64_json",
)
image_bytes = _extract_image_bytes(response.data[0])
save_result(image_bytes, {
"model": model,
"prompt": full_prompt,
"mode": "style_calibration",
"object_zh": zh_name,
"object_en": en_prompt,
"style_seed": STYLE_SEED_EN,
"timestamp": datetime.now().isoformat(),
}, subdir=subdir)
success += 1
except Exception as e:
print(f" ❌ 失败: {e}")
print()
print(f"🎉 风格校准完成!成功 {success}/{len(CALIBRATION_OBJECTS)} 张")
print(f" 输出目录: {OUTPUT_BASE / subdir}")
print()
print("📋 验证方法:打开上述目录,检查 5 张图是否视觉风格统一。")
print(" 如果风格不一致,需要调整 STYLE_SEED_EN 后重新运行。")
# ---------------------------------------------------------------------------
# 功能:双参考融合
# ---------------------------------------------------------------------------
def cmd_fuse(client, style_path: str, content_path: str, prompt: str,
model: str, subdir: str):
"""双参考图融合"""
model = model or DEFAULT_MODEL
fusion_prompt = prompt or DEFAULT_FUSION_PROMPT
subdir = subdir or "双参考融合"
style_file = Path(style_path).resolve()
content_file = Path(content_path).resolve()
if not style_file.exists():
print(f"❌ 风格参考图不存在: {style_file}")
sys.exit(1)
if not content_file.exists():
print(f"❌ 内容参考图不存在: {content_file}")
sys.exit(1)
print("🔗 双参考融合")
print(f" 模型: {model}")
print(f" 风格参考: {style_file.name}")
print(f" 内容参考: {content_file.name}")
print(f" Prompt: {fusion_prompt[:80]}...")
print()
# 编码图片为 base64
style_b64 = base64.b64encode(style_file.read_bytes()).decode()
content_b64 = base64.b64encode(content_file.read_bytes()).decode()
# 确定图片 MIME 类型
ext_map = {".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg",
".webp": "image/webp", ".gif": "image/gif"}
style_mime = ext_map.get(style_file.suffix.lower(), "image/png")
content_mime = ext_map.get(content_file.suffix.lower(), "image/png")
# 尝试方法 1: images.edit(带多图的编辑模式)
image_bytes = None
errors = []
# 方法 1: images.edit
print(" 尝试方式 1: images.edit ...")
try:
response = client.images.edit(
model=model,
image=[
{"type": "image_file", "image_file": {
"file_data": f"data:{style_mime};base64,{style_b64}",
"filename": style_file.name,
}},
{"type": "image_file", "image_file": {
"file_data": f"data:{content_mime};base64,{content_b64}",
"filename": content_file.name,
}},
],
prompt=fusion_prompt,
size="1024x1024",
)
image_bytes = _extract_image_bytes(response.data[0])
print(" ✅ 方式 1 成功")
except Exception as e:
errors.append(f"images.edit: {e}")
print(f" ⚠️ 方式 1 失败: {e}")
# 方法 2: images.generate with b64 image in prompt (chat-style)
if image_bytes is None:
print(" 尝试方式 2: images.generate with extra_body ...")
try:
response = client.images.generate(
model=model,
prompt=fusion_prompt,
size="1024x1024",
quality="medium",
n=1,
response_format="b64_json",
extra_body={
"image": [
f"data:{style_mime};base64,{style_b64[:100]}...", # 截断防止请求过大
],
},
)
image_bytes = _extract_image_bytes(response.data[0])
print(" ✅ 方式 2 成功")
except Exception as e:
errors.append(f"images.generate+extra_body: {e}")
print(f" ⚠️ 方式 2 失败: {e}")
if image_bytes is None:
print()
print("❌ 双参考融合 API 调用失败。")
print(" 这可能是因为 API易 不支持多图输入的 images.edit 端点。")
print()
print("💡 替代方案:")
print(" 1. 使用 API易 网页界面上传多张参考图手动操作")
print(" 2. 或将风格描述写入 prompt,仅上传内容参考图(单图模式)")
print()
print(" 所有错误:")
for err in errors:
print(f" - {err}")
sys.exit(1)
save_result(image_bytes, {
"model": model,
"prompt": fusion_prompt,
"mode": "dual_reference_fusion",
"style_reference": str(style_file),
"content_reference": str(content_file),
"size": "1024x1024",
"timestamp": datetime.now().isoformat(),
}, subdir=subdir)
# ---------------------------------------------------------------------------
# CLI 入口
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description="ChildHood AI Art Generation Tool — 通过 API易 生图",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""\
示例:
python image_gen.py --test # 测试连通性
python image_gen.py -g "千禧年中国城郊的小卖部门口" # 文生图
python image_gen.py -g "a red apple" --no-style # 不加风格前缀
python image_gen.py --calibrate # 风格校准
python image_gen.py --fuse --style ref.png --content photo.jpg # 双参考融合
python image_gen.py --fuse -s style.png -c photo.jpg -p "自定义" # 自定义 prompt
环境变量 (ai-art-research/.env):
APIYI_API_KEY API易 令牌 (必填)
APIYI_BASE_URL API 地址 (默认 https://api.apiyi.com/v1)
APIYI_MODEL 默认模型 (默认 gpt-image-2)
""",
)
# 互斥的操作模式
mode = parser.add_mutually_exclusive_group()
mode.add_argument("--test", action="store_true", help="测试 API 连通性")
mode.add_argument("-g", "--generate", type=str, metavar="PROMPT", help="文生图")
mode.add_argument("--calibrate", action="store_true", help="风格校准(5 个测试物体)")
mode.add_argument("--fuse", action="store_true", help="双参考图融合模式")
# 参数
parser.add_argument("-s", "--style", type=str, help="风格参考图路径(配合 --fuse)")
parser.add_argument("-c", "--content", type=str, help="内容参考图路径(配合 --fuse)")
parser.add_argument("-p", "--prompt", type=str, help="自定义 prompt")
parser.add_argument("-m", "--model", type=str, help=f"模型 (默认: {DEFAULT_MODEL})")
parser.add_argument("--no-style", action="store_true", help="文生图不加风格前缀")
parser.add_argument("--size", type=str, default="1024x1024", help="图片尺寸")
parser.add_argument("--quality", type=str, default="medium",
choices=["low", "medium", "high"], help="质量")
parser.add_argument("--subdir", type=str, default="", help="输出子目录名")
args = parser.parse_args()
# 无参数时显示帮助
if len(sys.argv) == 1:
parser.print_help()
return
client = get_client()
if args.test:
cmd_test(client)
elif args.generate:
cmd_generate(
client, args.generate,
use_style=not args.no_style,
size=args.size, quality=args.quality,
model=args.model, subdir=args.subdir or "文生图",
)
elif args.calibrate:
cmd_calibrate(client, model=args.model)
elif args.fuse:
if not args.style or not args.content:
parser.error("双参考融合需要 --style (-s) 和 --content (-c) 参数")
cmd_fuse(
client, args.style, args.content,
prompt=args.prompt, model=args.model, subdir=args.subdir,
)
if __name__ == "__main__":
main()
@@ -0,0 +1,8 @@
{
"model": "gpt-image-2",
"prompt": "3D low-poly game art, flat cel-shaded with solid color blocks, bright warm-cool balanced saturated palette, soft diffused lighting with subtle shadows, simplified geometric forms with clean edges, low-angle third-person perspective, nostalgic warm summer atmosphere, a simple red cube on a white background, test image",
"mode": "connection_test",
"size": "1024x1024",
"quality": "low",
"timestamp": "2026-06-10T00:35:40.485529"
}
+215 -24
View File
@@ -2,7 +2,7 @@
> 完整游戏世界的空间、时代、氛围设计。
> 覆盖城市主场景 + 乡下场景,以及场景与各系统的关联。
> 最后更新:2026-06-01
> 最后更新:2026-06-10
---
@@ -51,8 +51,8 @@
|------|------|--------|
| 尺度 | ~80m × 60m | ~150m × 120m |
| 区域数 | 6 个核心 + 1 背景 | 主街 + 生活区 + 边缘三大板块 |
| 可进入室内 | 0-1 个 | 5-6 个(家 + 店铺 + 网吧) |
| 店铺 | 仅小卖部 | 小卖部、书店、街机店、网吧、录像出租点 |
| 可进入室内 | 0-1 个 | ~12 个(家 + 6 店铺 + 4 小伙伴家 + 网吧) |
| 店铺 | 仅小卖部 | 小卖部、书店、粮油店、服装店、鞋店、街机店、网吧、录像出租点、储蓄所 |
| 居住形态 | 平房带院子 | 事业单位楼房 + 小区 + 平房区 |
### 2.3 总体布局
@@ -109,7 +109,7 @@
## 三、商业主街
主街约 70m 长、5-6m 宽的水泥路,是整个场景最繁华的区域。以中间为界,**西侧是商业娱乐区**(街机、网吧、录像出租),**东侧是传统生活区**(小卖部、书店、学校)。街边散布着各类摊贩。
主街约 70m 长、5-6m 宽的水泥路,是整个场景最繁华的区域。以中间为界,**西侧是商业娱乐区**(街机、网吧、录像出租、储蓄所、棋牌室),**东侧是传统生活区**(小卖部、书店、粮油店、服装店、鞋店、学校)。街边散布着各类摊贩和公交站牌。
### 3.1 东侧(传统生活)
@@ -201,7 +201,65 @@
---
#### 3.1.3 学校
#### 3.1.3 粮油店
**位置**:主街东部,书店旁边
**面积**:小型(~3m × 3m)
**可进入**:简化(门口交互为主)
**外观**:
- 窄门面,门口摆着几袋大米和油桶
- 招牌手写"粮油"二字,简单直接
- 门口有电子秤
**时代物件**:散装大米、5L 塑料油桶、酱油醋大缸(散装打)、挂面
**系统关联**:
- 氛围营造(城郊生活必需品店铺)
- 可能作为零花钱系统的消费点
---
#### 3.1.4 服装店/裁缝铺
**位置**:主街东部,粮油店旁边
**面积**:小型(~3m × 3m)
**可进入**:简化(门口交互为主)
**外观**:
- 门口挂着几件成衣样品
- 招牌写着"XX 服装"或"裁缝"
- 老板娘坐在门口
**时代物件**:成衣、布匹、缝纫机、量衣尺
**系统关联**:
- 氛围营造(裁缝铺是城郊标配)
- 简化对话交互
---
#### 3.1.5 鞋店
**位置**:主街东部,服装店旁边
**面积**:小型(~3m × 3m)
**可进入**:简化(门口交互为主)
**外观**:
- 门口摆着几排鞋(运动鞋为主)
- 招牌写着"XX 鞋业"
- 墙上挂着运动鞋海报(安踏/李宁风格)
**时代物件**:运动鞋(安踏/李宁早期款式)、塑料凉鞋、布鞋
**系统关联**:
- 氛围营造(运动鞋是小孩的梦想)
- 简化对话交互
- 可能与"换装"概念联动
---
#### 3.1.6 学校
**位置**:主街最东端
**朝向**:正门面向主街,侧门(小铁门)面向生活区巷子
@@ -279,6 +337,7 @@
- 经济系统(柜台换币消费)
- 小霸王常驻于此(格斗高手)
- 西侧的"磁石"——孩子们被声音和灯光吸引过来
- **包机房后屋**:门帘隔开的内部空间,提供主机游戏(PS/SS 风格),比街机更沉浸但更贵
---
@@ -362,6 +421,58 @@
---
#### 3.2.4 储蓄所
**位置**:主街西部,录像出租点旁边
**面积**:小型(~4m × 3m)
**可进入**:简化(柜台交互)
**外观**:
- 正式的门面,比周围店铺更"正经"
- 招牌写着"XX 储蓄所"或"XX 信用社"
- 门上有铁栅栏门(下班后拉上)
**时代物件**:存折、手写利率牌、算盘、老式点钞机
**系统关联**:
- 零花钱系统(存取零花钱?待定)
- 时代氛围(储蓄所是那个年代的金融地标)
---
#### 3.2.5 棋牌室
**位置**:主街西部
**面积**:小型
**可进入**:❌ 不可进入(大人的场所)
**外观**:
- 半开的门,里面烟雾缭绕
- 门口可能坐着抽烟的大人
- 招牌写着"棋牌"或"茶室"
- 能听到麻将牌碰撞的声音
**系统关联**:
- 纯氛围营造(大人的世界,小孩好奇但不能进)
- 声音景观(麻将声作为主街西侧的背景音)
---
#### 3.2.6 主街设施
**公交站牌**:
- 主街中段的简易公交站牌(铁杆+铁皮站牌)
- 公交车会经过但不可乘坐
- 纯装饰,增强城郊出行氛围
**废品回收点**:
- 主街路边固定摊位
- 一把秤 + 几个编织袋 + 地上分类的废品
- 老板娘坐镇,可交互卖废品
- 与流动收废品车互补(固定点方便,流动车价格可能不同)
---
### 3.3 街边摊贩
**分布**:散布在主街各处 + 生活区巷子中
@@ -372,6 +483,8 @@
|----------|---------|---------|------|
| 冰棍推车 | 主街阴凉处、学校门口 | 泡沫箱+棉被保温,绿豆冰/红豆冰/白糖冰棍 | 夏天标配 |
| 烤肠机 | 小卖部门口、网吧门口 | 电烤机,1-2 元一根 | 简单经济触点 |
| 烤鱿鱼 | 主街散布 | 铁板,酱料 | 城郊特色小吃 |
| 烤里脊 | 主街散布 | 铁板串烤 | 城郊特色小吃 |
| 糖葫芦 | 随机出现 | 插在草把子上,山楂/草莓/橘子 | 随机事件 |
| 爆米花 | 偶尔出现在主街/空地附近 | 老式黑铁葫芦爆米花机,"嘭"一声 | 随机事件,声音记忆 |
| 棉花糖 | 偶尔出现在空地/学校附近 | 手摇式棉花糖机 | 随机事件 |
@@ -396,15 +509,28 @@
|---|----------|------|--------|---------|
| 1 | 合并小卖部 | 8m×5m | ✅ | 经济核心、收集入口、街坊聚集 |
| 2 | 书店 | 5m×4m | ✅ | 漫画/书籍、叙事触点、时代氛围 |
| 3 | 学校(仅操场) | 25m×18m | — | 肢体活动、剧情场景 |
| 3 | 粮油店 | ~3m×3m | 简化 | 氛围营造、城郊生活必需品 |
| 4 | 服装店/裁缝铺 | ~3m×3m | 简化 | 氛围营造、简化对话 |
| 5 | 鞋店 | ~3m×3m | 简化 | 氛围营造、运动鞋梦想 |
| 6 | 学校(仅操场) | 25m×18m | — | 肢体活动、剧情场景 |
**西侧(商业娱乐)**:
| # | 店铺/设施 | 面积 | 可进入 | 核心功能 |
|---|----------|------|--------|---------|
| 4 | 街机店 | 8m×6m | ✅ | 街机小游戏、社交核心 |
| 5 | 网吧 | 6m×5m | ✅ | PC 小游戏、社交、时代氛围 |
| 6 | 录像出租点 | 5m×4m | ✅ | 录像带/磁带、叙事触点、收集 |
| 7 | 街机店(含包机房后屋) | 8m×6m | ✅ | 街机小游戏、社交核心、包机房 |
| 8 | 网吧 | 6m×5m | ✅ | PC 小游戏、社交、时代氛围 |
| 9 | 录像出租点 | 5m×4m | ✅ | 录像带/磁带、叙事触点、收集 |
| 10 | 储蓄所 | ~4m×3m | 简化 | 存取零花钱?时代氛围 |
| 11 | 棋牌室 | 小型 | ❌ | 纯氛围(大人的场所) |
**主街设施**:
| # | 设施 | 性质 |
|---|------|------|
| 12 | 公交站牌 | 纯装饰 |
| 13 | 废品回收点 | 路边固定摊位,可交互卖废品 |
| 14 | 街边摊贩 × N | 散布式(烤鱿鱼、烤里脊、冰棍推车、烤肠等) |
---
@@ -430,9 +556,15 @@
- 楼道:水泥楼梯,声控灯,墙上贴着小广告和水电费通知
**死党 A 的家**:
- 在某栋楼的某层(不可进入,但外观有标识——比如阳台上挂着四驱车赛道模型)
- 在某栋楼的某层(简化室内,可进入)
- 外观有标识——比如阳台上挂着四驱车赛道模型
- 室内简化:小空间 + 对话为主,展示家境好的氛围(大电视、四驱车跑道、电脑)
- 从楼下能看到他家阳台上摆着的东西
**小区设施**:
- **自行车棚**:水泥柱 + 石棉瓦顶,停着几辆老式自行车(永久牌/凤凰牌)
- 信报箱区(铁皮信报箱,嵌在楼道口墙上)
**系统关联**:
- 四驱车系统(死党 A 在小区空地展示/比拼四驱车)
- 社交场所(小区空地是另一个孩子聚集点)
@@ -498,6 +630,15 @@
- 墙上贴着水电费通知、小广告
- 楼道里偶尔碰到邻居(背景 NPC)
**女班长家**:
- 同栋楼不同层(简化室内,可进入)
- 室内简化:小空间 + 对话为主
- 女孩的房间视角,与主角家形成对照
**楼下设施**:
- **信报箱**:铁皮信报箱嵌在楼道口,可能收到通知/信件
- **自行车棚**:楼旁小型车棚(如有独立空间)
**系统关联**:
- FC 家用机小游戏(坐在客厅电视前玩)
- 暑假作业叙事线(书桌上的作业)
@@ -535,11 +676,16 @@
| 修车铺 | 修自行车的,门口散落零件 | 扳手、打气筒、旧轮胎 |
| 小吃摊 | 下午出摊,卖凉皮、凉面 | 铁皮推车、调料瓶一字排开 |
这些小店大多不可进入(仅在门口交互),主要是氛围营造和日常事件触发。
这些小店大多简化交互(门口对话为主),主要是氛围营造和日常事件触发。其中早餐店可交互买东西。
**平房区设施**:
- **公共厕所**:平房区内的旱厕,城郊生活的标志性设施
- **公共水龙头**:巷子角落
**死党 B 的家**:
- 平房区某一间(外观有标识——门口可能放着弹珠罐子或自制玩具)
- 不可进入,但门口有时可以和死党 B 互动
- 平房区某一间(简化室内,可进入)
- 外观有标识——门口可能放着弹珠罐子或自制玩具
- 室内简化:小空间 + 对话为主,展示温馨但简朴的氛围
**系统关联**:
- 对比叙事(小区 vs 平房区的物质差异)
@@ -574,11 +720,16 @@
| # | 区域 | 位置 | 类型 | 核心功能 |
|---|------|------|------|---------|
| 1 | 小区 | 靠东侧 | 正经住宅(3-4 栋 6 层楼 + 中庭) | 死党 A 的家、四驱车场地、对比叙事 |
| 1 | 小区 | 靠东侧 | 正经住宅(3-4 栋 6 层楼 + 中庭) | 死党 A 的家(简化室内)、四驱车场地、对比叙事 |
| 2 | 主角家 | 中部 | 事业单位家属楼(独栋 6 层) | 起点/终点、FC 游戏、叙事 |
| 3 | 平房区 | 靠西侧 | 自建平房群 + 生活小店 | 死党 B 的家、弹珠场地、对比叙事 |
| 2b | 女班长家 | 同主角家楼 | 同栋不同层(简化室内) | 女班长家、社交 |
| 3 | 平房区 | 靠西侧 | 自建平房群 + 生活小店 | 死党 B 的家(简化室内)、弹珠场地、对比叙事 |
| 4 | 巷子通道 | 贯穿 | 2-3 个巷子口 + 内部连接路 | 穿行、日常事件、氛围 |
**小区设施**:自行车棚、信报箱
**平房区设施**:公共厕所、公共水龙头
**平房区小店**:早餐店(可交互)、理发店/修车铺/缝纫店(背景+对话)
---
## 五、边缘区域
@@ -644,6 +795,24 @@
---
### 5.3 秘密基地(隐藏区域)
**设计理念**:散布在地图各处的小型隐藏空间,每个都有独特的"钻进去"方式。代表童年探索的乐趣——发现只有小孩才知道的秘密角落。
| # | 名称 | 位置 | 进入方式 | 氛围 |
|---|------|------|---------|------|
| H1 | 秘密基地 A | 工地蓝色围挡后面 | 围挡底部有个洞,蹲下钻进去 | 废弃建材、涂鸦、偷藏的零食 |
| H2 | 秘密基地 B | 平房区深处的死角 | 穿过一条很窄的巷子 | 老式家具、墙角涂鸦、安静 |
| H3 | 秘密基地 C | 待定 | 待定 | 待定 |
**系统关联**:
- 探索奖励(发现秘密基地本身就是一个成就)
- 收集品散落(秘密基地里有独特收集品)
- 叙事触点(秘密基地里发现前人留下的东西、和朋友在这里约定)
- 氛围营造("这是我们的秘密"的专属感)
---
## 六、乡下场景(奶奶家)
### 6.1 定位与尺度
@@ -785,20 +954,29 @@
```
┌──────────────────────────────────┐
│ 大型空地/工地 河边/河堤 │
│ [秘密基地A] │
└──────────┬───────────┬───────────┘
│ │
┌──────────┴───────────┴───────────┐
│ 生活区巷子 │
│ [平房区] ── [主角家] ── [小区] │
│ [平房区] ── [主角家+女班长家] ── [小区] │
│ [秘密基地B] │
│ [早餐店·理发店·修车铺·缝纫店] │
│ [公共厕所·自行车棚·信报箱] │
└──┬──────────┬──────────┬─────────┘
│巷子口① │巷子口② │巷子口③
┌──┴──────────┴──────────┴──────────┐
│ 商业主街 │
│ │
│ 西侧(娱乐) 东侧(生活) │
│ [街机店] [小卖部] │
│ [网吧] [书店] │
│ [录像出租] [学校] │
│ [棋牌室] [小卖部] │
│ [储蓄所] [粮油店] │
│ [录像出租] [书店] │
│ [网吧] [服装店] │
│ [街机店] [鞋店] │
│ [学校] │
│ │
│ [公交站牌] [废品回收点] [摊贩×N] │
└────────────────────────────────────┘
```
@@ -858,7 +1036,7 @@
| **女班长** | 学校操场(跳绳/跳皮筋) | 主街、小卖部门口 | 上午活跃 |
| **转学生** | 小卖部门口(初来时) | 逐渐扩展到空地、街机店 | 中后期活动范围扩大 |
| **残障儿童** | 平房区巷子口(家附近) | 小卖部门口(坐着看大家玩) | 下午出现 |
| **小卖部老板儿子** | 合并小卖部 | 街机店(偷溜去玩) | 上午在店里、下午偷溜 |
| **小卖部老板儿子** | 合并小卖部(后屋是家) | 街机店(偷溜去玩) | 上午在店里、下午偷溜 |
| **小霸王** | 街机店(格斗高手) | 空地(找人挑衅) | 下午到傍晚 |
### 8.2 设计原则
@@ -994,10 +1172,17 @@
|------|------|------|
| 合并小卖部 | 主街东侧 | 买零食、买文具、买四驱车配件、开卡片 |
| 书店 | 主街东侧 | 租借漫画/书籍 |
| 街机店 | 主街西侧 | 柜台换币、投币游戏 |
| 粮油店 | 主街东侧 | 氛围 + 可能消费 |
| 服装店/裁缝铺 | 主街东侧 | 氛围 + 简化对话 |
| 鞋店 | 主街东侧 | 氛围 + 简化对话 |
| 街机店 | 主街西侧 | 柜台换币、投币游戏、包机房 |
| 网吧 | 主街西侧 | 上网收费 |
| 录像出租点 | 主街西侧 | 租借录像带/磁带 |
| 储蓄所 | 主街西侧 | 存取零花钱?待定 |
| 废品回收点 | 主街路边 | 卖废品换钱 |
| 早餐店 | 平房区 | 买早点(包子油条豆浆) |
| 街边摊贩 | 主街 + 巷子 | 买冰棍、买小吃(比店铺便宜) |
| 流动收废品车 | 巷子巡游 | 卖废品换钱(流动价格) |
| 村口小卖部 | 乡下 | 乡下唯一消费点 |
### 10.4 叙事系统 × 场景触发
@@ -1086,9 +1271,9 @@
| **v0.2** | NPC 放置 + 弹珠场地标识 + NPC 常驻点标识 |
| **v0.3** | 时段光影 + 天气初步 + 学校操场 |
| **v0.4** | 街机店室内 + 录像出租点室内 + 叙事触发点布设 + 小区外观 |
| **v0.5** | 网吧室内 + 书店室内 + 平房区 + 生活小店 + 工地围挡 + 摊贩体系 |
| **v0.5** | 网吧室内 + 书店室内 + 粮油店/服装店/鞋店 + 储蓄所 + 平房区 + 生活小店 + 工地围挡 + 摊贩体系 + 废品回收点 |
| **v0.6** | **乡下场景全部**(奶奶家、小溪、田间路、村口小卖部) |
| **v0.7** | 全场景整合 + 城乡切换 + 细节打磨 |
| **v0.7** | 全场景整合 + 城乡切换 + 4 家小伙伴家(简化室内)+ 秘密基地 + 棋牌室 + 公交站牌 + 细节打磨 |
| **v1.0** | 氛围精修(声音景观、天气效果、光影优化) |
---
@@ -1100,7 +1285,13 @@
- [ ] 乡下阶段的 NPC 具体设计(村里有几个人?什么性格?)
- [ ] 天气系统的详细设计(v0.3 前确定范围)
- [ ] 室内场景的光照方案(自然光 vs 灯光)
- [ ] 平房区生活小店哪些可以进入、哪些仅门口交互
- [ ] 网吧的 PC 小游戏内容和家里 FC 的体验差异如何区分
- [ ] 街边摊贩的出现逻辑(固定位置+固定时段?还是随机事件?)
- [ ] 主街东西两侧是否有视觉上的渐变差异(东侧更传统朴素、西侧更鲜亮热闹?)
- [x] ~~平房区生活小店哪些可以进入、哪些仅门口交互~~ → 早餐店可交互,其他简化对话
- [x] ~~小伙伴家是否可进入~~ → 4 家可进入(全部简化室内):死党A家、死党B家、老板儿子家(小卖部后屋)、女班长家(主角家同楼)
- [x] ~~包机房安排~~ → 合并进街机店后屋
- [x] ~~废品回收点~~ → 主街固定摊位 + 流动收废品车
- [ ] 秘密基地 C 的具体位置和进入方式
- [ ] 储蓄所是否真正参与零花钱系统(还是纯氛围)
- [ ] 服装店/鞋店是否有实际交互(买衣服/换装?还是纯氛围)
@@ -0,0 +1,281 @@
# 头脑风暴:灵感补充 + 城郊建筑与区域规划
**日期**:2026-06-10
**参与**:starmen1265 × 策划 Agent
**状态**:✅ 方向确认完毕
---
## 讨论一:灵感列表对比与新发现
用户提供了一份童年记忆灵感列表,对照已有三份头脑风暴记录进行逐一对比。
### 已有记录(不重复)
抓虫(乡下抓知了)、跳皮筋、呼啦圈、跳房子、石头剪刀布、警察抓小偷、一二三木头人、天线调台、日立电视、收音机、ADSL拨号上网、排球女将、大哥大、BP机、广播体操、广场舞、废旧报纸卖废品、少年包青天、菜地(迈皋桥地理元素)
### ⭐ 新发现元素
#### 乡下乡土活动(奶奶家阶段素材)
| # | 元素 | 灵感价值 |
|---|------|---------|
| 1 | 钓鱼(鱼竿,区别于"摸鱼") | ⭐⭐ 乡村独立小游戏潜力 |
| 2 | 游泳(夏天河里/水塘) | ⭐⭐ 场景事件+氛围 |
| 3 | 挤牛奶 | ⭐ 互动体验 |
| 4 | 喂鸡、掏鸡蛋 | ⭐⭐⭐ 高辨识度、互动感强 |
| 5 | 菜地浇水 | ⭐ 零花钱/好感度任务 |
| 6 | 打水漂 | ⭐⭐ 简单小互动,有物理乐趣 |
#### 小孩日常小游戏
| # | 元素 | 灵感价值 |
|---|------|---------|
| 7 | 双人拍手小游戏(互推游戏) | ⭐⭐ 简单但互动感极强 |
| 8 | 钻狗洞的秘密基地 | ⭐⭐⭐ **极佳的探索叙事素材** |
| 9 | 猜在哪个手里 | ⭐ 最简单的NPC互动小游戏 |
| 10 | 叠纸飞机(不同纸、不同技法) | ⭐⭐⭐ 收集+小游戏双潜力 |
| 11 | 跑步比赛 | ⭐ 简单的NPC互动触发 |
#### 人物与社交
| # | 元素 | 灵感价值 |
|---|------|---------|
| 12 | 爷爷奶奶家的各种年龄段亲戚小孩 | ⭐⭐⭐ 乡下NPC角色体系 |
| 13 | 北方来南方的同学 | ⭐⭐⭐ 与"转学生"角色互补 |
| 14 | 等着去别人家(串门文化) | ⭐⭐ 日常叙事素材 |
| 15 | 不知道名字的小哥哥跟你玩了一下午 | ⭐⭐⭐ **极高情感潜力的微型叙事线** |
#### 食物与零食
| # | 元素 | 灵感价值 |
|---|------|---------|
| 16 | 大大泡泡糖 | ⭐⭐ 品牌辨识度极高 |
| 17 | 秀逗(超酸的糖) | ⭐⭐ 互动素材——表情反应 |
| 18 | 无花果(丝) | ⭐ 零花钱消费场景填充 |
| 19 | 老鼠屎(盐津葡萄/话梅粒) | ⭐⭐ 名字本身就是小孩文化缩影 |
| 20 | 跳跳糖 | ⭐⭐ 视觉效果潜力 |
| 21 | 烤鱿鱼 | ⭐ 街边饮食扩展 |
| 22 | 烤里脊 | ⭐ 街边饮食扩展 |
| 23 | 炒饭和速冻水饺 | ⭐⭐ 与体力系统直接联动 |
#### 日常生活与物品
| # | 元素 | 灵感价值 |
|---|------|---------|
| 24 | 每天都有吃饭聊天时间 | ⭐⭐ 氛围事件+叙事窗口 |
| 25 | 小天鹅(洗衣机品牌) | ⭐ 时代品牌元素 |
| 26 | MP3(2000年前后刚开始出现) | ⭐⭐ 时代物品——比随身听更"潮" |
| 27 | 装修(邻居家在装修) | ⭐⭐ 场景细节(讨论后决定不做) |
| 28 | 墙上涂鸦 | ⭐ 收集/互动素材 |
| 29 | 十二生肖 | 待确认具体指什么 |
| 30 | 吃遍附近的美食 | ⭐⭐ 探索型目标 |
| 31 | 纸壳 | ⭐ 与叠纸飞机联动 |
| 32 | 西游记后传(2000年播出) | ⭐⭐ 电视内容补充 |
| 33 | 玉河摩托 | 待确认具体指什么 |
| 34 | 公交 | ⭐ 场景/移动手段 |
#### 游戏机制灵感
**体力系统雏形**:
> 吃饭补充体力,饭点之前体力无限。如果不吃饭,体力没跑几步就没了。
**动态家庭事件链**:
> 爸爸妈妈早上会给留两顿炒饭 → 如果都不吃,连续三天,会换成速冻水饺 → 如果还不吃,每天会多给两顿饭钱。
这两个设计同时做到了:还原真实 × 驱动行为 × 叙事表达。
#### 重点标记 ⭐⭐⭐
1. **钻狗洞的秘密基地** — 探索叙事
2. **叠纸飞机** — 收集+小游戏
3. **爷爷奶奶家的亲戚小孩** — 乡下NPC体系
4. **不知道名字的小哥哥** — 情感微型叙事
5. **体力系统+家庭动态事件** — 核心玩法机制
---
## 讨论二:城郊区域建筑与区域规划
基于全部已记录的活动/事件,讨论城市主场景应有哪些建筑与区域(不含乡下)。
### 确认的变更(相比现有场景设计)
| # | 变更项 | 原设计 | 新决定 |
|---|--------|--------|--------|
| 1 | 小伙伴家 | 全部不可进入 | 4 家可进入(简化室内) |
| 2 | 包机房 | 无 | 合并进街机店后屋 |
| 3 | 废品回收 | 无 | 固定点 + 流动收废品车 |
| 4 | 公交站 | 无 | 主街公交站牌(纯装饰) |
| 5 | 秘密基地 | 无 | 多处散布 |
| 6 | 棋牌室 | 无 | 新增 |
| 7 | 储蓄所 | 无 | 新增 |
| 8 | 菜地 | 无 | 不做(留乡下) |
| 9 | 装修 | 无 | 不做 |
| 10 | 游泳 | 无 | 河边不能下水 |
---
### 完整建筑与区域清单
#### 一、商业主街(东西走向,~70m x 5-6m)
**东侧(传统生活)**
| # | 名称 | 尺寸 | 可进入 | 核心功能 | 备注 |
|---|------|------|--------|---------|------|
| S1 | 合并小卖部 | 8m x 5m | ✅ | 零食/文具/四驱配件/经济核心 | 后半部分是老板儿子家(简化室内) |
| S2 | 书店 | 5m x 4m | ✅ | 漫画/书籍租借 | |
| S3 | 粮油店 | 小型 | ✅ | 米面油酱醋 | |
| S4 | 服装店/裁缝铺 | 小型 | 简化 | 氛围 + 可能买衣服 | |
| S5 | 鞋店 | 小型 | 简化 | 氛围 + 运动鞋梦想 | |
**西侧(娱乐商业)**
| # | 名称 | 尺寸 | 可进入 | 核心功能 | 备注 |
|---|------|------|--------|---------|------|
| S6 | 街机店 | 8m x 6m | ✅ | 街机小游戏 + 社交 | 含包机房后屋(门帘隔着) |
| S7 | 网吧 | 6m x 5m | ✅ | PC 小游戏 + 社交 | |
| S8 | 录像出租点 | 5m x 4m | ✅ | 录像带/磁带租借 | 只出租,无观影厅 |
| S9 | 储蓄所 | 小型 | 简化 | 存取零花钱? | |
| S10 | 棋牌室 | 小型 | ❌ | 纯氛围(大人的场所) | |
**主街设施(散布)**
| # | 名称 | 性质 |
|---|------|------|
| S11 | 公交站牌 | 纯装饰,公交经过不可乘坐 |
| S12 | 废品回收点 | 路边固定摊位,可交互卖废品 |
| S13 | 街边摊贩 × N | 散布式(烤鱿鱼、烤里脊、冰棍推车、烤肠等) |
**主街尽端**
| # | 名称 | 尺寸 | 可进入 |
|---|------|------|--------|
| S14 | 学校操场 | 25m x 18m | 仅室外 |
#### 二、生活区巷子(主街北侧,南北纵深)
**中部 — 事业单位家属楼**
| # | 名称 | 性质 | 备注 |
|---|------|------|------|
| R1 | 主角家 | 6 层家属楼,完整室内(两室一厅) | 起点终点、FC 游戏、电视、电话 |
| R2 | 女班长家 | 同栋楼,简化室内 | 同楼不同层 |
| R3 | 信报箱 | 楼下设施 | 氛围 + 可能收信/通知 |
**东侧 — 小区**
| # | 名称 | 性质 | 备注 |
|---|------|------|------|
| R4 | 小区 | 3-4 栋 6 层楼 + 中庭空地 | 死党 A 住这里 |
| R5 | 死党 A 家 | 小区内某栋,简化室内 | 家境好,有四驱车跑道 |
| R6 | 自行车棚 | 小区内设施 | 氛围 |
**西侧 — 平房区**
| # | 名称 | 性质 | 可交互 | 备注 |
|---|------|------|--------|------|
| R7 | 平房区 | 十几二十间平房 + 窄巷子 | — | 死党 B 住这里 |
| R8 | 死党 B 家 | 平房区内某间,简化室内 | ✅ | 家境差,温馨 |
| R9 | 早餐店 | 平房区沿街 | ✅ 买早点 | 包子油条豆浆 |
| R10 | 理发店 | 平房区小店 | 简化对话 | 老式转椅+镜子 |
| R11 | 修车铺 | 平房区小店 | 简化对话 | 门口堆轮胎零件 |
| R12 | 缝纫店 | 平房区小店 | 简化对话 | 缝纫机响 |
| R13 | 公共厕所 | 平房区内 | ❌ | 城郊生活标志 |
**巷子通道**
| # | 名称 | 备注 |
|---|------|------|
| R14 | 巷子口 × 2-3 | 主街与生活区的连接口 |
#### 三、边缘区域(北端)
| # | 名称 | 尺寸 | 备注 |
|---|------|------|------|
| E1 | 大型空地/工地 | ~40m x 30m | 一半空地(弹珠主战场),一半工地(围挡) |
| E2 | 河边/河堤 | ~50m x 8m | 不能下水,氛围+打水漂 |
#### 四、隐藏区域
| # | 名称 | 位置 | 进入方式 |
|---|------|------|---------|
| H1 | 秘密基地 A | 工地围挡后面 | 钻洞 |
| H2 | 秘密基地 B | 平房区死角 | 穿窄巷 |
| H3 | 秘密基地 C | 待定 | 待定 |
#### 五、移动元素
| # | 名称 | 路线 | 功能 |
|---|------|------|------|
| M1 | 流动收废品车 | 巷子内巡游 | 卖废品 |
| M2 | 街边摊贩 | 主街散布 | 零食/小吃 |
---
### 主街店铺分布(东西排列)
```
← 东(生活) 西(娱乐)→
┌──────────────────────────────────────────────────────────────────────┐
│ [学校操场] [小卖部] [粮油店] [书店] [服装店] [鞋店] · 公交站 · [棋牌室] [储蓄所] [录像出租] [网吧] [街机店] │
│ ↑ 废品回收点(路边) ↑ 摊贩散布 │
└──────────────────────────────────────────────────────────────────────┘
```
---
### 可进入室内完整清单
| # | 地点 | 规模 | 关联 NPC |
|---|------|------|---------|
| 1 | 主角家 | 完整(两室一厅) | — |
| 2 | 合并小卖部 | 完整 | 老板儿子 |
| 3 | 书店 | 中等 | — |
| 4 | 街机店(含包机房后屋) | 中等 | 小霸王 |
| 5 | 网吧 | 中等 | — |
| 6 | 录像出租点 | 中等 | — |
| 7 | 死党 A 家 | 简化(小空间+对话) | 死党 A |
| 8 | 死党 B 家 | 简化 | 死党 B |
| 9 | 老板儿子家 | 简化(小卖部后屋) | 老板儿子 |
| 10 | 女班长家 | 简化(主角家同楼) | 女班长 |
---
### 讨论共识记录
1. **菜地**:城市场景不做,留给乡下阶段
2. **秘密基地**:多处散布,小型空间(工地围挡后、平房区死角、至少 2-3 处)
3. **小伙伴家**:4 家可进入,全部简化室内(小空间 + 对话为主)
- 死党 A 家 → 小区内
- 死党 B 家 → 平房区内
- 老板儿子家 → 小卖部后屋
- 女班长家 → 主角家同楼不同层
4. **录像厅**:只做出租点,不做观影厅
5. **包机房**:合并进街机店后屋
6. **废品回收**:主街固定摊位 + 流动收废品车(巷子巡游)
7. **装修**:不做
8. **公交站**:纯装饰站牌
9. **摊贩**:散布式
10. **游泳**:河边不能下水
11. **新增店铺**:粮油店、服装店/裁缝铺、鞋店 → 都放主街
12. **新增设施**:棋牌室(大人场所/纯氛围)、储蓄所、公共厕所、自行车棚、信报箱
13. **平房区小店**:早餐店(可交互)、理发店/修车铺/缝纫店(背景+对话)
---
### 统计
| 类别 | 数量 |
|------|------|
| 商业主街店铺/设施 | 14 个 |
| 生活区建筑/设施 | 14 个 |
| 边缘区域 | 2 个 |
| 隐藏区域 | 多处 |
| 移动元素 | 2 类 |
| **可进入室内** | ~12 个 |
### 与概念草案的关系
概念草案定义「可进入室内:主角家 + 4 家店铺」。新设计变更为 6 家店铺 + 4 家小伙伴家(简化)+ 主角家 = ~12 个可进入室内。这是扩展,不与概念草案冲突。概念草案冻结,变更记录在系统设计案中。
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,224 @@
# 交互系统 · 拾取流程时序图(v1.0 Effect 驱动版)
## 场景
玩家进入地上某个交互物的交互范围,按 E 交互后,人物做动作,交互物消失,背包加一个新物品,UI 显示捡到一个物品。
## 架构层级
| 层级 | 实体 | 所在位置 |
|------|------|---------|
| **Logic** | `FInteractionManager` | ASystemDriver 持有 (普通C++, 纯事件驱动, 缓存 Presentation 数据) |
| **Logic** | `FEffectManager` | ASystemDriver 持有 (普通C++, Driver 提供Tick) |
| **World** | `ASystemDriver` | AInfo 全局唯一 (持有所有Manager, 显式控制创建/Tick顺序) |
| **Data** | `UInteractableComponent` | 关卡实例 |
| **Data** | `UPersistenceSubsystem` | GameInstanceSubsystem |
| **Data** | `BackpackSystem` | GameInstance |
| **Presentation** | `UInteractionPromptComponent` | Character |
## 数据流规则
- 表现层直接读数据层(Manager state / Component data / Backpack data)
- 逻辑事件仅在 A 影响 B 时使用(如 Consume Action → Persistence 写入)
- 两个表现实体之间不直接访问
---
## Effect 配置
```
Effect: "拾取地面物品"
TotalDuration: 1.5s, Cancellable: false
TriggerTime | Action | 说明
----------- | --------------------- | ----
0.0s | TurnToTarget | TurnDuration=0.2,转向交互物
0.0s | PlayMontage | Montage=AM_Pickup,弯腰拾取动画
0.6s | Consume | 标记消耗,禁用检测球
0.6s | HideActor | 隐藏交互物 Actor
0.6s | AddToBackpack | 物品进背包 + 持久化
1.0s | ShowPickupNotification| 显示 "捡到了 [旧橡皮]!"
```
---
## 时序图
```mermaid
sequenceDiagram
autonumber
actor Player as 玩家
participant Char as AChildHoodCharacter
participant Mgr as FInteractionManager<br/>(普通C++ · ASystemDriver持有)
participant Comp as UInteractableComponent<br/>(Data · 关卡实例)
participant Actor as AInteractableActor
participant EFM as FEffectManager<br/>(普通C++ · ASystemDriver持有)
participant Persist as UPersistenceSubsystem<br/>(Data · GameInstance)
participant Bag as BackpackSystem
participant Pres as UInteractionPromptComponent<br/>(Presentation · Character)
participant Driver as ASystemDriver<br/>(AInfo · 持有所有Manager)
rect rgb(230, 245, 255)
Note over Player,Driver: ═══ Phase 0 · 进入检测范围 ═══
Player->>Char: WASD 移动进入 TriggerRadius
Comp->>Mgr: DetectionSphere OnOverlapBegin<br/>ASystemDriver::Get(World)->GetInteractionManager().RegisterInteractable(this)
Mgr->>Mgr: NearbyInteractables.Add(Comp)<br/>Sort by Dist2D → SelectedIndex = 0
Mgr->>Mgr: RefreshCachedSelection()<br/>缓存 PromptText + WorldPosition
end
rect rgb(255, 250, 230)
Note over Player,Driver: ═══ Phase 1 · Prompt 显示(Pres 每帧读 Manager 缓存)═══
loop 每帧 Pres TickComponent
Pres->>Mgr: GetState() → Free
Pres->>Mgr: HasSelection() → true
Pres->>Mgr: GetSelectedPromptText() → "[E] 拾取 旧橡皮"
Pres->>Mgr: GetSelectedWorldPosition() → FVector
Pres->>Pres: PromptWidget.SetText + SetScreenPosition
end
Note right of Pres: Pres 不碰 Component<br/>只读 Manager 缓存
end
rect rgb(230, 255, 230)
Note over Player,Driver: ═══ Phase 2 · 按 E,Character 通过 Driver 协调两个 Manager ═══
Player->>Char: 按 E (IA_Interact)
Char->>Mgr: Driver->GetInteractionManager().BeginInteract() → 返回 Comp
Mgr->>Mgr: State = Executing, CurrentInteractable = Comp
Char->>Char: SetInteracting(true) → 屏蔽移动
Char->>EFM: Driver->GetEffectManager().PlayEffect(Comp.Data.InteractEffect, Context)<br/>Context = {Char, Comp, Actor}
Note right of EFM: Effect "拾取地面物品"<br/>T=0.0 TurnToTarget + PlayMontage<br/>T=0.6 Consume+Hide+Backpack<br/>T=1.0 PickupNotification<br/>Total=1.5s
end
rect rgb(255, 240, 245)
Note over Player,Driver: ═══ Phase 3 · Presentation 检测状态变化 ═══
Pres->>Mgr: Tick: GetState() → Executing (变化!)
Pres->>Pres: PromptWidget.Hide()
end
rect rgb(240, 240, 255)
Note over Player,Driver: ═══ Phase 4 · Effect 执行(ASystemDriver Tick → EffectManager)═══
Note over Driver: T=0.0: TurnToTarget + PlayMontage
Driver->>EFM: Driver::Tick → EffectManager.Update(DeltaTime)
EFM->>EFM: ElapsedTime=0.0 → 触发 TurnToTarget Action
EFM->>Char: RInterpTo 转向 Comp(持续帧)
EFM->>EFM: 触发 PlayMontage Action
EFM->>Char: GetMesh()->GetAnimInstance()->Montage_Play(AM_Pickup)
Note right of Char: 播放弯腰拾取动画
Note over Driver: T=0.0→0.6: 持续转向 + 动画播放
loop 每帧 Driver Tick
Driver->>EFM: Tick → Update(DeltaTime)
EFM->>EFM: ElapsedTime += DeltaTime
EFM->>Char: TurnToTarget: RInterpTo 插值
end
Note over Driver: T=0.6: Consume + HideActor + AddToBackpack
Driver->>EFM: Tick → Update → ElapsedTime=0.6 触发
EFM->>Comp: Consume() — 数据变更
Comp-->>Comp: bIsConsumed=true<br/>DetectionSphere → NoCollision
Note right of Comp: 禁用碰撞同步触发 EndOverlap<br/>→ UnregisterInteractable<br/>Manager 标记延迟清理
EFM->>Persist: MarkItemCollected("rubber_01")
Persist-->>Persist: CollectedItemIDs.Add("rubber_01")
EFM->>Actor: SetActorHiddenInGame(true)
EFM->>Bag: AddItem("rubber_01", "旧橡皮")
Bag-->>Bag: Items.Add({Id:"rubber_01", Name:"旧橡皮"})
Pres->>Comp: Tick: IsConsumed() → true (变化!)
Pres->>Actor: SetActorHiddenInGame(true)
Note over Driver: T=1.0: ShowPickupNotification
Driver->>EFM: Tick → Update → ElapsedTime=1.0 触发
EFM->>Pres: 显示拾取通知 "捡到了 [旧橡皮]!"
end
rect rgb(230, 255, 255)
Note over Player,Driver: ═══ Phase 5 · Effect 完成,回到 Free ═══
Driver->>EFM: Tick → Update → ElapsedTime >= TotalDuration(1.5s)
EFM->>EFM: FinishEffect()
EFM->>Mgr: OnEffectComplete 回调
Mgr->>Mgr: State = Free, CurrentInteractable = nullptr
Mgr->>Char: SetInteracting(false) → 恢复移动
Pres->>Mgr: Tick: GetState() → Free (变化!)
Pres->>Mgr: GetSelectedInteractable() → null (已注销)
Note over Pres: 无其他交互物在范围,不显示 Prompt
Player->>Char: WASD 继续移动
end
```
---
## 关键数据流说明
### 表现层读数据(只读 Manager 缓存,不碰 Component)
| 读取时机 | PromptComponent 读谁 | 读什么 |
|----------|---------------------|--------|
| 每帧 Tick | Manager | `GetState()` |
| Free 状态 | Manager | `HasSelection()` |
| Free 状态 | Manager | `GetSelectedPromptText()` |
| Free 状态 | Manager | `GetSelectedWorldPosition()` |
### Manager 缓存更新(选择变化时从 Component 读一次)
| 触发时机 | Manager 方法 | 缓存内容 |
|----------|-------------|---------|
| RegisterInteractable | RefreshCachedSelection() | PromptText + WorldPosition |
| UnregisterInteractable | RefreshCachedSelection() | PromptText + WorldPosition |
| SwitchTarget | RefreshCachedSelection() | PromptText + WorldPosition |
### Effect 内 Action 执行(直接操作)
| Action | 操作对象 | 说明 |
|--------|---------|------|
| TurnToTarget | Char->SetActorRotation | RInterpTo 跨帧插值 |
| PlayMontage | Char->GetMesh()->GetAnimInstance() | 播放蒙太奇 |
| Consume | Component->Consume() | 标记消耗+禁用检测 |
| HideActor | Actor->SetActorHiddenInGame(true) | 隐藏 |
| AddToBackpack | PersistenceSubsystem + BackpackSystem | 写入持久层+背包 |
| ShowPickupNotification | PromptComponent / Widget | 显示通知 |
### 逻辑事件(仅 A 影响 B)
| 事件 | 触发者 | 订阅者 |
|------|--------|--------|
| OnItemConsumed | Manager(由 Consume Action 触发) | PersistenceSubsystem |
| OnDoorToggled | Manager(由 ToggleDoor Action 触发) | PersistenceSubsystem + DoorComponent |
---
## 注意事项
### EndOverlap 时序问题
Phase 4 中 `Consume()` 禁用 DetectionSphere 会**同步触发** `EndOverlap`,进而调用 `Manager->UnregisterInteractable()`。此时 Manager 处于 Executing 状态且 Effect 仍在使用 CurrentInteractable。
**解决方案**:Manager 的 `UnregisterInteractable` 中,当 `State == Executing` 且移除的正是 `CurrentInteractable` 时,仅从 NearbyInteractables 列表移除,**不清除 CurrentInteractable 引用**,等 Effect 完成回调中统一清理。
### ASystemDriver 生命周期
- `ASystemDriver` 是 AInfo 全局唯一,持有所有 World 级 Manager(普通 C++ 对象)
- 关卡加载完成 → `GameInstance` Spawn `ASystemDriver`
- `Driver::BeginPlay` 显式创建 `FInteractionManager` + `FEffectManager`,控制顺序
- `Driver::BeginPlay` 中用 `TFunction` 连线 `EffectManager.OnEffectComplete → Manager.OnInteractionFinished`
- `Driver::Tick` 显式调用 `EffectManager.Update(Delta)`,控制 Tick 顺序
- 关卡卸载 → World 销毁 → Driver 自动销毁 → Manager 随 Driver 析构
- 新关卡加载 → 重新 Spawn + 创建
### 系统协调关系
- `FInteractionManager` 和 `FEffectManager` 是普通 C++ 类,由 `ASystemDriver` 持有
- `AChildHoodCharacter` 输入回调通过 `ASystemDriver::Get(World)` 访问两个 Manager
- `Driver::BeginPlay` 中通过 `TFunction<void()>` 连接 EffectManager 完成回调到 Manager
- `FInteractionManager` 纯事件驱动,无 Tick/Update
- `UChildHoodGameInstance` 不持有任何交互系统对象,仅 `UPersistenceSubsystem` 跨 World 存活
### TurnToTarget 跨帧插值
TurnToTarget Action 不是瞬时完成的。EffectManager 在 Update 中维护 `FTurnState`(起始旋转、目标旋转、已用时间),每帧调用 `FMath::RInterpTo` 直到转向完成。多个 Action 可以并行执行。
+162 -17
View File
@@ -2,8 +2,8 @@
> 任务ID: P-009
> 对应执行案: `docs/design/收集系统-执行案.md` (v0.1.1)
> 关联系统: 物品系统(上游单向接收)、交互系统(间接,通过物品系统委托)
> 日期: 2026-06-07
> 关联系统: 物品系统(上游单向接收)、交互系统(间接,通过物品系统委托)、GameInstance 持久化层
> 日期: 2026-06-07(2026-06-11 更新:加入多场景持久化架构)
---
@@ -15,6 +15,16 @@
**预估工期**:2-3 天
**新增/修改代码量**:约 250-350 行 C++ + 3 个 UMG 蓝图
### 跨场景持久化架构
> 收集进度是玩家核心持久数据。当前设计中 `UCollectionManager` 将「行为控制」(UI、通知、动画)和「持久数据」(CollectedIDs、Progress)混在同一组件,随 Character 销毁而丢失。需要通过数据层分离解决:
- **持久数据层**:收集状态(CollectedIDs、Progress、bIsComplete)存储在 `UChildHoodGameInstance` 中(跨关卡存活)
- **行为层**:`UCollectionManager` 仅负责运行时 UI 控制和委托监听,BeginPlay 时从 GameInstance 恢复状态
- **交互物恢复**:场景中的已收集物品由交互系统通过 `InteractableComponent::RestorePersistentState()` 处理(见交互系统技术方案第 12 节)
v0.1 为单场景原型,此架构以接口预留形式存在,不影响当前功能。详见 [第 12 节:跨场景持久化设计](#12-跨场景持久化设计)。
---
## 1. 需求功能点罗列
@@ -123,6 +133,17 @@ classDiagram
-CheckCompletion() void
-ShowCompletionPrompt() void
-UpdateProgressUI() void
-LoadStateFromGameInstance() void
-SaveStateToGameInstance() void
}
class UChildHoodGameInstance {
-TSet~FName~ CollectedItemIDs
-int32 CollectionProgress
-bool bCollectionComplete
+GetCollectionState(TSet_FName_&, int32&, bool&) void
+SetCollectionState(TSet_FName_&, int32, bool) void
+ResetAll() void
}
class UCollectionNotifWidget {
@@ -175,6 +196,7 @@ classDiagram
AChildHoodCharacter *-- UCollectionManager : 组合
UCollectionManager --> UInteractionManager : 关联(绑定委托)
UCollectionManager ..> UChildHoodGameInstance : 读取/写入持久状态
UCollectionManager o-- UCollectionNotifWidget : 聚合(管理生命周期)
UCollectionManager o-- UCollectionProgressWidget : 聚合(管理生命周期)
UCollectionManager ..> UCollectionCompleteWidget : 按需创建
@@ -188,7 +210,7 @@ classDiagram
| 组合 `*--` | 生命周期绑定 | Character 持有 CollectionManager;CollectionManager 内含 Config |
| 聚合 `o--` | 引用但非拥有 | CollectionManager 管理通知/进度 Widget 的创建和销毁 |
| 关联 `-->` | 持久引用 | CollectionManager 绑定 InteractionManager 的委托 |
| 依赖 `..>` | 临时使用 | CollectionManager 按需创建完成提示 Widget |
| 依赖 `..>` | 临时使用 | CollectionManager 从 GameInstance 读取/写入持久状态;按需创建完成提示 Widget |
### 新增类
@@ -489,6 +511,8 @@ void UCollectionManager::ProcessNewCollection(FName ItemId)
// 步骤 3: 记录
CollectedIDs.Add(ItemId);
Progress = CollectedIDs.Num(); // 用 Set 大小作为权威进度值,避免状态不一致
// 持久化到 GameInstance
SaveStateToGameInstance();
```
---
@@ -519,6 +543,7 @@ void UCollectionManager::ProcessNewCollection(FName ItemId)
if (Progress >= Config.TotalCount && !bIsComplete)
{
bIsComplete = true;
SaveStateToGameInstance(); // 持久化完成状态
CheckCompletion(); // 触发完成表现
}
}
@@ -637,10 +662,8 @@ void UCollectionManager::BeginPlay()
{
Super::BeginPlay();
// 初始化状态
Progress = 0;
CollectedIDs.Empty();
bIsComplete = false;
// 从 GameInstance 恢复持久状态(v0.1 单场景等效于空数据初始化)
LoadStateFromGameInstance();
// 创建进度 UI
if (ProgressWidgetClass)
@@ -652,7 +675,12 @@ void UCollectionManager::BeginPlay()
if (ProgressWidget)
{
ProgressWidget->AddToViewport(5);
ProgressWidget->UpdateProgress(0, Config.TotalCount);
ProgressWidget->UpdateProgress(Progress, Config.TotalCount);
// 已完成时恢复完成样式
if (bIsComplete)
{
ProgressWidget->SetCompleteStyle();
}
}
}
}
@@ -772,20 +800,34 @@ void UCollectionManager::ShowCompletionPrompt()
#### 重启状态处理
**实现**:v0.1 无持久化。CollectionManager::BeginPlay() 中完全重置:
**实现**:收集状态通过 `UChildHoodGameInstance` 跨场景持久化。CollectionManager::BeginPlay() 中从 GameInstance 恢复:
```cpp
void UCollectionManager::BeginPlay()
void UCollectionManager::LoadStateFromGameInstance()
{
Super::BeginPlay();
Progress = 0;
CollectedIDs.Empty();
bIsComplete = false;
// ... 创建 UI、绑定委托
if (UChildHoodGameInstance* GI = GetWorld()->GetGameInstance<UChildHoodGameInstance>())
{
GI->GetCollectionState(CollectedIDs, Progress, bIsComplete);
}
else
{
// 回退:无 GameInstance 时初始化为空(编辑器预览等场景)
Progress = 0;
CollectedIDs.Empty();
bIsComplete = false;
}
}
void UCollectionManager::SaveStateToGameInstance()
{
if (UChildHoodGameInstance* GI = GetWorld()->GetGameInstance<UChildHoodGameInstance>())
{
GI->SetCollectionState(CollectedIDs, Progress, bIsComplete);
}
}
```
重启后进度 UI 恢复显示 `📦 杂物 0/10`。
v0.1 单场景下:GameInstance 以空数据初始化,等效于 `📦 杂物 0/10`。场景内正常收集后状态写入 GameInstance,若通过 Seamless Travel 切换关卡则自动恢复。非无缝跳转(OpenLevel)时 GameInstance 仍存活,新场景的 CollectionManager 可读取之前的数据。
#### 收集通知与拾取通知时序
@@ -819,6 +861,7 @@ void UCollectionManager::BeginPlay()
### 注意事项
1. **委托绑定时机**:CollectionManager 在 BeginPlay() 中通过 `GetOwner()->GetComponentByClass<UInteractionManager>()` 获取引用并绑定。需确保 InteractionManager 已初始化。若为 nullptr 则在 Tick 中延迟绑定(最多重试 N 帧后报错)。
2. **持久化读写时机**:`SaveStateToGameInstance()` 在每次 ProcessNewCollection 记录后和完成判定后调用,保证 GameInstance 中的数据始终最新。`LoadStateFromGameInstance()` 仅在 BeginPlay 中调用一次。
2. **v0.1 无拾取通知 UI**:执行文档中拾取通知由物品系统负责,但 v0.1 物品系统仅为委托层(InteractionManager 上的委托)。收集通知的 0.3s 延迟仍按设计实现(为拾取通知预留时间窗口),即使拾取通知 UI 暂未实现。
3. **通知移除回调**:UCollectionNotifWidget 淡出完成后需通知 CollectionManager 从 ActiveNotifications 中移除自身引用。使用委托或弱引用回调实现,避免循环引用。
4. **FName 作为 ID**:收集系统使用 FName(ItemId)作为去重键,与 InteractionManager 委托广播的 ItemId 类型一致。
@@ -847,14 +890,33 @@ InteractionManager (ExecuteAnimEvent)
├─ 查品类 (Category == TrackedCategory?) → 不匹配则忽略
├─ 查重复 (CollectedIDs.Contains?) → 已存在则忽略
├─ 记录 (CollectedIDs.Add + Progress = Num())
├─ SaveStateToGameInstance() → UChildHoodGameInstance (持久化)
├─ UpdateProgressUI() → WBP_CollectionProgress (文本 + 弹性动画)
├─ SetTimer(NotifDelay) → ShowCollectionNotification() → WBP_CollectionNotification
└─ 完成判定 (Progress >= TotalCount)
→ bIsComplete = true
→ SaveStateToGameInstance() (持久化完成状态)
→ SetCompleteStyle() → WBP_CollectionProgress (金色)
→ SetTimer(TotalDelay) → ShowCompletionPrompt() → WBP_CollectionComplete
```
### 跨场景恢复流
```
场景切换 (OpenLevel / Seamless Travel)
→ 新场景加载
→ AChildHoodCharacter::BeginPlay()
→ UCollectionManager::BeginPlay()
→ LoadStateFromGameInstance()
→ UChildHoodGameInstance::GetCollectionState(OutIDs, OutProgress, OutComplete)
→ 恢复 CollectedIDs / Progress / bIsComplete
→ 创建进度 UI(显示已恢复的进度值)
→ 若 bIsComplete → 恢复完成样式
→ 场景中 AInteractableActor::BeginPlay()
→ UInteractableComponent::RestorePersistentState(GI)
→ GI->IsItemCollected(ItemId) → 已收集则隐藏 + 禁用
```
---
## 10. 可调参数汇总
@@ -886,10 +948,11 @@ InteractionManager (ExecuteAnimEvent)
Source/ChildHood/
├── Collection/
│ ├── CollectionTypes.h # FCollectionConfig 结构体定义
│ ├── CollectionManager.h/.cpp # 收集系统核心组件
│ ├── CollectionManager.h/.cpp # 收集系统核心组件(含 GameInstance 读写)
│ ├── CollectionNotifWidget.h/.cpp # 收集通知 UI 基类
│ ├── CollectionProgressWidget.h/.cpp # 收集进度 UI 基类
│ └── CollectionCompleteWidget.h/.cpp # 完成提示 UI 基类
├── ChildHoodGameInstance.h/.cpp # 新增:跨关卡持久数据持有者(收集状态在此持久化)
├── ChildHoodCharacter.h/.cpp # 修改:添加 CollectionManager 成员和访问器
Content/
@@ -928,3 +991,85 @@ Content/
> **关键路径**:6b-1 → 6b-2 → 6b-4 → 6b-5 → 6b-6。6b-3(进度 UI)可在 6b-1 完成后并行。总关键路径约 3 天。
> **前置依赖**:交互系统的 InteractionManager 组件需已完成(特别是 OnItemPickedUp/OnItemFound 委托),方可进行 6b-2 的委托绑定测试。
---
## 12. 跨场景持久化设计
> 收集进度是玩家的核心持久数据,必须在场景切换时保留。本节定义收集系统如何通过 `UChildHoodGameInstance` 实现跨场景数据持久化。
### 架构原则
```
┌──────────────────────────────────────────────────┐
│ UChildHoodGameInstance │ ← 跨关卡存活
│ ├── CollectedItemIDs: TSet<FName> │ 所有场景共享
│ ├── CollectionProgress: int32 │
│ └── bCollectionComplete: bool │
└──────────────────────────────────────────────────┘
↕ BeginPlay 加载 / 收集时回写
┌──────────────────────────────────────────────────┐
│ UCollectionManager (每个场景重建) │ ← 行为层
│ ├── 从 GameInstance 读取恢复进度 │ 不持有持久数据所有权
│ ├── 收集时写入 GameInstance │
│ └── 控制 UI / 通知 / 动画 │
└──────────────────────────────────────────────────┘
↕ 场景恢复
┌──────────────────────────────────────────────────┐
│ 场景中的 AInteractableActor │ ← 由交互系统负责恢复
│ └── RestorePersistentState() │ (见交互系统技术方案第 12 节)
└──────────────────────────────────────────────────┘
```
### UChildHoodGameInstance 收集相关接口
```cpp
UCLASS()
class UChildHoodGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
// === 收集状态 ===
void GetCollectionState(TSet<FName>& OutIDs, int32& OutProgress, bool& OutComplete) const;
void SetCollectionState(const TSet<FName>& IDs, int32 Progress, bool bComplete);
// === 物品消耗状态(交互系统也使用)===
bool IsItemCollected(FName ItemId) const;
void MarkItemCollected(FName ItemId);
// === 门状态(交互系统也使用)===
bool IsDoorOpen(FName DoorId) const;
void SetDoorOpen(FName DoorId, bool bIsOpen);
// === 重置 ===
void ResetAll();
private:
// 收集系统
TSet<FName> CollectedItemIDs;
int32 CollectionProgress = 0;
bool bCollectionComplete = false;
// 交互系统
TMap<FName, bool> DoorStates;
};
```
> **注意**:`CollectedItemIDs` 同时被 `IsItemCollected()`(交互物恢复)和 `GetCollectionState()`(收集进度恢复)使用,确保两系统数据一致性。
### 读写时机
| 时机 | 方法 | 说明 |
|------|------|------|
| CollectionManager::BeginPlay | `LoadStateFromGameInstance()` | 场景加载时恢复进度 |
| ProcessNewCollection 记录后 | `SaveStateToGameInstance()` | 每次新收集时同步写入 |
| 完成判定后 | `SaveStateToGameInstance()` | 完成状态立即持久化 |
| InteractableComponent::Consume | `GI->MarkItemCollected()` | 交互物消耗状态写入(由交互系统调用) |
### v0.1 行为
- v0.1 为单场景原型,不存在跨场景切换
- `UChildHoodGameInstance` 在 v0.1 中以空数据初始化,所有查询返回默认值
- `LoadStateFromGameInstance()` 等效于初始化为 0
- 后续多场景版本只需在 Project Settings 中将 GameInstance Class 设为 `UChildHoodGameInstance`,收集系统代码无需修改
- 后续版本可通过 USaveGame 将 GameInstance 数据序列化到磁盘,实现跨会话持久化
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,52 @@
{
"subfolder": "AutomaticDoor",
"source_path": "/Game/BlockingStarterPack/Blueprints/AutomaticDoor",
"actor_blueprint_count": 0,
"non_actor_count": 6,
"completed": true,
"actor_blueprints": {},
"skipped_non_actor": [
{
"name": "B_AutoDoor",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_AutoDoorDouble",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_AutoLateralDoor",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_AutoLateralDoorDouble",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_BaseAutoDoor",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "DoorParts_Struct",
"ue_class_type": "UserDefinedStruct",
"note": [
"非 Actor 类型: UserDefinedStruct"
]
}
]
}
@@ -0,0 +1,38 @@
{
"subfolder": "AutomaticDoor/Curves",
"source_path": "/Game/BlockingStarterPack/Blueprints/AutomaticDoor/Curves",
"actor_blueprint_count": 0,
"non_actor_count": 4,
"completed": true,
"actor_blueprints": {},
"skipped_non_actor": [
{
"name": "Curve_DoorBreakOpen",
"ue_class_type": "CurveFloat",
"note": [
"非 Actor 类型: CurveFloat"
]
},
{
"name": "Curve_DoorClose",
"ue_class_type": "CurveFloat",
"note": [
"非 Actor 类型: CurveFloat"
]
},
{
"name": "Curve_DoorOpen",
"ue_class_type": "CurveFloat",
"note": [
"非 Actor 类型: CurveFloat"
]
},
{
"name": "Curve_DoorRapidClose",
"ue_class_type": "CurveFloat",
"note": [
"非 Actor 类型: CurveFloat"
]
}
]
}
@@ -0,0 +1,122 @@
{
"subfolder": "AutomaticDoor/Sounds",
"source_path": "/Game/BlockingStarterPack/Blueprints/AutomaticDoor/Sounds",
"actor_blueprint_count": 0,
"non_actor_count": 16,
"completed": true,
"actor_blueprints": {},
"skipped_non_actor": [
{
"name": "BigDoor_Closing01",
"ue_class_type": "SoundWave",
"note": [
"非 Actor 类型: SoundWave"
]
},
{
"name": "BigDoor_Closing02",
"ue_class_type": "SoundWave",
"note": [
"非 Actor 类型: SoundWave"
]
},
{
"name": "BigDoor_Closing_Cue",
"ue_class_type": "SoundCue",
"note": [
"非 Actor 类型: SoundCue"
]
},
{
"name": "BigDoor_Moving01",
"ue_class_type": "SoundWave",
"note": [
"非 Actor 类型: SoundWave"
]
},
{
"name": "BigDoor_Moving_Cue",
"ue_class_type": "SoundCue",
"note": [
"非 Actor 类型: SoundCue"
]
},
{
"name": "BigDoor_StartOpening01",
"ue_class_type": "SoundWave",
"note": [
"非 Actor 类型: SoundWave"
]
},
{
"name": "BigDoor_StartOpening01_Cue",
"ue_class_type": "SoundCue",
"note": [
"非 Actor 类型: SoundCue"
]
},
{
"name": "DoorSoundAttenuation",
"ue_class_type": "SoundAttenuation",
"note": [
"非 Actor 蓝图: SoundAttenuation"
]
},
{
"name": "SimpleDoor_Closing01",
"ue_class_type": "SoundWave",
"note": [
"非 Actor 类型: SoundWave"
]
},
{
"name": "SimpleDoor_Closing_Cue",
"ue_class_type": "SoundCue",
"note": [
"非 Actor 类型: SoundCue"
]
},
{
"name": "SimpleDoor_StarClosing01",
"ue_class_type": "SoundWave",
"note": [
"非 Actor 类型: SoundWave"
]
},
{
"name": "SimpleDoor_StarClosing01_Cue",
"ue_class_type": "SoundCue",
"note": [
"非 Actor 类型: SoundCue"
]
},
{
"name": "SimpleDoor_StarClosing02",
"ue_class_type": "SoundWave",
"note": [
"非 Actor 类型: SoundWave"
]
},
{
"name": "SimpleDoor_StartOpening01",
"ue_class_type": "SoundWave",
"note": [
"非 Actor 类型: SoundWave"
]
},
{
"name": "SimpleDoor_StartOpening02",
"ue_class_type": "SoundWave",
"note": [
"非 Actor 类型: SoundWave"
]
},
{
"name": "SimpleDoor_StartOpening_Cue",
"ue_class_type": "SoundCue",
"note": [
"非 Actor 类型: SoundCue"
]
}
]
}
+122
View File
@@ -0,0 +1,122 @@
{
"subfolder": "Decals",
"source_path": "/Game/BlockingStarterPack/Blueprints/Decals",
"actor_blueprint_count": 0,
"non_actor_count": 16,
"completed": true,
"actor_blueprints": {},
"skipped_non_actor": [
{
"name": "B_CircleZoneDecal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_OctagonZoneDecal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_ShapeArrow_Decal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_ShapeCross_Decal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_ShapeLess_Decal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_ShapeSquare_Decal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_SquareZone_Decal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_SquareZoneBlue_Decal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_SquareZoneGreen_Decal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_SquareZoneRed_Decal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_SquareZoneYellow_Decal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_SripeZoneBlue_Decal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_SripeZoneGreen_Decal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_SripeZoneRed_Decal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_SripeZoneWhite_Decal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_SripeZoneYellow_Decal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
}
]
}
@@ -0,0 +1,52 @@
{
"subfolder": "Decals/Class",
"source_path": "/Game/BlockingStarterPack/Blueprints/Decals/Class",
"actor_blueprint_count": 0,
"non_actor_count": 6,
"completed": true,
"actor_blueprints": {},
"skipped_non_actor": [
{
"name": "B_BlockingDecal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_SimpleBlockingDecal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_ZoneDecal",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "BlockingDecalData",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "BlockingDecalMaterialParameters_Struct",
"ue_class_type": "UserDefinedStruct",
"note": [
"非 Actor 类型: UserDefinedStruct"
]
},
{
"name": "DecalPivotPoint_Enum",
"ue_class_type": "UserDefinedEnum",
"note": [
"非 Actor 类型: UserDefinedEnum"
]
}
]
}
@@ -0,0 +1,31 @@
{
"subfolder": "Interactions",
"source_path": "/Game/BlockingStarterPack/Blueprints/Interactions",
"actor_blueprint_count": 0,
"non_actor_count": 3,
"completed": true,
"actor_blueprints": {},
"skipped_non_actor": [
{
"name": "B_FloorButton",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_PushButton",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_TriggerButton",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
}
]
}
@@ -0,0 +1,17 @@
{
"subfolder": "Interactions/Curves",
"source_path": "/Game/BlockingStarterPack/Blueprints/Interactions/Curves",
"actor_blueprint_count": 0,
"non_actor_count": 1,
"completed": true,
"actor_blueprints": {},
"skipped_non_actor": [
{
"name": "Curve_ButtonPress",
"ue_class_type": "CurveFloat",
"note": [
"非 Actor 类型: CurveFloat"
]
}
]
}
@@ -0,0 +1,17 @@
{
"subfolder": "Interactions/Types",
"source_path": "/Game/BlockingStarterPack/Blueprints/Interactions/Types",
"actor_blueprint_count": 0,
"non_actor_count": 1,
"completed": true,
"actor_blueprints": {},
"skipped_non_actor": [
{
"name": "ReplicatedPressedButton_Struct",
"ue_class_type": "UserDefinedStruct",
"note": [
"非 Actor 类型: UserDefinedStruct"
]
}
]
}
@@ -0,0 +1,80 @@
{
"subfolder": "Lights",
"source_path": "/Game/BlockingStarterPack/Blueprints/Lights",
"actor_blueprint_count": 0,
"non_actor_count": 10,
"completed": true,
"actor_blueprints": {},
"skipped_non_actor": [
{
"name": "B_BlockingLights",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_CeilingLight01",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_CeilingLight02a",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_CeilingLight03a",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_CeilingLight03b",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_CeilingLightBulb",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_CeilingLightFlat02a",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_FlamingTorchWithSupport",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_RoadLamp_01",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_RoadLight",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
}
]
}
@@ -0,0 +1,45 @@
{
"subfolder": "Others",
"source_path": "/Game/BlockingStarterPack/Blueprints/Others",
"actor_blueprint_count": 0,
"non_actor_count": 5,
"completed": true,
"actor_blueprints": {},
"skipped_non_actor": [
{
"name": "B_DisplayPanel",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_HumanScaleReference",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_SimpleCameraPreview",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_SplineArrow",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_TurnToCameraComponent",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
}
]
}
@@ -0,0 +1,24 @@
{
"subfolder": "Others/BlockingTooltip",
"source_path": "/Game/BlockingStarterPack/Blueprints/Others/BlockingTooltip",
"actor_blueprint_count": 0,
"non_actor_count": 2,
"completed": true,
"actor_blueprints": {},
"skipped_non_actor": [
{
"name": "B_BlockingTooltipActor",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "NS_BlockingTooltip",
"ue_class_type": "NiagaraSystem",
"note": [
"非 Actor 类型: NiagaraSystem"
]
}
]
}
@@ -0,0 +1,52 @@
{
"subfolder": "Procedural",
"source_path": "/Game/BlockingStarterPack/Blueprints/Procedural",
"actor_blueprint_count": 0,
"non_actor_count": 6,
"completed": true,
"actor_blueprints": {},
"skipped_non_actor": [
{
"name": "B_BarrierWall",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_Ladder_01",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_Ladder_02",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_ProceduralBlockoutActor",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_RoadSpeedBump",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_StairPlane",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
}
]
}
@@ -0,0 +1,24 @@
{
"subfolder": "StaticDoor",
"source_path": "/Game/BlockingStarterPack/Blueprints/StaticDoor",
"actor_blueprint_count": 0,
"non_actor_count": 2,
"completed": true,
"actor_blueprints": {},
"skipped_non_actor": [
{
"name": "B_StaticDoor",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
},
{
"name": "B_StaticDoorDouble",
"ue_class_type": "Blueprint",
"note": [
"非 Actor 蓝图: Blueprint"
]
}
]
}
+102
View File
@@ -0,0 +1,102 @@
{
"started_at": "2026-06-10T11:57:44.766012",
"scan_root": "/Game/BlockingStarterPack",
"output_dir": "C:\\MiceCola\\Indi\\ChildHood\\output\\asset-catalog",
"steps": [
{
"name": "discover",
"status": "ok",
"total": 644,
"meshes": 425,
"materials": 86,
"blueprints": 78,
"others": 55,
"mesh_subfolders": [
"Architecture/Barriers",
"Architecture/BayWindows",
"Architecture/Beams",
"Architecture/Doors",
"Architecture/Floors",
"Architecture/Stairs",
"Architecture/Walls",
"Architecture/WindowsSet",
"Bathroom",
"Bedroom",
"BigBuildings",
"Decorations",
"DisplayPanels",
"Extra/Camera",
"Extra",
"Furnitures",
"Gears",
"Industrials",
"Interactions",
"LandMesh",
"Lights",
"Pipes",
"Primitives/Others",
"Primitives",
"Roads",
"SmallBuildings",
"Trashs",
"Vegetations",
"Vehicles",
"Weapons"
],
"material_subfolders": [
"Others/BlockingTooltip",
"",
"Blocking/Basics",
"Blocking",
"Blocking/TriPlanarLocalAligned",
"Blocking/TriPlanarWorldAligned",
"Decals",
"Editor",
"Glass",
"Light"
],
"blueprint_subfolders": [
"AutomaticDoor",
"AutomaticDoor/Curves",
"AutomaticDoor/Sounds",
"Decals",
"Decals/Class",
"Interactions",
"Interactions/Curves",
"Interactions/Types",
"Lights",
"Others",
"Others/BlockingTooltip",
"Procedural",
"StaticDoor"
],
"elapsed_sec": 4.2
},
{
"name": "mesh",
"status": "ok",
"total": 425,
"analyzed": 425,
"failed": 0,
"elapsed_sec": 1.5
},
{
"name": "material",
"status": "ok",
"total": 86,
"analyzed": 86,
"failed": 0,
"elapsed_sec": 0.5
},
{
"name": "blueprint",
"status": "ok",
"total": 78,
"actor_blueprints": 0,
"skipped_non_actor": 78,
"elapsed_sec": 0.6
}
],
"completed_at": "2026-06-10T11:57:51.806102",
"status": "ok"
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,82 @@
{
"subfolder": "Blocking",
"source_path": "/Game/BlockingStarterPack/Materials/Blocking",
"count": 2,
"completed": true,
"materials": {
"M_BlockingMaterial": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/M_BlockingMaterial.M_BlockingMaterial",
"ue_class_type": "Material",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/M_BlockingMaterial.M_BlockingMaterial",
"parameters": {
"vector": [],
"scalar": []
},
"analysis_notes": [
"基础 Material,参数需从实例推断"
],
"base_color_hex": "",
"style_tags": [
"unclassified"
]
},
"MI_DisplayPanelFace": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/MI_DisplayPanelFace.MI_DisplayPanelFace",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/M_BlockingMaterial.M_BlockingMaterial",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0,
"g": 0.0,
"b": 0.0,
"a": 1.0
}
},
{
"name": "Wire Color",
"value": {
"r": 0.0,
"g": 0.3398,
"b": 1.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "WiresPower",
"value": 5.0
},
{
"name": "UV Offset X",
"value": 0.0
},
{
"name": "UV Offset Y",
"value": 0.0
},
{
"name": "UV Scale X",
"value": 0.5
},
{
"name": "UV Scale Y",
"value": 0.5
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#000000",
"style_tags": [
"display"
]
}
}
}
@@ -0,0 +1,595 @@
{
"subfolder": "Blocking/Basics",
"source_path": "/Game/BlockingStarterPack/Materials/Blocking/Basics",
"count": 14,
"completed": true,
"materials": {
"MI_DevBlack": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevBlack.MI_DevBlack",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/M_BlockingMaterial.M_BlockingMaterial",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0104,
"g": 0.0104,
"b": 0.0104,
"a": 1.0
}
},
{
"name": "Wire Color",
"value": {
"r": 0.1615,
"g": 0.1615,
"b": 0.1615,
"a": 1.0
}
}
],
"scalar": [
{
"name": "WiresPower",
"value": 2.0
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#030303",
"style_tags": [
"basic_color"
]
},
"MI_DevBlue": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevBlue.MI_DevBlue",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/M_BlockingMaterial.M_BlockingMaterial",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0,
"g": 0.0,
"b": 1.0,
"a": 1.0
}
},
{
"name": "Wire Color",
"value": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "WiresPower",
"value": 2.0
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#0000FF",
"style_tags": [
"basic_color"
]
},
"MI_DevBrown": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevBrown.MI_DevBrown",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/M_BlockingMaterial.M_BlockingMaterial",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0729,
"g": 0.031,
"b": 0.0087,
"a": 1.0
}
},
{
"name": "Wire Color",
"value": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "WiresPower",
"value": 2.0
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#130802",
"style_tags": [
"basic_color"
]
},
"MI_DevFoliage": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevFoliage.MI_DevFoliage",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/M_BlockingMaterial.M_BlockingMaterial",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0,
"g": 0.151,
"b": 0.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "WiresPower",
"value": 0.5
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#002700",
"style_tags": [
"basic_color"
]
},
"MI_DevGray": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevGray.MI_DevGray",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/M_BlockingMaterial.M_BlockingMaterial",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0885,
"g": 0.0885,
"b": 0.0885,
"a": 1.0
}
},
{
"name": "Wire Color",
"value": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "RoughnessMax",
"value": 0.8
},
{
"name": "RoughnessMin",
"value": 0.6
},
{
"name": "WiresPower",
"value": 1.679003
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#171717",
"style_tags": [
"basic_color"
]
},
"MI_DevGreen": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevGreen.MI_DevGreen",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/M_BlockingMaterial.M_BlockingMaterial",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0,
"g": 1.0,
"b": 0.0,
"a": 1.0
}
},
{
"name": "Wire Color",
"value": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "WiresPower",
"value": 2.0
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#00FF00",
"style_tags": [
"basic_color"
]
},
"MI_DevOrange": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevOrange.MI_DevOrange",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/M_BlockingMaterial.M_BlockingMaterial",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.8308,
"g": 0.3238,
"b": 0.026,
"a": 1.0
}
},
{
"name": "Wire Color",
"value": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "Wire Roughness",
"value": 0.6
},
{
"name": "UV Offset X",
"value": 0.0
},
{
"name": "UV Offset Y",
"value": 0.0
},
{
"name": "UV Scale X",
"value": 1.0
},
{
"name": "UV Scale Y",
"value": 1.0
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#D45307",
"style_tags": [
"basic_color"
]
},
"MI_DevPink": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevPink.MI_DevPink",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/M_BlockingMaterial.M_BlockingMaterial",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 1.0,
"g": 0.3882,
"b": 0.8682,
"a": 1.0
}
}
],
"scalar": [
{
"name": "WiresPower",
"value": 0.5
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#FF63DD",
"style_tags": [
"basic_color"
]
},
"MI_DevPurple": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevPurple.MI_DevPurple",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/M_BlockingMaterial.M_BlockingMaterial",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.6562,
"g": 0.0355,
"b": 0.413,
"a": 1.0
}
},
{
"name": "Wire Color",
"value": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "WiresPower",
"value": 2.0
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#A70969",
"style_tags": [
"basic_color"
]
},
"MI_DevRed": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevRed.MI_DevRed",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/M_BlockingMaterial.M_BlockingMaterial",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 1.0,
"g": 0.0,
"b": 0.0,
"a": 1.0
}
},
{
"name": "Wire Color",
"value": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "WiresPower",
"value": 2.0
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#FF0000",
"style_tags": [
"basic_color"
]
},
"MI_DevSkyBlue": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevSkyBlue.MI_DevSkyBlue",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/M_BlockingMaterial.M_BlockingMaterial",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0,
"g": 0.4038,
"b": 1.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "WiresPower",
"value": 0.5
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#0067FF",
"style_tags": [
"basic_color"
]
},
"MI_DevWhite": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevWhite.MI_DevWhite",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/M_BlockingMaterial.M_BlockingMaterial",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.3698,
"g": 0.3698,
"b": 0.3698,
"a": 1.0
}
}
],
"scalar": [
{
"name": "WiresPower",
"value": 0.5
},
{
"name": "Line Grow Distance",
"value": 60000.0
},
{
"name": "Line Weight Multiply On Distance",
"value": 0.5
},
{
"name": "Wire Weight (Small)",
"value": 0.47
},
{
"name": "Wire Weight (Medium)",
"value": 0.465
},
{
"name": "Wire Weight (Large)",
"value": 0.475
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#5E5E5E",
"style_tags": [
"basic_color"
]
},
"MI_DevYellow": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevYellow.MI_DevYellow",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/M_BlockingMaterial.M_BlockingMaterial",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 1.0,
"g": 1.0,
"b": 0.0,
"a": 1.0
}
},
{
"name": "Wire Color",
"value": {
"r": 0.0,
"g": 0.0,
"b": 0.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "WiresPower",
"value": 0.5
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#FFFF00",
"style_tags": [
"basic_color"
]
},
"MI_LightGreen": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_LightGreen.MI_LightGreen",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/M_BlockingMaterial.M_BlockingMaterial",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.4798,
"g": 1.0,
"b": 0.3867,
"a": 1.0
}
}
],
"scalar": [
{
"name": "WiresPower",
"value": 0.5
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#7AFF63",
"style_tags": [
"unclassified"
]
}
}
}
@@ -0,0 +1,292 @@
{
"subfolder": "Blocking/TriPlanarLocalAligned",
"source_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarLocalAligned",
"count": 14,
"completed": true,
"materials": {
"MI_DevBlack_TPLA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarLocalAligned/MI_DevBlack_TPLA.MI_DevBlack_TPLA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevBlack.MI_DevBlack",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"local_aligned"
]
},
"MI_DevBlue_TPLA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarLocalAligned/MI_DevBlue_TPLA.MI_DevBlue_TPLA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevBlue.MI_DevBlue",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"local_aligned"
]
},
"MI_DevBrown_TPLA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarLocalAligned/MI_DevBrown_TPLA.MI_DevBrown_TPLA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevBrown.MI_DevBrown",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"local_aligned"
]
},
"MI_DevFoliage_TPLA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarLocalAligned/MI_DevFoliage_TPLA.MI_DevFoliage_TPLA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevFoliage.MI_DevFoliage",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"local_aligned"
]
},
"MI_DevGray_TPLA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarLocalAligned/MI_DevGray_TPLA.MI_DevGray_TPLA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevGray.MI_DevGray",
"parameters": {
"vector": [],
"scalar": [
{
"name": "TriplanardSmooth",
"value": 0.0
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"local_aligned"
]
},
"MI_DevGreen_TPLA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarLocalAligned/MI_DevGreen_TPLA.MI_DevGreen_TPLA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevGreen.MI_DevGreen",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"local_aligned"
]
},
"MI_DevOrange_TPLA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarLocalAligned/MI_DevOrange_TPLA.MI_DevOrange_TPLA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevOrange.MI_DevOrange",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"local_aligned"
]
},
"MI_DevPink_TPLA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarLocalAligned/MI_DevPink_TPLA.MI_DevPink_TPLA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevPink.MI_DevPink",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"local_aligned"
]
},
"MI_DevPurple_TPLA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarLocalAligned/MI_DevPurple_TPLA.MI_DevPurple_TPLA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevPurple.MI_DevPurple",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"local_aligned"
]
},
"MI_DevRed_TPLA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarLocalAligned/MI_DevRed_TPLA.MI_DevRed_TPLA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevRed.MI_DevRed",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"local_aligned"
]
},
"MI_DevSkyBlue_TPLA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarLocalAligned/MI_DevSkyBlue_TPLA.MI_DevSkyBlue_TPLA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevSkyBlue.MI_DevSkyBlue",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"local_aligned"
]
},
"MI_DevWhite_TPLA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarLocalAligned/MI_DevWhite_TPLA.MI_DevWhite_TPLA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevWhite.MI_DevWhite",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"local_aligned"
]
},
"MI_DevYellow_TPLA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarLocalAligned/MI_DevYellow_TPLA.MI_DevYellow_TPLA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevYellow.MI_DevYellow",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"local_aligned"
]
},
"MI_LightGreen_TPLA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarLocalAligned/MI_LightGreen_TPLA.MI_LightGreen_TPLA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_LightGreen.MI_LightGreen",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"local_aligned"
]
}
}
}
@@ -0,0 +1,296 @@
{
"subfolder": "Blocking/TriPlanarWorldAligned",
"source_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarWorldAligned",
"count": 14,
"completed": true,
"materials": {
"MI_DevBlack_TPWA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarWorldAligned/MI_DevBlack_TPWA.MI_DevBlack_TPWA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevBlack.MI_DevBlack",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"world_aligned"
]
},
"MI_DevBlue_TPWA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarWorldAligned/MI_DevBlue_TPWA.MI_DevBlue_TPWA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevBlue.MI_DevBlue",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"world_aligned"
]
},
"MI_DevBrown_TPWA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarWorldAligned/MI_DevBrown_TPWA.MI_DevBrown_TPWA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevBrown.MI_DevBrown",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"world_aligned"
]
},
"MI_DevFoliage_TPWA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarWorldAligned/MI_DevFoliage_TPWA.MI_DevFoliage_TPWA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevFoliage.MI_DevFoliage",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"world_aligned"
]
},
"MI_DevGray_TPWA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarWorldAligned/MI_DevGray_TPWA.MI_DevGray_TPWA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevGray.MI_DevGray",
"parameters": {
"vector": [],
"scalar": [
{
"name": "TriplanardSmooth",
"value": 0.0
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"world_aligned"
]
},
"MI_DevGreen_TPWA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarWorldAligned/MI_DevGreen_TPWA.MI_DevGreen_TPWA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevGreen.MI_DevGreen",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"world_aligned"
]
},
"MI_DevOange_TPWA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarWorldAligned/MI_DevOange_TPWA.MI_DevOange_TPWA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevOrange.MI_DevOrange",
"parameters": {
"vector": [],
"scalar": [
{
"name": "TriplanardSmooth",
"value": 0.0
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"world_aligned"
]
},
"MI_DevPink_TPWA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarWorldAligned/MI_DevPink_TPWA.MI_DevPink_TPWA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevPink.MI_DevPink",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"world_aligned"
]
},
"MI_DevPurple_TPWA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarWorldAligned/MI_DevPurple_TPWA.MI_DevPurple_TPWA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevPurple.MI_DevPurple",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"world_aligned"
]
},
"MI_DevRed_TPWA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarWorldAligned/MI_DevRed_TPWA.MI_DevRed_TPWA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevRed.MI_DevRed",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"world_aligned"
]
},
"MI_DevSkyBlue_TPWA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarWorldAligned/MI_DevSkyBlue_TPWA.MI_DevSkyBlue_TPWA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevSkyBlue.MI_DevSkyBlue",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"world_aligned"
]
},
"MI_DevWhite_TPWA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarWorldAligned/MI_DevWhite_TPWA.MI_DevWhite_TPWA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevWhite.MI_DevWhite",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"world_aligned"
]
},
"MI_DevYellow_TPWA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarWorldAligned/MI_DevYellow_TPWA.MI_DevYellow_TPWA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_DevYellow.MI_DevYellow",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"world_aligned"
]
},
"MI_LightGreen_TPWA": {
"ue_path": "/Game/BlockingStarterPack/Materials/Blocking/TriPlanarWorldAligned/MI_LightGreen_TPWA.MI_LightGreen_TPWA",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/Basics/MI_LightGreen.MI_LightGreen",
"parameters": {
"vector": [],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"tri_planar",
"world_aligned"
]
}
}
}
+205
View File
@@ -0,0 +1,205 @@
{
"subfolder": "Decals",
"source_path": "/Game/BlockingStarterPack/Materials/Decals",
"count": 7,
"completed": true,
"materials": {
"M_StainDecalColor": {
"ue_path": "/Game/BlockingStarterPack/Materials/Decals/M_StainDecalColor.M_StainDecalColor",
"ue_class_type": "Material",
"parent_material": "/Game/BlockingStarterPack/Materials/Decals/M_StainDecalColor.M_StainDecalColor",
"parameters": {
"vector": [],
"scalar": []
},
"analysis_notes": [
"基础 Material,参数需从实例推断"
],
"base_color_hex": "",
"style_tags": [
"decal",
"stain"
]
},
"MI_StainDecalColorBlack": {
"ue_path": "/Game/BlockingStarterPack/Materials/Decals/MI_StainDecalColorBlack.MI_StainDecalColorBlack",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Decals/M_StainDecalColor.M_StainDecalColor",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0,
"g": 0.0,
"b": 0.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#000000",
"style_tags": [
"decal",
"stain"
]
},
"MI_StainDecalColorBlue": {
"ue_path": "/Game/BlockingStarterPack/Materials/Decals/MI_StainDecalColorBlue.MI_StainDecalColorBlue",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Decals/M_StainDecalColor.M_StainDecalColor",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0,
"g": 0.0,
"b": 1.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#0000FF",
"style_tags": [
"decal",
"stain"
]
},
"MI_StainDecalColorGreen": {
"ue_path": "/Game/BlockingStarterPack/Materials/Decals/MI_StainDecalColorGreen.MI_StainDecalColorGreen",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Decals/M_StainDecalColor.M_StainDecalColor",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0,
"g": 1.0,
"b": 0.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#00FF00",
"style_tags": [
"decal",
"stain"
]
},
"MI_StainDecalColorRed": {
"ue_path": "/Game/BlockingStarterPack/Materials/Decals/MI_StainDecalColorRed.MI_StainDecalColorRed",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Decals/M_StainDecalColor.M_StainDecalColor",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 1.0,
"g": 0.0,
"b": 0.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#FF0000",
"style_tags": [
"decal",
"stain"
]
},
"MI_StainDecalColorSkyBlue": {
"ue_path": "/Game/BlockingStarterPack/Materials/Decals/MI_StainDecalColorSkyBlue.MI_StainDecalColorSkyBlue",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Decals/M_StainDecalColor.M_StainDecalColor",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0,
"g": 0.4038,
"b": 1.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#0067FF",
"style_tags": [
"decal",
"stain"
]
},
"MI_StainDecalColorYellow": {
"ue_path": "/Game/BlockingStarterPack/Materials/Decals/MI_StainDecalColorYellow.MI_StainDecalColorYellow",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Decals/M_StainDecalColor.M_StainDecalColor",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 1.0,
"g": 1.0,
"b": 0.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#FFFF00",
"style_tags": [
"decal",
"stain"
]
}
}
}
+354
View File
@@ -0,0 +1,354 @@
{
"subfolder": "Editor",
"source_path": "/Game/BlockingStarterPack/Materials/Editor",
"count": 8,
"completed": true,
"materials": {
"M_DevTriggerView": {
"ue_path": "/Game/BlockingStarterPack/Materials/Editor/M_DevTriggerView.M_DevTriggerView",
"ue_class_type": "Material",
"parent_material": "/Game/BlockingStarterPack/Materials/Editor/M_DevTriggerView.M_DevTriggerView",
"parameters": {
"vector": [],
"scalar": []
},
"analysis_notes": [
"基础 Material,参数需从实例推断"
],
"base_color_hex": "",
"style_tags": [
"editor",
"debug"
]
},
"MI_DevArrowMoving": {
"ue_path": "/Game/BlockingStarterPack/Materials/Editor/MI_DevArrowMoving.MI_DevArrowMoving",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Editor/M_DevTriggerView.M_DevTriggerView",
"parameters": {
"vector": [
{
"name": "ColorA",
"value": {
"r": 0.0,
"g": 0.0,
"b": 0.0,
"a": 1.0
}
},
{
"name": "ColorB",
"value": {
"r": 0.0571,
"g": 0.3069,
"b": 1.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "CoordX",
"value": 6.0
},
{
"name": "CoordY",
"value": 6.0
},
{
"name": "CustomPannerTime",
"value": 0.0
},
{
"name": "SpeedY",
"value": -1.0
},
{
"name": "PulseMin",
"value": 0.5
},
{
"name": "PulseSpeed",
"value": 0.5
},
{
"name": "Opacity",
"value": 0.4
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#000000",
"style_tags": [
"editor",
"debug"
]
},
"MI_DevArrowMoving2": {
"ue_path": "/Game/BlockingStarterPack/Materials/Editor/MI_DevArrowMoving2.MI_DevArrowMoving2",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Editor/M_DevTriggerView.M_DevTriggerView",
"parameters": {
"vector": [
{
"name": "ColorA",
"value": {
"r": 0.0,
"g": 0.0,
"b": 0.0,
"a": 1.0
}
},
{
"name": "ColorB",
"value": {
"r": 1.0,
"g": 0.4833,
"b": 0.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "CoordX",
"value": 6.0
},
{
"name": "CoordY",
"value": 6.0
},
{
"name": "DepthFadeDistance",
"value": 15.0
},
{
"name": "Opacity",
"value": 0.4
},
{
"name": "CustomPannerTime",
"value": 0.0
},
{
"name": "SpeedX",
"value": -4.0
},
{
"name": "SpeedY",
"value": 0.1
},
{
"name": "PulseMin",
"value": 0.0
},
{
"name": "PulseSpeed",
"value": 1.0
},
{
"name": "CustomRotationTime",
"value": 0.0
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#000000",
"style_tags": [
"editor",
"debug"
]
},
"MI_DevArrowMoving3": {
"ue_path": "/Game/BlockingStarterPack/Materials/Editor/MI_DevArrowMoving3.MI_DevArrowMoving3",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Editor/M_DevTriggerView.M_DevTriggerView",
"parameters": {
"vector": [
{
"name": "ColorA",
"value": {
"r": 0.0,
"g": 0.0,
"b": 0.0,
"a": 1.0
}
},
{
"name": "ColorB",
"value": {
"r": 0.338,
"g": 0.0981,
"b": 1.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "CoordX",
"value": 6.0
},
{
"name": "CoordY",
"value": 6.0
},
{
"name": "CustomPannerTime",
"value": 0.0
},
{
"name": "SpeedY",
"value": 0.5
},
{
"name": "CustomRotationTime",
"value": 0.0
},
{
"name": "Opacity",
"value": 0.4
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#000000",
"style_tags": [
"editor",
"debug"
]
},
"MI_DevTriggerViewBlue": {
"ue_path": "/Game/BlockingStarterPack/Materials/Editor/MI_DevTriggerViewBlue.MI_DevTriggerViewBlue",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Editor/M_DevTriggerView.M_DevTriggerView",
"parameters": {
"vector": [
{
"name": "ColorB",
"value": {
"r": 0.0,
"g": 0.2832,
"b": 1.0,
"a": 0.0
}
}
],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#0048FF",
"style_tags": [
"editor",
"debug"
]
},
"MI_DevTriggerViewGreen": {
"ue_path": "/Game/BlockingStarterPack/Materials/Editor/MI_DevTriggerViewGreen.MI_DevTriggerViewGreen",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Editor/M_DevTriggerView.M_DevTriggerView",
"parameters": {
"vector": [
{
"name": "ColorB",
"value": {
"r": 0.0,
"g": 1.0,
"b": 0.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#00FF00",
"style_tags": [
"editor",
"debug"
]
},
"MI_DevTriggerViewOrange": {
"ue_path": "/Game/BlockingStarterPack/Materials/Editor/MI_DevTriggerViewOrange.MI_DevTriggerViewOrange",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Editor/M_DevTriggerView.M_DevTriggerView",
"parameters": {
"vector": [
{
"name": "ColorB",
"value": {
"r": 1.0,
"g": 0.5,
"b": 0.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#FF8000",
"style_tags": [
"editor",
"debug"
]
},
"MI_DevTriggerViewRed": {
"ue_path": "/Game/BlockingStarterPack/Materials/Editor/MI_DevTriggerViewRed.MI_DevTriggerViewRed",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Editor/M_DevTriggerView.M_DevTriggerView",
"parameters": {
"vector": [
{
"name": "ColorB",
"value": {
"r": 1.0,
"g": 0.0,
"b": 0.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#FF0000",
"style_tags": [
"editor",
"debug"
]
}
}
}
+665
View File
@@ -0,0 +1,665 @@
{
"subfolder": "Glass",
"source_path": "/Game/BlockingStarterPack/Materials/Glass",
"count": 11,
"completed": true,
"materials": {
"M_DevGlass": {
"ue_path": "/Game/BlockingStarterPack/Materials/Glass/M_DevGlass.M_DevGlass",
"ue_class_type": "Material",
"parent_material": "/Game/BlockingStarterPack/Materials/Glass/M_DevGlass.M_DevGlass",
"parameters": {
"vector": [],
"scalar": []
},
"analysis_notes": [
"基础 Material,参数需从实例推断"
],
"base_color_hex": "",
"style_tags": [
"glass",
"transparent"
]
},
"MI_DevGlassBlue": {
"ue_path": "/Game/BlockingStarterPack/Materials/Glass/MI_DevGlassBlue.MI_DevGlassBlue",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Glass/M_DevGlass.M_DevGlass",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0,
"g": 0.6714,
"b": 1.0,
"a": 1.0
}
},
{
"name": "GlassColor",
"value": {
"r": 0.0,
"g": 0.6714,
"b": 1.0,
"a": 0.0
}
}
],
"scalar": [
{
"name": "Specular",
"value": 1.0
},
{
"name": "Wire Specular",
"value": 1.0
},
{
"name": "Opacity",
"value": 0.4
},
{
"name": "Wire Opacity",
"value": 0.5
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#00ABFF",
"style_tags": [
"glass",
"transparent"
]
},
"MI_DevGlassGreen": {
"ue_path": "/Game/BlockingStarterPack/Materials/Glass/MI_DevGlassGreen.MI_DevGlassGreen",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Glass/M_DevGlass.M_DevGlass",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0,
"g": 1.0,
"b": 0.0,
"a": 1.0
}
},
{
"name": "GlassColor",
"value": {
"r": 0.0,
"g": 1.0,
"b": 0.0,
"a": 0.0
}
}
],
"scalar": [
{
"name": "Specular",
"value": 1.0
},
{
"name": "Wire Specular",
"value": 1.0
},
{
"name": "Opacity",
"value": 0.4
},
{
"name": "Wire Opacity",
"value": 0.5
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#00FF00",
"style_tags": [
"glass",
"transparent"
]
},
"MI_DevGlassMetalBlue": {
"ue_path": "/Game/BlockingStarterPack/Materials/Glass/MI_DevGlassMetalBlue.MI_DevGlassMetalBlue",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Glass/M_DevGlass.M_DevGlass",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0,
"g": 0.6714,
"b": 1.0,
"a": 1.0
}
},
{
"name": "Wire Color",
"value": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 1.0
}
},
{
"name": "GlassColor",
"value": {
"r": 0.0,
"g": 0.6714,
"b": 1.0,
"a": 0.0
}
}
],
"scalar": [
{
"name": "Metallic",
"value": 1.0
},
{
"name": "Specular",
"value": 1.0
},
{
"name": "Wire Specular",
"value": 1.0
},
{
"name": "Opacity",
"value": 0.4
},
{
"name": "Wire Opacity",
"value": 0.5
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#00ABFF",
"style_tags": [
"glass",
"transparent"
]
},
"MI_DevGlassMetalGreen": {
"ue_path": "/Game/BlockingStarterPack/Materials/Glass/MI_DevGlassMetalGreen.MI_DevGlassMetalGreen",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Glass/M_DevGlass.M_DevGlass",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0,
"g": 1.0,
"b": 0.0,
"a": 1.0
}
},
{
"name": "Wire Color",
"value": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 1.0
}
},
{
"name": "GlassColor",
"value": {
"r": 0.0,
"g": 1.0,
"b": 0.0,
"a": 0.0
}
}
],
"scalar": [
{
"name": "Metallic",
"value": 1.0
},
{
"name": "Wire Metallic",
"value": 1.0
},
{
"name": "Specular",
"value": 1.0
},
{
"name": "Wire Specular",
"value": 1.0
},
{
"name": "Opacity",
"value": 0.4
},
{
"name": "Wire Opacity",
"value": 0.5
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#00FF00",
"style_tags": [
"glass",
"transparent"
]
},
"MI_DevGlassMetalRed": {
"ue_path": "/Game/BlockingStarterPack/Materials/Glass/MI_DevGlassMetalRed.MI_DevGlassMetalRed",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Glass/M_DevGlass.M_DevGlass",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 1.0,
"g": 0.0,
"b": 0.0,
"a": 1.0
}
},
{
"name": "Wire Color",
"value": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 1.0
}
},
{
"name": "GlassColor",
"value": {
"r": 1.0,
"g": 0.0,
"b": 0.0,
"a": 0.0
}
}
],
"scalar": [
{
"name": "Metallic",
"value": 1.0
},
{
"name": "Wire Metallic",
"value": 1.0
},
{
"name": "Specular",
"value": 1.0
},
{
"name": "Wire Specular",
"value": 1.0
},
{
"name": "Opacity",
"value": 0.4
},
{
"name": "Wire Opacity",
"value": 0.5
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#FF0000",
"style_tags": [
"glass",
"transparent"
]
},
"MI_DevGlassMetalWhite": {
"ue_path": "/Game/BlockingStarterPack/Materials/Glass/MI_DevGlassMetalWhite.MI_DevGlassMetalWhite",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Glass/M_DevGlass.M_DevGlass",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 0.0
}
},
{
"name": "Wire Color",
"value": {
"r": 0.0,
"g": 0.0,
"b": 0.0,
"a": 1.0
}
},
{
"name": "GlassColor",
"value": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 0.0
}
}
],
"scalar": [
{
"name": "Metallic",
"value": 1.0
},
{
"name": "Wire Metallic",
"value": 1.0
},
{
"name": "WiresPower",
"value": 0.5
},
{
"name": "Specular",
"value": 1.0
},
{
"name": "Wire Specular",
"value": 1.0
},
{
"name": "Opacity",
"value": 0.4
},
{
"name": "Wire Opacity",
"value": 0.5
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#FFFFFF",
"style_tags": [
"glass",
"transparent"
]
},
"MI_DevGlassMetalWhiteSoft": {
"ue_path": "/Game/BlockingStarterPack/Materials/Glass/MI_DevGlassMetalWhiteSoft.MI_DevGlassMetalWhiteSoft",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Glass/M_DevGlass.M_DevGlass",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 1.0
}
},
{
"name": "Wire Color",
"value": {
"r": 0.0,
"g": 0.0,
"b": 0.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "Metallic",
"value": 1.0
},
{
"name": "Wire Metallic",
"value": 1.0
},
{
"name": "Specular",
"value": 1.0
},
{
"name": "Wire Specular",
"value": 1.0
},
{
"name": "Opacity",
"value": 0.08
},
{
"name": "Wire Opacity",
"value": 0.12
},
{
"name": "OpacityMax",
"value": 0.12
},
{
"name": "OpacityMin",
"value": 0.08
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#FFFFFF",
"style_tags": [
"glass",
"transparent"
]
},
"MI_DevGlassRed": {
"ue_path": "/Game/BlockingStarterPack/Materials/Glass/MI_DevGlassRed.MI_DevGlassRed",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Glass/M_DevGlass.M_DevGlass",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 1.0,
"g": 0.0,
"b": 0.0,
"a": 1.0
}
},
{
"name": "Wire Color",
"value": {
"r": 0.0,
"g": 0.0,
"b": 0.0,
"a": 1.0
}
},
{
"name": "GlassColor",
"value": {
"r": 1.0,
"g": 0.0,
"b": 0.0,
"a": 0.0
}
}
],
"scalar": [
{
"name": "Specular",
"value": 1.0
},
{
"name": "Wire Specular",
"value": 1.0
},
{
"name": "Opacity",
"value": 0.4
},
{
"name": "Wire Opacity",
"value": 0.5
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#FF0000",
"style_tags": [
"glass",
"transparent"
]
},
"MI_DevGlassWhite": {
"ue_path": "/Game/BlockingStarterPack/Materials/Glass/MI_DevGlassWhite.MI_DevGlassWhite",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Glass/M_DevGlass.M_DevGlass",
"parameters": {
"vector": [
{
"name": "Wire Color",
"value": {
"r": 0.0,
"g": 0.0,
"b": 0.0,
"a": 1.0
}
},
{
"name": "GlassColor",
"value": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 0.0
}
}
],
"scalar": [
{
"name": "WiresPower",
"value": 0.5
},
{
"name": "Specular",
"value": 1.0
},
{
"name": "Wire Specular",
"value": 1.0
},
{
"name": "Opacity",
"value": 0.4
},
{
"name": "Wire Opacity",
"value": 0.5
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#000000",
"style_tags": [
"glass",
"transparent"
]
},
"MI_DevGlassWhiteSoft": {
"ue_path": "/Game/BlockingStarterPack/Materials/Glass/MI_DevGlassWhiteSoft.MI_DevGlassWhiteSoft",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Glass/M_DevGlass.M_DevGlass",
"parameters": {
"vector": [],
"scalar": [
{
"name": "Metallic",
"value": 0.0
},
{
"name": "Specular",
"value": 1.0
},
{
"name": "Wire Specular",
"value": 1.0
},
{
"name": "Opacity",
"value": 0.08
},
{
"name": "Wire Opacity",
"value": 0.12
},
{
"name": "OpacityMax",
"value": 0.12
},
{
"name": "OpacityMin",
"value": 0.08
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"glass",
"transparent"
]
}
}
}
+302
View File
@@ -0,0 +1,302 @@
{
"subfolder": "Light",
"source_path": "/Game/BlockingStarterPack/Materials/Light",
"count": 9,
"completed": true,
"materials": {
"M_DevLight": {
"ue_path": "/Game/BlockingStarterPack/Materials/Light/M_DevLight.M_DevLight",
"ue_class_type": "Material",
"parent_material": "/Game/BlockingStarterPack/Materials/Light/M_DevLight.M_DevLight",
"parameters": {
"vector": [],
"scalar": []
},
"analysis_notes": [
"基础 Material,参数需从实例推断"
],
"base_color_hex": "",
"style_tags": [
"unclassified"
]
},
"MI_DevLight_Green": {
"ue_path": "/Game/BlockingStarterPack/Materials/Light/MI_DevLight_Green.MI_DevLight_Green",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Light/M_DevLight.M_DevLight",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0,
"g": 1.0,
"b": 0.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#00FF00",
"style_tags": [
"emissive",
"light_color"
]
},
"MI_DevLight_NaturalBlue": {
"ue_path": "/Game/BlockingStarterPack/Materials/Light/MI_DevLight_NaturalBlue.MI_DevLight_NaturalBlue",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Light/M_DevLight.M_DevLight",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.4169,
"g": 0.5275,
"b": 1.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "WiresPower",
"value": 0.5
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#6A87FF",
"style_tags": [
"emissive",
"light_color"
]
},
"MI_DevLight_NaturalOrange": {
"ue_path": "/Game/BlockingStarterPack/Materials/Light/MI_DevLight_NaturalOrange.MI_DevLight_NaturalOrange",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Light/M_DevLight.M_DevLight",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 1.0,
"g": 0.5116,
"b": 0.3404,
"a": 1.0
}
}
],
"scalar": [
{
"name": "WiresPower",
"value": 0.5
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#FF8257",
"style_tags": [
"emissive",
"light_color"
]
},
"MI_DevLight_NaturalWhite": {
"ue_path": "/Game/BlockingStarterPack/Materials/Light/MI_DevLight_NaturalWhite.MI_DevLight_NaturalWhite",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Light/M_DevLight.M_DevLight",
"parameters": {
"vector": [],
"scalar": [
{
"name": "WiresPower",
"value": 0.5
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "",
"style_tags": [
"emissive",
"light_color"
]
},
"MI_DevLight_NaturalYellow": {
"ue_path": "/Game/BlockingStarterPack/Materials/Light/MI_DevLight_NaturalYellow.MI_DevLight_NaturalYellow",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Light/M_DevLight.M_DevLight",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 1.0,
"g": 0.8803,
"b": 0.2678,
"a": 1.0
}
}
],
"scalar": [
{
"name": "WiresPower",
"value": 0.5
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#FFE044",
"style_tags": [
"emissive",
"light_color"
]
},
"MI_DevLight_Off": {
"ue_path": "/Game/BlockingStarterPack/Materials/Light/MI_DevLight_Off.MI_DevLight_Off",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Light/M_DevLight.M_DevLight",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.2344,
"g": 0.2344,
"b": 0.2344,
"a": 1.0
}
}
],
"scalar": [
{
"name": "DirtScale",
"value": 0.1
},
{
"name": "Dirt Normal Scale",
"value": 0.01
},
{
"name": "Roughness",
"value": 0.1
},
{
"name": "Wire Roughness",
"value": 0.5
},
{
"name": "Metallic",
"value": 0.9
},
{
"name": "Light Intensity",
"value": 0.0
},
{
"name": "WiresPower",
"value": 2.0
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#3C3C3C",
"style_tags": [
"emissive",
"light_color"
]
},
"MI_DevLight_Red": {
"ue_path": "/Game/BlockingStarterPack/Materials/Light/MI_DevLight_Red.MI_DevLight_Red",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Light/M_DevLight.M_DevLight",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 1.0,
"g": 0.0,
"b": 0.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "Light Intensity",
"value": 2.2
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#FF0000",
"style_tags": [
"emissive",
"light_color"
]
},
"MI_DevLight_SkyBlue": {
"ue_path": "/Game/BlockingStarterPack/Materials/Light/MI_DevLight_SkyBlue.MI_DevLight_SkyBlue",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Light/M_DevLight.M_DevLight",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0,
"g": 0.4038,
"b": 1.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#0067FF",
"style_tags": [
"emissive",
"light_color"
]
}
}
}
@@ -0,0 +1,40 @@
{
"subfolder": "Others/BlockingTooltip",
"source_path": "/Game/BlockingStarterPack/Materials/Others/BlockingTooltip",
"count": 2,
"completed": true,
"materials": {
"M_BlockingTooltipBase": {
"ue_path": "/Game/BlockingStarterPack/Blueprints/Others/BlockingTooltip/M_BlockingTooltipBase.M_BlockingTooltipBase",
"ue_class_type": "Material",
"parent_material": "/Game/BlockingStarterPack/Blueprints/Others/BlockingTooltip/M_BlockingTooltipBase.M_BlockingTooltipBase",
"parameters": {
"vector": [],
"scalar": []
},
"analysis_notes": [
"基础 Material,参数需从实例推断"
],
"base_color_hex": "",
"style_tags": [
"unclassified"
]
},
"M_BlockingTooltipText": {
"ue_path": "/Game/BlockingStarterPack/Blueprints/Others/BlockingTooltip/M_BlockingTooltipText.M_BlockingTooltipText",
"ue_class_type": "Material",
"parent_material": "/Game/BlockingStarterPack/Blueprints/Others/BlockingTooltip/M_BlockingTooltipText.M_BlockingTooltipText",
"parameters": {
"vector": [],
"scalar": []
},
"analysis_notes": [
"基础 Material,参数需从实例推断"
],
"base_color_hex": "",
"style_tags": [
"unclassified"
]
}
}
}
+132
View File
@@ -0,0 +1,132 @@
{
"subfolder": "",
"source_path": "/Game/BlockingStarterPack/Materials",
"count": 5,
"completed": true,
"materials": {
"M_OverviewLevelFloor": {
"ue_path": "/Game/BlockingStarterPack/Maps/M_OverviewLevelFloor.M_OverviewLevelFloor",
"ue_class_type": "Material",
"parent_material": "/Game/BlockingStarterPack/Maps/M_OverviewLevelFloor.M_OverviewLevelFloor",
"parameters": {
"vector": [],
"scalar": []
},
"analysis_notes": [
"基础 Material,参数需从实例推断"
],
"base_color_hex": "",
"style_tags": [
"unclassified"
]
},
"M_OverviewLevelFloor2": {
"ue_path": "/Game/BlockingStarterPack/Maps/M_OverviewLevelFloor2.M_OverviewLevelFloor2",
"ue_class_type": "Material",
"parent_material": "/Game/BlockingStarterPack/Maps/M_OverviewLevelFloor2.M_OverviewLevelFloor2",
"parameters": {
"vector": [],
"scalar": []
},
"analysis_notes": [
"基础 Material,参数需从实例推断"
],
"base_color_hex": "",
"style_tags": [
"unclassified"
]
},
"M_DevMirror": {
"ue_path": "/Game/BlockingStarterPack/Materials/M_DevMirror.M_DevMirror",
"ue_class_type": "Material",
"parent_material": "/Game/BlockingStarterPack/Materials/M_DevMirror.M_DevMirror",
"parameters": {
"vector": [],
"scalar": []
},
"analysis_notes": [
"基础 Material,参数需从实例推断"
],
"base_color_hex": "",
"style_tags": [
"mirror",
"reflective"
]
},
"M_FullyBlack": {
"ue_path": "/Game/BlockingStarterPack/Materials/M_FullyBlack.M_FullyBlack",
"ue_class_type": "Material",
"parent_material": "/Game/BlockingStarterPack/Materials/M_FullyBlack.M_FullyBlack",
"parameters": {
"vector": [],
"scalar": []
},
"analysis_notes": [
"基础 Material,参数需从实例推断"
],
"base_color_hex": "",
"style_tags": [
"special"
]
},
"MI_DevGrid": {
"ue_path": "/Game/BlockingStarterPack/Materials/MI_DevGrid.MI_DevGrid",
"ue_class_type": "MaterialInstanceConstant",
"parent_material": "/Game/BlockingStarterPack/Materials/Blocking/M_BlockingMaterial.M_BlockingMaterial",
"parameters": {
"vector": [
{
"name": "Color",
"value": {
"r": 0.0,
"g": 0.0,
"b": 0.0,
"a": 1.0
}
},
{
"name": "Wire Color",
"value": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 1.0
}
}
],
"scalar": [
{
"name": "Metallic",
"value": 1.0
},
{
"name": "Roughness",
"value": 0.55
},
{
"name": "Roughness Checkers Multiply",
"value": 0.9
},
{
"name": "Wire Metallic",
"value": 1.0
},
{
"name": "Wire Roughness",
"value": 0.198
},
{
"name": "RefractionDepthBias",
"value": 0.0
}
]
},
"analysis_notes": [],
"base_color_hex": "#000000",
"style_tags": [
"grid",
"debug"
]
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,758 @@
{
"subfolder": "Architecture/BayWindows",
"source_path": "/Game/BlockingStarterPack/Meshes/Architecture/BayWindows",
"count": 12,
"completed": true,
"meshes": {
"SM_BayWindow_100x300": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/BayWindows/SM_BayWindow_100x300.SM_BayWindow_100x300",
"bounding_box": {
"min": [
-0.0,
-3.966,
-0.0
],
"max": [
100.0,
3.969,
300.0
],
"size": [
100.0,
7.936,
300.0
]
},
"pivot": {
"type": "CORNER",
"offset_xy": [
49.999805450454005,
0.0015639066696166992
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevGlass",
"material_name": "MI_DevGlassMetalWhite"
},
{
"index": 1,
"slot_name": "Face",
"material_name": "MI_DevWhite"
},
{
"index": 2,
"slot_name": "Join",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"window"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_BayWindow_100x400": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/BayWindows/SM_BayWindow_100x400.SM_BayWindow_100x400",
"bounding_box": {
"min": [
-0.0,
-3.966,
-0.0
],
"max": [
100.0,
3.969,
400.0
],
"size": [
100.0,
7.936,
400.0
]
},
"pivot": {
"type": "CORNER",
"offset_xy": [
49.999805450454005,
0.001555800437927246
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevGlass",
"material_name": "MI_DevGlassMetalWhite"
},
{
"index": 1,
"slot_name": "Face",
"material_name": "MI_DevWhite"
},
{
"index": 2,
"slot_name": "Join",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"window"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_BayWindow_200x300": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/BayWindows/SM_BayWindow_200x300.SM_BayWindow_200x300",
"bounding_box": {
"min": [
-0.0,
-3.966,
-0.0
],
"max": [
200.0,
3.969,
300.0
],
"size": [
200.0,
7.936,
300.0
]
},
"pivot": {
"type": "CORNER",
"offset_xy": [
99.99980926515127,
0.0015639066696166992
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevGlass",
"material_name": "MI_DevGlassMetalWhite"
},
{
"index": 1,
"slot_name": "Face",
"material_name": "MI_DevWhite"
},
{
"index": 2,
"slot_name": "Join",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"window"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_BayWindow_200x400": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/BayWindows/SM_BayWindow_200x400.SM_BayWindow_200x400",
"bounding_box": {
"min": [
-0.0,
-3.966,
-0.0
],
"max": [
200.0,
3.969,
400.0
],
"size": [
200.0,
7.936,
400.0
]
},
"pivot": {
"type": "CORNER",
"offset_xy": [
99.99980926515127,
0.001555800437927246
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevGlass",
"material_name": "MI_DevGlassMetalWhite"
},
{
"index": 1,
"slot_name": "Face",
"material_name": "MI_DevWhite"
},
{
"index": 2,
"slot_name": "Join",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"window"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_BayWindow_400x300": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/BayWindows/SM_BayWindow_400x300.SM_BayWindow_400x300",
"bounding_box": {
"min": [
-0.0,
-3.966,
-0.0
],
"max": [
400.0,
3.969,
300.0
],
"size": [
400.0,
7.936,
300.0
]
},
"pivot": {
"type": "CORNER",
"offset_xy": [
199.99980163575674,
0.0015639066696166992
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevGlass",
"material_name": "MI_DevGlassMetalWhite"
},
{
"index": 1,
"slot_name": "Face",
"material_name": "MI_DevWhite"
},
{
"index": 2,
"slot_name": "Join",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"window"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_BayWindow_400x400": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/BayWindows/SM_BayWindow_400x400.SM_BayWindow_400x400",
"bounding_box": {
"min": [
-0.0,
-3.966,
-0.0
],
"max": [
400.0,
3.969,
400.0
],
"size": [
400.0,
7.936,
400.0
]
},
"pivot": {
"type": "CORNER",
"offset_xy": [
199.99980163575674,
0.001555800437927246
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevGlass",
"material_name": "MI_DevGlassMetalWhite"
},
{
"index": 1,
"slot_name": "Face",
"material_name": "MI_DevWhite"
},
{
"index": 2,
"slot_name": "Join",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"window"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_BayWindowDoor_200x300": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/BayWindows/SM_BayWindowDoor_200x300.SM_BayWindowDoor_200x300",
"bounding_box": {
"min": [
-0.0,
-3.966,
-0.0
],
"max": [
200.0,
3.969,
300.0
],
"size": [
200.0,
7.936,
300.0
]
},
"pivot": {
"type": "CORNER",
"offset_xy": [
99.99980926515127,
0.001558065414428711
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevGlass",
"material_name": "MI_DevGlassMetalWhite"
},
{
"index": 1,
"slot_name": "Face",
"material_name": "MI_DevWhite"
},
{
"index": 2,
"slot_name": "Join",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door",
"window"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_BayWindowDoor_200x400": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/BayWindows/SM_BayWindowDoor_200x400.SM_BayWindowDoor_200x400",
"bounding_box": {
"min": [
-0.0,
-3.966,
-0.0
],
"max": [
200.0,
3.969,
400.0
],
"size": [
200.0,
7.936,
400.0
]
},
"pivot": {
"type": "CORNER",
"offset_xy": [
99.99980926515127,
0.001555800437927246
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevGlass",
"material_name": "MI_DevGlassMetalWhite"
},
{
"index": 1,
"slot_name": "Face",
"material_name": "MI_DevWhite"
},
{
"index": 2,
"slot_name": "Join",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door",
"window"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_BayWindowDoor_400x300": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/BayWindows/SM_BayWindowDoor_400x300.SM_BayWindowDoor_400x300",
"bounding_box": {
"min": [
-0.0,
-3.966,
-0.0
],
"max": [
400.0,
3.969,
300.0
],
"size": [
400.0,
7.936,
300.0
]
},
"pivot": {
"type": "CORNER",
"offset_xy": [
199.99980163575674,
0.001558065414428711
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevGlass",
"material_name": "MI_DevGlassMetalWhite"
},
{
"index": 1,
"slot_name": "Face",
"material_name": "MI_DevWhite"
},
{
"index": 2,
"slot_name": "Join",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door",
"window"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_BayWindowDoor_400x400": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/BayWindows/SM_BayWindowDoor_400x400.SM_BayWindowDoor_400x400",
"bounding_box": {
"min": [
-0.0,
-3.966,
-0.0
],
"max": [
400.0,
3.969,
400.0
],
"size": [
400.0,
7.936,
400.0
]
},
"pivot": {
"type": "CORNER",
"offset_xy": [
199.99980163575674,
0.001555800437927246
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevGlass",
"material_name": "MI_DevGlassMetalWhite"
},
{
"index": 1,
"slot_name": "Face",
"material_name": "MI_DevWhite"
},
{
"index": 2,
"slot_name": "Join",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door",
"window"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_BayWindowDoorDouble_400x300": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/BayWindows/SM_BayWindowDoorDouble_400x300.SM_BayWindowDoorDouble_400x300",
"bounding_box": {
"min": [
-0.0,
-3.966,
-0.0
],
"max": [
400.0,
3.969,
300.0
],
"size": [
400.0,
7.936,
300.0
]
},
"pivot": {
"type": "CORNER",
"offset_xy": [
199.99980163575674,
0.001558065414428711
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevGlass",
"material_name": "MI_DevGlassMetalWhite"
},
{
"index": 1,
"slot_name": "Face",
"material_name": "MI_DevWhite"
},
{
"index": 2,
"slot_name": "Join",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door",
"window"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_BayWindowDoorDouble_400x400": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/BayWindows/SM_BayWindowDoorDouble_400x400.SM_BayWindowDoorDouble_400x400",
"bounding_box": {
"min": [
-0.0,
-3.966,
-0.0
],
"max": [
400.0,
3.969,
400.0
],
"size": [
400.0,
7.936,
400.0
]
},
"pivot": {
"type": "CORNER",
"offset_xy": [
199.99980163575674,
0.001555800437927246
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevGlass",
"material_name": "MI_DevGlassMetalWhite"
},
{
"index": 1,
"slot_name": "Face",
"material_name": "MI_DevWhite"
},
{
"index": 2,
"slot_name": "Join",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door",
"window"
],
"scene_tags": [
"architecture",
"construction"
]
}
}
}
@@ -0,0 +1,350 @@
{
"subfolder": "Architecture/Beams",
"source_path": "/Game/BlockingStarterPack/Meshes/Architecture/Beams",
"count": 6,
"completed": true,
"meshes": {
"SM_Beam_300": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Beams/SM_Beam_300.SM_Beam_300",
"bounding_box": {
"min": [
-20.0,
-20.0,
0.0
],
"max": [
20.0,
20.0,
300.0
],
"size": [
40.0,
40.0,
300.0
]
},
"pivot": {
"type": "CENTER_BOTTOM",
"offset_xy": [
0.0,
-2.47955322265625e-05
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"structure"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_Beam_400": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Beams/SM_Beam_400.SM_Beam_400",
"bounding_box": {
"min": [
-20.0,
-20.0,
0.0
],
"max": [
20.0,
20.0,
400.0
],
"size": [
40.0,
40.0,
400.0
]
},
"pivot": {
"type": "CENTER_BOTTOM",
"offset_xy": [
0.0,
-3.24249267578125e-05
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"structure"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_Beam_400_45d": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Beams/SM_Beam_400_45d.SM_Beam_400_45d",
"bounding_box": {
"min": [
-19.999,
-419.999,
-0.0
],
"max": [
20.001,
-268.845,
296.749
],
"size": [
40.0,
151.155,
296.749
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
0.0007476806640625,
-344.4219055175781
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"structure"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_RoundedBeam_300": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Beams/SM_RoundedBeam_300.SM_RoundedBeam_300",
"bounding_box": {
"min": [
-30.0,
-30.0,
0.0
],
"max": [
30.0,
30.0,
300.0
],
"size": [
60.0,
60.0,
300.0
]
},
"pivot": {
"type": "CENTER_BOTTOM",
"offset_xy": [
0.0,
-2.47955322265625e-05
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"structure"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_RoundedBeam_400": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Beams/SM_RoundedBeam_400.SM_RoundedBeam_400",
"bounding_box": {
"min": [
-30.0,
-30.0,
0.0
],
"max": [
30.0,
30.0,
400.0
],
"size": [
60.0,
60.0,
400.0
]
},
"pivot": {
"type": "CENTER_BOTTOM",
"offset_xy": [
0.0,
-3.4332275390625e-05
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"structure"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_RoundedBeam_400_45d": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Beams/SM_RoundedBeam_400_45d.SM_RoundedBeam_400_45d",
"bounding_box": {
"min": [
-30.0,
-429.981,
-0.291
],
"max": [
30.0,
-261.934,
303.597
],
"size": [
60.0,
168.047,
303.888
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
-1.71661376953125e-05,
-345.957763671875
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"structure"
],
"scene_tags": [
"architecture",
"construction"
]
}
}
}
@@ -0,0 +1,939 @@
{
"subfolder": "Architecture/Doors",
"source_path": "/Game/BlockingStarterPack/Meshes/Architecture/Doors",
"count": 16,
"completed": true,
"meshes": {
"SM_DoorFrame": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Doors/SM_DoorFrame.SM_DoorFrame",
"bounding_box": {
"min": [
-66.0,
-12.0,
0.0
],
"max": [
66.0,
12.0,
232.0
],
"size": [
132.0,
24.0,
232.0
]
},
"pivot": {
"type": "CENTER_BOTTOM",
"offset_xy": [
0.0,
-1.9073486328125e-05
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 1
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevWhite",
"material_name": "MI_DevWhite"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_DoorFrameDouble": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Doors/SM_DoorFrameDouble.SM_DoorFrameDouble",
"bounding_box": {
"min": [
-116.0,
-12.0,
0.0
],
"max": [
116.0,
12.0,
232.0
],
"size": [
232.0,
24.0,
232.0
]
},
"pivot": {
"type": "CENTER_BOTTOM",
"offset_xy": [
0.0,
-1.9073486328125e-05
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 1
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevWhite",
"material_name": "MI_DevWhite"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_DoorFrameGarage": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Doors/SM_DoorFrameGarage.SM_DoorFrameGarage",
"bounding_box": {
"min": [
-166.0,
-12.0,
0.0
],
"max": [
166.0,
12.0,
292.0
],
"size": [
332.0,
24.0,
292.0
]
},
"pivot": {
"type": "CENTER_BOTTOM",
"offset_xy": [
0.0,
-2.384185791015625e-05
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 1
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevWhite",
"material_name": "MI_DevWhite"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_FakeDoor": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Doors/SM_FakeDoor.SM_FakeDoor",
"bounding_box": {
"min": [
-66.0,
-0.0,
0.0
],
"max": [
66.0,
12.0,
232.0
],
"size": [
132.0,
12.0,
232.0
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
0.0,
5.999777317047119
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevWhite",
"material_name": "MI_DevWhite"
},
{
"index": 1,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 2,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_FakeDoorDouble": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Doors/SM_FakeDoorDouble.SM_FakeDoorDouble",
"bounding_box": {
"min": [
-116.0,
-0.0,
0.0
],
"max": [
116.0,
12.0,
232.0
],
"size": [
232.0,
12.0,
232.0
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
7.62939453125e-06,
5.999777317047119
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevWhite",
"material_name": "MI_DevWhite"
},
{
"index": 1,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 2,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_FakeFrameGarage": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Doors/SM_FakeFrameGarage.SM_FakeFrameGarage",
"bounding_box": {
"min": [
-166.0,
-0.002,
0.0
],
"max": [
166.0,
12.0,
292.0
],
"size": [
332.0,
12.002,
292.0
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
0.0,
5.998886585235596
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevWhite",
"material_name": "MI_DevWhite"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
},
{
"index": 2,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"unknown"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_GarageDoor_01": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Doors/SM_GarageDoor_01.SM_GarageDoor_01",
"bounding_box": {
"min": [
-150.0,
-5.914,
0.0
],
"max": [
150.0,
5.914,
276.004
],
"size": [
300.0,
11.828,
276.004
]
},
"pivot": {
"type": "CENTER_BOTTOM",
"offset_xy": [
0.0,
-1.430511474609375e-06
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
},
{
"index": 1,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_GarageDoor_02": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Doors/SM_GarageDoor_02.SM_GarageDoor_02",
"bounding_box": {
"min": [
-150.0,
-5.914,
-0.0
],
"max": [
150.0,
5.914,
276.004
],
"size": [
300.0,
11.828,
276.004
]
},
"pivot": {
"type": "CENTER_BOTTOM",
"offset_xy": [
0.0,
-1.430511474609375e-06
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevGlass",
"material_name": "MI_DevGlassMetalWhite"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
},
{
"index": 2,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_GarageDoorOpen_01": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Doors/SM_GarageDoorOpen_01.SM_GarageDoorOpen_01",
"bounding_box": {
"min": [
-150.0,
-224.485,
219.986
],
"max": [
150.0,
5.914,
296.388
],
"size": [
300.0,
230.398,
76.401
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
0.0,
-109.28529357910156
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
},
{
"index": 1,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_GarageDoorOpen_02": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Doors/SM_GarageDoorOpen_02.SM_GarageDoorOpen_02",
"bounding_box": {
"min": [
-150.0,
-224.484,
219.981
],
"max": [
150.0,
5.914,
296.388
],
"size": [
300.0,
230.398,
76.407
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
-0.00019073486328125,
-109.28524470329285
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevGlass",
"material_name": "MI_DevGlassMetalWhite"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
},
{
"index": 2,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_LateralDoor": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Doors/SM_LateralDoor.SM_LateralDoor",
"bounding_box": {
"min": [
-10.0,
-8.0,
0.0
],
"max": [
150.0,
8.0,
250.0
],
"size": [
160.0,
16.0,
250.0
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
70.00001525878906,
-2.574920654296875e-05
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
},
{
"index": 1,
"slot_name": "M_DevWhite",
"material_name": "MI_DevWhite"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_LateralDoorFrame_150": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Doors/SM_LateralDoorFrame_150.SM_LateralDoorFrame_150",
"bounding_box": {
"min": [
-75.0,
-55.236,
-0.0
],
"max": [
75.0,
55.236,
250.0
],
"size": [
150.0,
110.473,
250.0
]
},
"pivot": {
"type": "CENTER_BOTTOM",
"offset_xy": [
0.0,
0.0
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
},
{
"index": 1,
"slot_name": "M_DevWhite",
"material_name": "MI_DevWhite"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_LateralDoorFrame_300": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Doors/SM_LateralDoorFrame_300.SM_LateralDoorFrame_300",
"bounding_box": {
"min": [
-150.0,
-55.236,
-0.0
],
"max": [
150.0,
55.236,
250.0
],
"size": [
300.0,
110.473,
250.0
]
},
"pivot": {
"type": "CENTER_BOTTOM",
"offset_xy": [
0.0,
0.0
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
},
{
"index": 1,
"slot_name": "M_DevWhite",
"material_name": "MI_DevWhite"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_RotativeDoor_01": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Doors/SM_RotativeDoor_01.SM_RotativeDoor_01",
"bounding_box": {
"min": [
-100.0,
-12.0,
-0.0
],
"max": [
0.0,
11.991,
216.0
],
"size": [
100.0,
23.991,
216.0
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
-49.99995422363281,
-0.004532814025878906
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
},
{
"index": 2,
"slot_name": "M_DevGlass",
"material_name": "MI_DevGlassMetalWhite"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_RotativeDoor_02": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Doors/SM_RotativeDoor_02.SM_RotativeDoor_02",
"bounding_box": {
"min": [
-100.0,
-12.0,
0.0
],
"max": [
0.0,
11.991,
216.0
],
"size": [
100.0,
23.991,
216.0
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
-49.99995422363281,
-0.004460334777832031
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_RotativeDoor_03": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Doors/SM_RotativeDoor_03.SM_RotativeDoor_03",
"bounding_box": {
"min": [
-100.0,
-12.0,
0.0
],
"max": [
0.0,
11.991,
216.0
],
"size": [
100.0,
23.991,
216.0
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
-49.99995422363281,
-0.004532814025878906
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 3
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
},
{
"index": 2,
"slot_name": "M_DevWhite",
"material_name": "MI_DevWhite"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"door"
],
"scene_tags": [
"architecture",
"construction"
]
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,584 @@
{
"subfolder": "Architecture/Stairs",
"source_path": "/Game/BlockingStarterPack/Meshes/Architecture/Stairs",
"count": 10,
"completed": true,
"meshes": {
"SM_Stair_100": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Stairs/SM_Stair_100.SM_Stair_100",
"bounding_box": {
"min": [
0.0,
-0.0,
0.0
],
"max": [
200.0,
150.0,
102.0
],
"size": [
200.0,
150.0,
102.0
]
},
"pivot": {
"type": "CORNER",
"offset_xy": [
99.99999237056181,
74.99992302650207
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"stair"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_Stair_200": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Stairs/SM_Stair_200.SM_Stair_200",
"bounding_box": {
"min": [
0.0,
-0.0,
0.0
],
"max": [
200.0,
300.0,
202.0
],
"size": [
200.0,
300.0,
202.0
]
},
"pivot": {
"type": "CORNER",
"offset_xy": [
99.99999237056181,
149.99984621592557
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"stair"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_StairPlane_100": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Stairs/SM_StairPlane_100.SM_StairPlane_100",
"bounding_box": {
"min": [
0.0,
-0.0,
-20.0
],
"max": [
200.0,
150.0,
102.0
],
"size": [
200.0,
150.0,
122.0
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
99.99999237056181,
74.99992302650207
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"stair",
"vehicle"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_StairPlane_200": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Stairs/SM_StairPlane_200.SM_StairPlane_200",
"bounding_box": {
"min": [
0.0,
-0.0,
-20.0
],
"max": [
200.0,
300.0,
202.0
],
"size": [
200.0,
300.0,
222.0
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
99.99999237056181,
149.99984621592557
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"stair",
"vehicle"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_StairPlaneRounded_300_45d": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Stairs/SM_StairPlaneRounded_300_45d.SM_StairPlaneRounded_300_45d",
"bounding_box": {
"min": [
-300.0,
-212.132,
-20.0
],
"max": [
-70.71,
-0.0,
102.0
],
"size": [
229.29,
212.132,
122.0
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
-185.35503768920898,
-106.06606129789725
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"stair",
"vehicle"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_StairPlaneRounded_300_90d": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Stairs/SM_StairPlaneRounded_300_90d.SM_StairPlaneRounded_300_90d",
"bounding_box": {
"min": [
-300.0,
-300.0,
-20.0
],
"max": [
0.0,
-0.0,
202.0
],
"size": [
300.0,
300.0,
222.0
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
-149.9999389648583,
-150.00014105534137
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"stair",
"vehicle"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_StairPlaneRounded_400_45d": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Stairs/SM_StairPlaneRounded_400_45d.SM_StairPlaneRounded_400_45d",
"bounding_box": {
"min": [
-400.0,
-282.842,
-20.0
],
"max": [
-141.42,
-0.0,
102.0
],
"size": [
258.58,
282.842,
122.0
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
-270.71009063720703,
-141.4214359519974
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"stair",
"vehicle"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_StairPlaneRounded_400_90d": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Stairs/SM_StairPlaneRounded_400_90d.SM_StairPlaneRounded_400_90d",
"bounding_box": {
"min": [
-400.0,
-400.0,
-20.0
],
"max": [
0.0,
-0.0,
202.0
],
"size": [
400.0,
400.0,
222.0
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
-199.9999023437631,
-200.0001722176312
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"stair",
"vehicle"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_StairRounded_300_45d": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Stairs/SM_StairRounded_300_45d.SM_StairRounded_300_45d",
"bounding_box": {
"min": [
-300.0,
-212.132,
-0.0
],
"max": [
-70.711,
-0.0,
102.0
],
"size": [
229.289,
212.132,
102.0
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
-185.35552597045898,
-106.066326141241
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"stair"
],
"scene_tags": [
"architecture",
"construction"
]
},
"SM_StairRounded_300_90d": {
"ue_path": "/Game/BlockingStarterPack/Meshes/Architecture/Stairs/SM_StairRounded_300_90d.SM_StairRounded_300_90d",
"bounding_box": {
"min": [
-300.0,
-300.001,
-0.0
],
"max": [
-0.001,
-0.0,
202.0
],
"size": [
299.999,
300.001,
202.0
]
},
"pivot": {
"type": "OTHER",
"offset_xy": [
-150.00024414039217,
-150.00042724603554
]
},
"mesh_complexity": {
"num_verts": null,
"num_triangles": null,
"num_lods": 1,
"num_sections": 2
},
"sockets": [],
"material_slots": [
{
"index": 0,
"slot_name": "M_DevOrange",
"material_name": "MI_DevOrange"
},
{
"index": 1,
"slot_name": "M_DevBlack",
"material_name": "MI_DevGray"
}
],
"analysis_notes": [],
"key_surfaces": [],
"ground_sink_estimate": null,
"style_tags": [
"architecture",
"stair"
],
"scene_tags": [
"architecture",
"construction"
]
}
}
}

Some files were not shown because too many files have changed in this diff Show More