Files
opendance-windows/tasks/013-scene-selection.md
zijunchiang 5581cbe05d 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 全部通过
2026-09-17 00:45:23 +08:00

5.3 KiB
Raw Permalink Blame History

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 0100 / 0.015 Hz
gustDirection ±1
ground.friction / restitution / slopeLimit 01 / 01 / 0~89 度
environment.ambientIntensity / fogDensity 04 / 01
camera.distance / height 0.550 / 020
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 机位、雾浓度映射区间。