589 lines
10 KiB
Markdown
589 lines
10 KiB
Markdown
# 游戏配置文件设计方案(阶段讨论稿)
|
||
|
||
本文档用于整理当前阶段推荐的配置文件设计方案,供后端、客户端和后台管理设计参考。
|
||
目标是让配置真正成为游戏的驱动入口,同时兼顾后续多玩法、多资源、多活动复用。
|
||
|
||
---
|
||
|
||
## 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 组合。**
|
||
|