Add config-driven game host updates

This commit is contained in:
2026-03-25 13:58:51 +08:00
parent f0ced54805
commit d1cc6cc473
28 changed files with 3247 additions and 105 deletions

587
config-design-proposal.md Normal file
View File

@@ -0,0 +1,587 @@
# 游戏配置文件设计方案(阶段讨论稿)
本文档用于整理当前阶段推荐的配置文件设计方案,供后端、客户端和后台管理设计参考。
目标是让配置真正成为游戏的驱动入口,同时兼顾后续多玩法、多资源、多活动复用。
---
## 1. 设计目标
配置文件系统需要解决以下问题:
- 驱动地图、玩法、资源、调试开关
- 支持顺序赛、积分赛以及后续更多玩法
- 支持将来后台管理系统的内容编排
- 保证地图空间信息与玩法语义分层
- 保证当前阶段可平滑迁移,不推翻已有实现
当前推荐原则:
- 配置只描述,不执行逻辑
- 地图、空间对象、玩法规则、资源包分层
- KML 负责空间底稿,不负责复杂玩法语义
- 主配置先保持单文件,后续再升级为 manifest 组合
---
## 2. 顶层配置结构
当前推荐主入口配置结构如下:
```json
{
"schemaVersion": "1",
"version": "2026.03.25",
"app": {},
"map": {},
"playfield": {},
"game": {},
"resources": {},
"debug": {}
}
```
各层职责如下:
- `app`
活动级或应用级基础信息
- `map`
地图底图和空间底座
- `playfield`
当前玩法使用的空间对象定义
- `game`
当前玩法规则配置
- `resources`
资源包与 profile
- `debug`
调试与开发开关
---
## 3. 为什么不再以 course 作为总抽象
在定向语义里,`course` 是准确术语,表示路线。
但从系统长期扩展看,`course` 并不是所有玩法的上位概念。
例如:
- 顺序赛有明显的 `course`
- 积分赛更像一组控制点与分数
- 金币赛更像可收集点集合
- 幽灵赛可能包含危险区、隐身点、追逐者
- 迷雾赛可能包含 reveal 点、扫描点、区域
因此推荐:
- 将上位内容模型提升为 `playfield`
- `course` 只作为 `playfield.kind` 的一种
例如:
```json
{
"playfield": {
"kind": "course"
}
}
```
或:
```json
{
"playfield": {
"kind": "control-set"
}
}
```
---
## 4. KML 与配置的边界
当前推荐边界非常明确:
### 4.1 KML 负责空间底稿
KML 适合描述:
- 点坐标
- 起点 / 检查点 / 终点
- 顺序号
- 点位名称
- 腿线几何
### 4.2 配置负责玩法解释
配置负责描述:
- 点位分值
- 打点规则
- 显隐规则
- 动态积分
- 道具能力
- 迷雾规则
- 占领规则
- 特殊玩法语义
一句话总结:
**KML 描述空间事实,配置描述玩法解释。**
---
## 5. 推荐的字段结构
### 5.1 `app`
用于活动级基础信息。
示例:
```json
{
"app": {
"id": "lxcb-001",
"title": "雪熊领秀城区定向赛",
"locale": "zh-CN"
}
}
```
### 5.2 `map`
用于地图底图与空间底座。
示例:
```json
{
"map": {
"tiles": "lxcb-001/tiles/",
"mapmeta": "lxcb-001/tiles/meta.json",
"declination": 6.91,
"initialView": {
"zoom": 17
}
}
}
```
### 5.3 `playfield`
用于描述当前玩法使用的空间对象及其来源。
示例:
```json
{
"playfield": {
"kind": "course",
"source": {
"type": "kml",
"url": "lxcb-001/course/c01.kml"
},
"CPRadius": 6,
"controlOverrides": {},
"metadata": {}
}
}
```
建议后续逐步支持的对象包括:
- `controls`
- `collectibles`
- `zones`
- `hazards`
- `links`
- `spawnPoints`
### 5.4 `game`
用于描述玩法规则。
推荐统一结构如下:
```json
{
"game": {
"mode": "",
"rulesVersion": "1",
"session": {},
"punch": {},
"scoring": {},
"guidance": {},
"visibility": {},
"finish": {},
"telemetry": {},
"feedback": {}
}
}
```
#### `session`
控制一局游戏的流程参数:
- 是否手动开始
- 是否必须打开始点
- 是否必须打结束点
- 是否允许自动结束
- 最大时长
#### `punch`
控制打点规则:
- 打点策略
- 打点半径
- 是否必须选中后打卡
#### `scoring`
控制积分与结算:
- 完成型
- 固定分
- 动态分
#### `guidance`
控制引导方式:
- 是否显示腿线
- 是否显示腿线动画
- 是否允许 focus 选择
#### `visibility`
控制显隐逻辑:
- 是否开始后显示全图
- 是否采用迷雾
#### `finish`
控制结束规则:
- 是否必须打终点
- 是否允许随时结束
#### `telemetry`
控制通用运动信息参数:
- 年龄
- 静息心率
- 体重
#### `feedback`
控制反馈 profile
- 音频
- 震动
- UI 动效
### 5.5 `resources`
用于描述资源 profile。
示例:
```json
{
"resources": {
"audioProfile": "default",
"contentProfile": "default",
"themeProfile": "default-race"
}
}
```
当前阶段建议先保持轻量,后续再逐步拆成资源包 manifest。
### 5.6 `debug`
用于开发和调试相关开关。
示例:
```json
{
"debug": {
"allowModeSwitch": false,
"allowMockInput": false,
"allowSimulator": false
}
}
```
---
## 6. 顺序赛示例配置
```json
{
"schemaVersion": "1",
"version": "2026.03.25",
"app": {
"id": "lxcb-001",
"title": "雪熊领秀城区顺序赛"
},
"map": {
"tiles": "lxcb-001/tiles/",
"mapmeta": "lxcb-001/tiles/meta.json",
"declination": 6.91
},
"playfield": {
"kind": "course",
"source": {
"type": "kml",
"url": "lxcb-001/course/c01.kml"
},
"CPRadius": 6
},
"game": {
"mode": "classic-sequential",
"rulesVersion": "1",
"session": {
"requiresStartPunch": true,
"requiresFinishPunch": true,
"autoFinishOnLastControl": false,
"startManually": true
},
"punch": {
"policy": "enter-confirm",
"radiusMeters": 10
},
"guidance": {
"showLegs": true,
"legAnimation": true,
"allowFocusSelection": false
},
"visibility": {
"revealFullPlayfieldAfterStartPunch": true
},
"telemetry": {
"heartRate": {
"age": 30,
"restingHeartRateBpm": 62,
"userWeightKg": 65
}
},
"feedback": {
"audioProfile": "default",
"hapticsProfile": "default",
"uiEffectsProfile": "default"
}
},
"resources": {
"audioProfile": "default",
"contentProfile": "default"
},
"debug": {
"allowModeSwitch": false,
"allowMockInput": false
}
}
```
---
## 7. 积分赛示例配置
```json
{
"schemaVersion": "1",
"version": "2026.03.25",
"app": {
"id": "lxcb-001",
"title": "雪熊领秀城区积分赛"
},
"map": {
"tiles": "lxcb-001/tiles/",
"mapmeta": "lxcb-001/tiles/meta.json",
"declination": 6.91
},
"playfield": {
"kind": "control-set",
"source": {
"type": "kml",
"url": "lxcb-001/course/c01.kml"
},
"CPRadius": 6,
"controlOverrides": {
"control-1": { "score": 10 },
"control-2": { "score": 20 },
"control-3": { "score": 30 }
}
},
"game": {
"mode": "score-o",
"rulesVersion": "1",
"session": {
"requiresStartPunch": true,
"requiresFinishPunch": false,
"startManually": true
},
"punch": {
"policy": "enter-confirm",
"radiusMeters": 10,
"requiresFocusSelection": true
},
"guidance": {
"showLegs": false,
"legAnimation": false,
"allowFocusSelection": true
},
"scoring": {
"type": "score"
},
"finish": {
"finishControlAlwaysSelectable": true
},
"telemetry": {
"heartRate": {
"age": 30,
"restingHeartRateBpm": 62,
"userWeightKg": 65
}
},
"feedback": {
"audioProfile": "default",
"hapticsProfile": "default",
"uiEffectsProfile": "default"
}
},
"resources": {
"audioProfile": "default",
"contentProfile": "default"
},
"debug": {
"allowModeSwitch": false,
"allowMockInput": false
}
}
```
---
## 8. 当前老字段到新结构的迁移建议
### 地图层
- `map` -> `map.tiles`
- `mapmeta` -> `map.mapmeta`
- `declination` -> `map.declination`
### 路线层
- `course` -> `playfield.source.url`
- `CPRadius` -> `playfield.CPRadius`
### 玩法层
- `game.mode` -> `game.mode`
- `game.punchPolicy` -> `game.punch.policy`
- `PunchRadius` -> `game.punch.radiusMeters`
- `game.autoFinishOnLastControl` -> `game.session.autoFinishOnLastControl`
### telemetry 层
- `game.telemetry.age` -> `game.telemetry.heartRate.age`
- `game.telemetry.restingHeartRateBpm` -> `game.telemetry.heartRate.restingHeartRateBpm`
- `game.telemetry.userWeightKg` -> `game.telemetry.heartRate.userWeightKg`
### feedback 层
- `game.audio` -> `game.feedback.audio``resources.audioProfiles`
- `game.haptics` -> `game.feedback.haptics``resources.hapticsProfiles`
- `game.uiEffects` -> `game.feedback.uiEffects``resources.uiEffectsProfiles`
当前建议迁移策略:
- 第一阶段:代码同时兼容老字段和新结构
- 第二阶段:线上配置逐步切换
- 第三阶段:再清理旧字段兼容逻辑
---
## 9. 未来推荐的 manifest 方向
当前阶段主配置建议先保持单文件。
但未来配置规模变大时,推荐升级成多 manifest 组合:
```json
{
"schemaVersion": "1",
"version": "2026.03.25",
"map": {
"manifest": "maps/lxcb-001/map.json"
},
"playfield": {
"manifest": "playfields/lxcb-001/c01.json"
},
"game": {
"manifest": "modes/score-o/default.json"
},
"resources": {
"manifest": "packs/spring-2026/resources.json"
},
"debug": {}
}
```
这样可以支持:
- 一张地图挂多种玩法
- 一条 playfield 挂多种规则
- 一种玩法切换不同资源包
- 后台管理做拼装式发布
---
## 10. 服务端和后台管理的推荐核心对象
后续从服务端和后台管理的复用角度,建议围绕以下核心对象建模:
- `Map`
- `Playfield`
- `GameMode`
- `ResourcePack`
- `Event`
其中:
- `Map`
地图底图与空间底座
- `Playfield`
当前玩法场景中的空间对象定义
- `GameMode`
玩法规则模板
- `ResourcePack`
资源包与 profile
- `Event`
一次实际发布的活动实例
推荐关系可以理解为:
`Event = Map + Playfield + GameMode + ResourcePack + 发布参数`
---
## 11. 当前阶段推荐结论
当前阶段最推荐的方案是:
- 先保留单个 `game.json`
- 结构升级为 `app / map / playfield / game / resources / debug`
- 保留 KML 作为空间底稿来源
- 不再让 `course` 成为总抽象,而是提升为更通用的 `playfield`
- 让代码先双兼容,再逐步迁移线上配置
一句话总结:
**KML 描述空间事实,配置描述玩法解释;主配置按 `map / playfield / game / resources / debug` 分层,后续再升级成 manifest 组合。**