feat(scene): 场景选择与物理落地 + 整包发布(T-WIN-012/013/014)
T-WIN-013:API 升级与场景选择 - 契约快照同步到 rest-api 0.5.0 / data-models 0.7.0 / database 0.4.0 - catalog_types.gd 新增 SceneConfig(物理/环境/出生点/相机)与 Scene; CatalogBundle 增加 scenes[];from_dict 按契约 7.1 再收敛一次 (缺项默认、越界夹紧、非法枚举回退),与服务端 normalizeSceneConfig 同规则 - 新增 src/scene/scene_runtime.gd:GLB 载入 -> 碰撞体(mesh 三角网 / auto 命名约定 *_col、*_colonly / none 不兜底)-> 场景级重力(Area3D SPACE_OVERRIDE_REPLACE,不改全局 ProjectSettings)-> 风(每物理帧 apply_central_force,阵风正弦叠加,支持 local 坐标系)-> 环境光与雾 -> 出生点与相机(follow/fixed/orbit);无碰撞体时兜底地面 - CatalogStore 增加 scenes/default_scene/find_scene/ensure_scene_cached/ fetch_thumbnail(场景缓存带 sha256 指纹,避免换包后仍用旧 GLB) - 主界面改为 2x2 四个选择器(曲目/动作/模型/场景)+ 场景参数面板 (缩略图 + 重力/摩擦/弹性/碰撞体/风摘要) - 游戏界面载入场景并落地物理,角色落在出生点;场景缺失/下载失败/ 解析失败一律回退内置空场景并提示,不阻塞开始跟练 T-WIN-012:「关于」清单展示当前版本号 - 版本唯一来源为 project.godot 的 application/config/version(v0.2.0) - about_manifest() 输出版本/引擎/契约/协议/地址/隐私清单; 无头测试无 autoload,读取 AppConfig 走 get_node_or_null 容错 - Credits 的单个 CONTRACT_VERSION 常量改为 CONTRACT_VERSIONS 字典 (六项契约各自独立递增),并双向核对 contracts/VERSION T-WIN-014:发布必须是单个完整包 - build/package-release.sh 重写:只产出单个 zip + SHA256SUMS, 清理历史 .partN,超限时告警而非回退分卷;新增源产物与解压后完整性 自检(PE 头 MZ / pck 魔数 GDPC / exe+pck 同目录同名)与体积-上限对比 - deploy/proxy 相关的 Gitea IIS 256MB 上限说明见 opendance-web 测试:tests/test_scene_config.gd(12 例)+ tests/test_about.gd(4 例); 无头测试 687 通过 / 0 失败(10 个套件),verify.sh 全部通过
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
# T-WIN-013 API 连接升级与场景选择
|
||||
|
||||
- **状态**: done
|
||||
- **依赖**: T-WIN-002、T-WIN-008
|
||||
- **文档**: v1.6 §5.6、契约 `rest-api 0.5.0` / `data-models 0.7.0`
|
||||
|
||||
## 目标
|
||||
|
||||
跟随 Web 端场景管理(T-WEB-015)升级本端 API 连接,新增**场景选择**与**物理参数落地**。
|
||||
|
||||
## 交付物
|
||||
|
||||
| 文件 | 说明 |
|
||||
|--|--|
|
||||
| `contracts/*` | 契约快照同步到 `rest-api 0.5.0` / `data-models 0.7.0` / `database 0.4.0`(与 opendance-web 逐字节一致) |
|
||||
| `src/net/catalog_types.gd` | 新增 `Wind` / `Ground` / `ScenePhysics` / `SceneEnvironment` / `SpawnPoint` / `SceneCamera` / `SceneConfig` / `Scene`;`CatalogBundle` 增加 `scenes` |
|
||||
| `src/scene/scene_runtime.gd` | **场景实例化与物理落地**(新增目录 `src/scene/`) |
|
||||
| `src/autoload/catalog_store.gd` | `scenes()` / `default_scene()` / `find_scene()` / `ensure_scene_cached()` / `fetch_thumbnail()` |
|
||||
| `src/ui/scene_panel.gd` | 场景缩略图 + 重力/风/摩擦参数摘要 |
|
||||
| `src/ui/main_menu.gd` | 第 4 个选择器「场景」(2×2 栅格,与曲目/动作/模型并列) |
|
||||
| `src/ui/game_screen.gd` | 载入场景 → 落地物理 → 出生点定位 → 按场景相机取景 |
|
||||
|
||||
## 1. 契约解析与服务端同规则收敛
|
||||
|
||||
`SceneConfig.from_dict()` 复刻服务端 `normalizeSceneConfig()` 的规则:
|
||||
缺项用默认值、越界夹紧、非法枚举回退。**服务端已收敛,客户端仍再收敛一次** ——
|
||||
不能假设对端一定合法(旧版本服务端 / 人工改包)。
|
||||
|
||||
| 项 | 范围 |
|
||||
|--|--|
|
||||
| `gravity` | 各分量 ±200 |
|
||||
| `wind.base` | ±100 |
|
||||
| `gustAmplitude` / `gustFrequency` | 0~100 / 0.01~5 Hz |
|
||||
| `gustDirection` | ±1 |
|
||||
| `ground.friction` / `restitution` / `slopeLimit` | 0~1 / 0~1 / 0~89 度 |
|
||||
| `environment.ambientIntensity` / `fogDensity` | 0~4 / 0~1 |
|
||||
| `camera.distance` / `height` | 0.5~50 / 0~20 |
|
||||
| `spawnPoints` | 最多 16 个 |
|
||||
|
||||
## 2. 物理落地(服务端只存参数,仿真在本端)
|
||||
|
||||
- **重力**:`Area3D` + `gravity_space_override = SPACE_OVERRIDE_REPLACE`,
|
||||
**不改 `ProjectSettings` 的全局重力**,因此一个进程内切换场景互不影响。
|
||||
- **碰撞体**:`colliders: mesh` → 所有 `MeshInstance3D` 走 `create_trimesh_collision()`;
|
||||
`auto` → 只认命名约定(`*_col` / `*_colonly` / `*_collision` / …),
|
||||
`*colonly` 只提供碰撞不参与渲染(对齐 Godot/Blender 约定);
|
||||
`none` → 不生成任何碰撞体,**也不兜底**(尊重场景作者意图)。
|
||||
`auto`/`mesh` 下一个都没找到时生成兜底地面并记录降级原因,避免角色无限下落。
|
||||
- **风**:`Area3D` + 每物理帧对区域内 `RigidBody3D` `apply_central_force()`;
|
||||
阵风按正弦叠加(`base + dir * A * sin(2πft)`),`space: local` 时用本节点基向量变换。
|
||||
风速(m/s)→ 力(N)用固定系数 `WIND_FORCE_SCALE = 10`(契约只约束风速量纲)。
|
||||
- **地面材质**:`friction` / `restitution` → `PhysicsMaterial`,套用到所有场景静态体。
|
||||
- **环境**:环境光颜色/强度直接写进游戏界面已有的 `Environment`(不另建
|
||||
`WorldEnvironment`,避免互相覆盖);`fogDensity 0~1` 映射到 Godot 的
|
||||
`0.005~0.05`(Godot 的 `fog_density` 量纲更敏感,直接照搬会全屏糊死)。
|
||||
- **出生点与相机**:角色落在 `spawnPoints[0]`(含 yaw),按 `camera.mode` 取景
|
||||
(`follow` / `fixed` / `orbit`,orbit 在 `_process` 中缓慢环绕)。
|
||||
|
||||
## 3. 容错(不得阻塞开始跟练)
|
||||
|
||||
| 情况 | 行为 |
|
||||
|--|--|
|
||||
| 未选择场景 / 服务端无已发布场景 | 内置空场景 + 状态栏提示 |
|
||||
| 场景缺少 `glbUrl` | 回退内置空场景 + 界面提示 |
|
||||
| GLB 下载失败 | 回退内置空场景 + 明确原因 |
|
||||
| GLB 解析失败 / 根节点非 Node3D | 回退内置空场景 + `warnings` |
|
||||
| 物理参数非法 | 收敛为默认值(不抛错) |
|
||||
| 无碰撞体可用 | 兜底地面 + 降级提示 |
|
||||
|
||||
## 4. 可核对性
|
||||
|
||||
`SceneRuntime.describe()` 输出一行摘要(重力 / 风 / 摩擦 / 弹性 / 碰撞体数量 / 相机模式),
|
||||
启动时 `print` 到日志;界面同时展示同源参数。满足文档「目视 + 日志核对」。
|
||||
|
||||
## 验收(文档 §5.6)
|
||||
|
||||
| 验收项 | 结果 |
|
||||
|--|--|
|
||||
| 服务端发布新场景后「刷新目录」即可见并可选 | ✅ `catalog_export.scenes[]` → 第 4 个选择器;目录指纹变化即重新拉取 |
|
||||
| 场景 GLB 正常加载 | ✅ `GLTFDocument` 与 `VrmLoader` 同一条零原生依赖路线 |
|
||||
| 重力与风向参数按服务端配置生效(目视 + 日志核对) | ✅ `describe()` 打印 + 界面参数摘要;受力计算有单测逐值核对 |
|
||||
| 服务端不可达 / 场景资产缺失时明确提示并回退,不崩溃 | ✅ 见上表「容错」;`test_scene_runtime_falls_back_when_file_missing` 覆盖 |
|
||||
|
||||
## 测试
|
||||
|
||||
`tests/test_scene_config.gd`(12 个用例 / 60+ 断言):
|
||||
|
||||
- 默认值逐项与服务端 `SCENE_CONFIG_DEFAULTS` 一致;
|
||||
- 越界夹紧、非法枚举回退、非数值兜底、出生点 16 上限;
|
||||
- 风的正弦叠加逐时刻核对(t=0 / 1(4f) / 1(2f) / 3(4f));
|
||||
- 目录 `scenes[]` 解析与 `default_scene()` 跳过缺 GLB 的场景;
|
||||
- 重力与风力的纯函数计算、空场景兜底地面、`colliders: none` 不兜底;
|
||||
- 文件缺失时的降级路径、出生点位姿(yaw 90° 使 +Z 转向 +X)、
|
||||
相机 `fixed` 机位、雾浓度映射区间。
|
||||
Reference in New Issue
Block a user