config-design-proposal.md 11 KB

游戏配置文件设计方案(阶段讨论稿)

本文档用于整理当前阶段推荐的配置文件设计方案,供后端、客户端和后台管理设计参考。
目标是让配置真正成为游戏的驱动入口,同时兼顾后续多玩法、多资源、多活动复用。


1. 设计目标

配置文件系统需要解决以下问题:

  • 驱动地图、玩法、资源、调试开关
  • 支持顺序赛、积分赛以及后续更多玩法
  • 支持将来后台管理系统的内容编排
  • 保证地图空间信息与玩法语义分层
  • 保证当前阶段可平滑迁移,不推翻已有实现

当前推荐原则:

  • 配置只描述,不执行逻辑
  • 地图、空间对象、玩法规则、资源包分层
  • KML 负责空间底稿,不负责复杂玩法语义
  • 主配置先保持单文件,后续再升级为 manifest 组合

2. 顶层配置结构

当前推荐主入口配置结构如下:

{
  "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 的一种

例如:

{
  "playfield": {
    "kind": "course"
  }
}

或:

{
  "playfield": {
    "kind": "control-set"
  }
}

4. KML 与配置的边界

当前推荐边界非常明确:

4.1 KML 负责空间底稿

KML 适合描述:

  • 点坐标
  • 起点 / 检查点 / 终点
  • 顺序号
  • 点位名称
  • 腿线几何

4.2 配置负责玩法解释

配置负责描述:

  • 点位分值
  • 打点规则
  • 显隐规则
  • 动态积分
  • 道具能力
  • 迷雾规则
  • 占领规则
  • 特殊玩法语义

一句话总结:

KML 描述空间事实,配置描述玩法解释。


5. 推荐的字段结构

5.1 app

用于活动级基础信息。

示例:

{
  "app": {
    "id": "lxcb-001",
    "title": "雪熊领秀城区定向赛",
    "locale": "zh-CN"
  }
}

5.2 map

用于地图底图与空间底座。

示例:

{
  "map": {
    "tiles": "lxcb-001/tiles/",
    "mapmeta": "lxcb-001/tiles/meta.json",
    "declination": 6.91,
    "initialView": {
      "zoom": 17
    }
  }
}

5.3 playfield

用于描述当前玩法使用的空间对象及其来源。

示例:

{
  "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

用于描述玩法规则。

推荐统一结构如下:

{
  "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。

示例:

{
  "resources": {
    "audioProfile": "default",
    "contentProfile": "default",
    "themeProfile": "default-race"
  }
}

当前阶段建议先保持轻量,后续再逐步拆成资源包 manifest。

5.6 debug

用于开发和调试相关开关。

示例:

{
  "debug": {
    "allowModeSwitch": false,
    "allowMockInput": false,
    "allowSimulator": false
  }
}

6. 顺序赛示例配置

{
  "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. 积分赛示例配置

{
  "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.audioresources.audioProfiles
  • game.haptics -> game.feedback.hapticsresources.hapticsProfiles
  • game.uiEffects -> game.feedback.uiEffectsresources.uiEffectsProfiles

当前建议迁移策略:

  • 第一阶段:代码同时兼容老字段和新结构
  • 第二阶段:线上配置逐步切换
  • 第三阶段:再清理旧字段兼容逻辑

9. 未来推荐的 manifest 方向

当前阶段主配置建议先保持单文件。
但未来配置规模变大时,推荐升级成多 manifest 组合:

{
  "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 组合。