Files
spellforge/docs_dev/specs/2026-07-21-meta-progression-mvp-design.md
T

9.8 KiB
Raw Blame History

元进展 MVP(碎片 + 解锁树 · 法术only)设计规范

日期2026-07-21 · 状态:设计已确认,待写实现计划 背景game_design §7 的元进展(以太碎片 / 解锁树 / 图鉴 / 成就)代码 0% 实现。本规范定义元进展的第一个子项目:碎片货币 + 法术解锁树的完整赚→花→门控闭环。图鉴、成就、核心解锁、挑战模式为后续子项目。


1. 目标与范围

目标:给游戏加入 Roguelite 长期粘性闭环——每局结束按波次获得以太碎片(全局持久、死亡不失),在主菜单花碎片解锁进阶法术,解锁后的法术进入商店掉落池。即使失败也有收获感。

设计原则(数据驱动):本系统所有可调数值(解锁费用、碎片奖励曲线)必须落在 data/*.json 且能在游戏设计器里可视化调整,不得硬编码。解锁费用走 spells.jsonmeta.unlock_cost(法术编辑器 meta 框可调,同 shop_cost/mana_cost);碎片奖励曲线走 balance.json 的阈值表(平衡页签可调)。

本次 MVP 包含

  • 全局持久的碎片货币(user://meta.json)。
  • 每局结束(死亡)按到达波次发放碎片。
  • 主菜单「🔓 解锁」面板:显示碎片、花碎片解锁锁定法术。
  • 内容门控:~6 张进阶法术开局锁定,其余开局可用;商店池只纳入"开局解锁 + 已解锁"的法术。
  • ST-03「清除所有本地数据」一并清空 meta.json。

明确不做(后续子项目/迭代)

  • 核心(Core)解锁(依赖未实现的商店货架 B)、挑战模式解锁。
  • 图鉴 Codex、成就 / 炼金日志。
  • 通关首胜额外奖励(通关流程本身未实现)。
  • 存档防篡改(单机本地存档,MVP 用明文 JSONEndlessRecords 的 HMAC 不引入此处)。

2. 架构:组件与职责

2.1 MetaProgress(新 Autoload)—— 全局元进展状态

scripts/autoloads/meta_progress.gd,注册进 project.godot

const PATH := "user://meta.json"
var aether: int = 0
var unlocked: Array = []      # 已解锁法术 idString

func _ready() -> void: load_meta()
func add_aether(n: int) -> void          # 累加并存档(n<=0 直接返回)
func is_unlocked(id: String) -> bool     # id 在 unlocked 中
func unlock(id: String, cost: int) -> bool  # 若已解锁 或 aether<cost → 返回 false(不扣费);否则扣费+加入 unlocked+存档→true
func load_meta() -> void                 # 读 meta.json;缺失/损坏→aether=0, unlocked=[]
func save_meta() -> void                 # 写 { "aether": int, "unlocked": [...] }
func clear() -> void                     # aether=0, unlocked=[], 删 meta.jsonST-03 调用)
  • 职责边界:只管"有多少碎片、解锁了哪些、怎么存读"。不知道法术费用(费用来自 spells.json,调用方传入)、不碰商店/UI。

2.2 spells.json 门控字段(可在法术编辑器调)

给 6 张进阶法术的 meta"unlock_cost": N(放 meta 内:法术编辑器把 meta 当自由 JSON 保留,不会像顶层字段那样被 rebuild 丢弃,且策划可在 meta 框直接改费用)。无 unlock_cost 或 =0 → 开局解锁。锁定法术集 = meta.unlock_cost>0 的法术(即"改哪张法术的 unlock_cost 就等于改解锁树",纯数据驱动)。

2.3 商店池门控(shop_manager.gd

_refresh_slots 现有过滤 shop_cost>0,改为:

shop_cost>0 且 (unlock_cost==0 或 MetaProgress.is_unlocked(id))

锁定且未解锁的法术不进商店池。

2.4 碎片结算(数据驱动曲线,balance.json + SettingsManager + combat_manager.gd

  • data/balance.json 增字段 "aether_thresholds": [[1,0],[5,3],[10,8],[15,14],[20,20]][到达波次, 发放碎片] 阶梯,升序)。
  • SettingsManager(已负责加载 balance.json)加 aether_for_wave(wave) -> int:返回波次 ≤ 到达波次 的最高阶梯对应的碎片(阶梯查表;缺字段则回退内置默认表并 push_warning)。超过最高阶梯按最高值封顶(Endless 亦然)。
  • combat_manager._on_player_died(已在重置前捕获到达波次)新增:MetaProgress.add_aether(SettingsManager.aether_for_wave(wave))
  • **平衡页签(balance_tab.gd**支持编辑 aether_thresholdsJSON 文本框,比照 core_tab 的 edges 编辑法),并在保存时保留该字段(不被 rebuild 丢弃)。

2.5 解锁界面(main_menu.gd

主菜单新增「🔓 解锁」按钮 → 程序化 CanvasLayer 面板(比照现有 settings 面板写法):

  • 顶部显示 以太碎片: N
  • 遍历 SpellRegistrymeta.unlock_cost>0 的法术,逐行:显示名 — 费用 X 碎片 — [解锁]按钮
  • 按钮态:已解锁→显示"已解锁"禁用;未解锁且碎片不足→禁用;足够→可点,点击 MetaProgress.unlock(id, cost) 成功后刷新面板。
  • 关闭按钮返回菜单。文案走 i18n(新增 META_* 键,4 语言)。

3. 数据流

开始→游玩→死亡
   │  combat_manager._on_player_died 捕获 wave
   ▼  MetaProgress.add_aether( SettingsManager.aether_for_wave(wave) )  → 存 meta.json(全局持久)
主菜单「🔓解锁」面板 → 花碎片 MetaProgress.unlock(id,cost) → unlocked[]+存档
   ▼
下一局商店 _refresh_slots:池 = shop_cost>0 且 (unlock_cost==0 或 已解锁)

4. 数值

默认锁定的 6 张法术 + 费用(写入各自 meta.unlock_cost):

法术 id unlock_cost 说明
action_energy_orb 4 慢速高伤
action_frost_bolt 5 冻结
action_poison_pool 6 地面毒池(zone)
logic_loop 6 循环(强力)
action_chain_bolt 8 连锁闪电
action_summon_turret 10 召唤炮台

其余 ~13 张(spark_bolt / fire_bolt / poison_dart / laser_beam / water_wave / damage_plus / spread_mod / double_cast / pierce_plus / trigger_on_hit / every_n_shots / if_hp_below / if_enemy_nearby)开局可用。解锁全部 = 39 碎片 ≈ 5 局(W10 档)。以上费用均写在各法术 meta.unlock_cost法术编辑器可调

碎片奖励阈值表balance.json.aether_thresholds平衡页签可调):[[1,0],[5,3],[10,8],[15,14],[20,20]]——[波次,碎片] 阶梯,到达波次取 ≤ 它的最高阶梯值(如 W12→8、W20→20、Endless W25→20 封顶)。贴合 §7.1 的 0/3/8/20。

action_plasma_storm 是共鸣产物、shop_cost=0,本就不在商店池,无需门控。


5. 边界与默认

  • meta.json 缺失/损坏 → 全新档(aether 0、unlocked 空),不崩溃。
  • 已解锁法术再点解锁 → is_unlocked 提前拦截 / 按钮禁用,不重复扣费。
  • 碎片不入局内存档(ProfileManager A/B)——它是全局的,只在 meta.json。
  • ST-03 清除数据:SettingsManager.clear_all_local_data 增调 MetaProgress.clear()
  • 门控只影响商店掉落池;玩家的初始预设卡组(action_spark_bolt)不受影响。

6. 测试计划(Godot MCP 运行时实测)

  1. 持久往返MetaProgress.add_aether(10)save_meta → 新实例 load_meta → aether=10、unlocked 保留。
  2. 解锁扣费aether=5unlock("action_frost_bolt", 5)→true、aether=0、is_unlocked=true;再 unlock("action_chain_bolt", 8)→false(不足)、状态不变。
  3. 商店池门控unlocked 为空时 _refresh_slots 抽样多次,锁定法术(如 chain_bolt)不出现;解锁后再抽,可出现。
  4. 碎片阈值表SettingsManager.aether_for_wave 对 1/5/10/20 返回 0/3/8/20W12→8、W25→20(封顶);改 balance.json.aether_thresholds 后值随之变(数据驱动)。
  5. 清除数据MetaProgress.clear() 后 aether=0/unlocked 空/文件删除。
  6. 解锁面板:主菜单点「解锁」渲染碎片数 + 6 行锁定法术;截图确认;点解锁按钮态正确变化。
  7. 编辑器可调balance_tab 编辑 aether_thresholds 保存后不丢字段(execute_editor_script 走 core-tab 同款 fresh-load 往返验证);法术编辑器改 meta.unlock_cost 保留。

7. 涉及文件清单

文件 改动
scripts/autoloads/meta_progress.gd 新建 MetaProgress autoload
project.godot 注册 MetaProgress autoload
data/spells.json 6 张法术 meta 加 unlock_cost(费用,法术编辑器可调)
data/balance.json aether_thresholds 阈值表(碎片曲线,平衡页签可调)
scripts/autoloads/shop_manager.gd _refresh_slots 过滤加门控条件
scripts/domain/combat/combat_manager.gd _on_player_diedMetaProgress.add_aether(SettingsManager.aether_for_wave(wave))
scripts/autoloads/settings_manager.gd 加载 aether_thresholds + aether_for_wave()clear_all_local_data 增调 MetaProgress.clear()
addons/game_designer/balance_tab.gd 平衡页签支持编辑并保留 aether_thresholds编辑器可调
scenes/ui/main_menu.gd 🔓解锁」按钮 + 解锁面板
translations/*.po META_* 四语键

8. 完成定义 (DoD)

  • 上述 6 项测试在 Godot 4.6 运行时全绿、0 编译错误。
  • 死亡后碎片按波次增加并持久(重启游戏仍在)。
  • 主菜单解锁面板可花碎片解锁法术;解锁后该法术出现在商店池、未解锁前不出现。
  • ST-03 清除数据把碎片/解锁清空。
  • 所有可调数值(解锁费用 / 碎片曲线)均在数据文件且可在游戏设计器里改:法术编辑器改 unlock_cost、平衡页签改 aether_thresholds,保存后运行时生效、不丢字段。
  • game_design §7 现状 callout 更新为「碎片+解锁树 已实现(MVP);图鉴/成就/核心解锁待后续」。