Files
spellforge/docs_dev/specs/2026-07-20-mana-system-mvp-design.md
T
joywayerandClaude Opus 4.8 39e05f1f9a docs: Mana 系统精实 MVP 设计规范(待评审)
单一法杖蓝池 + 逐效果扣蓝 + 蓝空停施 + HUD 蓝条;数值取 numerical_design §2。
暂不做:递增蓝耗、infinite_spells 扣血、全局+法杖取Min、cast_delay 重构。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 16:05:06 +08:00

8.7 KiB
Raw Blame History

Mana 魔力系统 —— 精实 MVP 设计规范

日期2026-07-20 · 状态:设计已确认,待写实现计划 背景Mana 是 game_design §4.1 声称的核心平衡阀(限制"无限加特林"),但代码 0% 实现——无 mana 变量、无 mana_cost 数据,连 CoreDefinition 都缺 mana 字段。本规范定义"精实 MVP":让 Mana 成为真实可玩的资源门槛,暂不实现进阶反作弊/边缘机制。


1. 目标与范围

目标:法术施放消耗魔力;魔力随时间回复;魔力耗尽时停止施法,形成"高射速/高爆发 ↔ 魔力"的平衡张力,并有 HUD 蓝条可视化。

本次 MVP 包含

  • 单一魔力池(取自当前激活法杖 Core 的 mana_max + mana_regen/秒)。
  • 每个法术节点消耗其 mana_cost(按 numerical_design §2 权威数值)。
  • 施法时逐节点扣蓝;某节点付不起 → 停下本次施法,跳过剩余节点(已发射的动作保留)。
  • HUD 蓝条。
  • cores.json 加 mana 字段、spells.jsonmana_costCoreDefinition 加字段。

明确不做(后续迭代)

  • 持续射击递增蓝耗numerical §5.A 反"无限蓝机枪")。
  • infinite_spells Core 蓝空时扣 HP
  • 全局蓝池 + 法杖上限取 MinMVP 只有法杖单池)。
  • cast_delay/施法后摇重构(保持现有 cast_interval 自动施法节奏不变)。

2. 架构:组件与职责

数据状态、回蓝驱动、消耗门控三者分离,各自单一职责。

2.1 PlayerStats(Autoload)—— 魔力状态与操作

新增字段与方法:

var mana:        float = 100.0
var mana_max:    float = 100.0
var mana_regen:  float = 5.0     # 每秒回复

func set_mana_pool(max_v: float, regen: float) -> void   # 换杖时调用,重置 max/regen 并把 mana 夹到 [0,max]
func spend_mana(cost: float) -> bool                     # 够则扣除返回 true,否则不变返回 false
func regen_mana(delta: float) -> void                    # mana = min(mana_max, mana + mana_regen*delta);变化时 emit stats_changed
  • mana 纳入 stats_changedHUD 刷新用)。
  • reset_for_run() 增加 mana = mana_max(开局满蓝)。
  • 不纳入存档get_save_data/load_save_data 不含 mana)——回蓝以秒计,续玩从满蓝开始即可。
  • 职责边界:只管"当前有多少蓝、能不能花、怎么回",不知道谁在花。

2.2 CoreDefinition+2 字段)

@export var base_mana_max:   float = 100.0
@export var base_mana_regen: float = 5.0

WandPreset.make_core_by_id()cores.json 读入(缺省用上面默认值,向后兼容旧数据)。

2.3 PlayerManagerAutoload)—— 驱动

  • equip_wand(core, compiled):新增 PlayerStats.set_mana_pool(core.base_mana_max, core.base_mana_regen)core 为 null 时用默认)。
  • _physics_process(delta):新增一行 PlayerStats.regen_mana(delta)(回蓝持续进行,含商店/施法间隙)。
  • 职责边界:帧驱动器,只负责"按时回蓝、按时触发施法",不持有蓝量。

2.4 SpellEvaluator.execute_compiled(消耗门控)

核心规则:扣蓝发生在节点效果"实际生效处",而非盲目逐个遍历——否则会给被控制流跳过、并未真正发射的节点误扣蓝(现有语义"每次施法仅首个 ACTION 生效,除非 multicast/double_cast")。

具体扣蓝点:

  • ACTION 实际发射时(_push_projectile 及其 zone/summon 变体)扣其 mana_cost;若付不起 → 不发射该动作,且中止本次施法剩余部分
  • MODIFIER_apply_modifier 应用时扣其 mana_cost;付不起同样中止。
  • TRIGGER 注册 SubPayload 时扣其 mana_cost;命中后 SubPayloadexecute_sub,异步 on-hit)执行不再扣蓝(MVP 简化,避免命中回调里做资源判定)。
  • 被控制流跳过的节点不扣蓝:超出 actions_remaining 的 ACTION、被 LOGIC 门跳过的分支、被共鸣 _consumed 标记的输入卡,均不产生蓝耗。
  • 中止信号机制ctx 上的 depleted 标志位 vs 效果函数返回 bool 令主循环 break)留实现计划敲定;语义目标一致:一旦某要生效的节点付不起蓝,本次施法就此打住,已生效部分保留。
  • 仅主施法流消耗魔力execute_compiled 这一遍);SubPayload 的 on-hit 执行不消耗。

2.5 combat_s2 HUD —— 蓝条

  • 在 HUD 区新增蓝色 ProgressBar(或等效控件),max_value = PlayerStats.mana_maxvalue = PlayerStats.mana
  • 订阅 PlayerStats.stats_changed 刷新(HUD 已订阅该信号刷新其他数值)。
  • 文案键走 i18n(如需文字标签,新增 UI_MANA 四语键)。

3. 数据流

装备法杖 → PlayerStats 蓝池置满(max/regen 来自 Core)
   │
每帧 → PlayerManager.regen_mana(delta) → 蓝量上升至 max
   │
每 cast_interval 秒 → PlayerManager 触发 execute_compiled
   │
execute_compiled 逐节点 → spend_mana(cost)?付得起就执行,付不起就 break
   │
stats_changed → HUD 蓝条更新

4. 数值(权威来源 numerical_design §2

基础法杖mana_max = 100mana_regen = 5/秒

每法术 mana_costdoc ID → 实际 data ID 映射):

doc §2 实际 spells.json id mana_cost
spark_bolt action_spark_bolt 5
double_cast modifier_double_cast 2
spread_mod modifier_spread_mod 0
damage_plus modifier_damage_plus 15
trigger_hit trigger_on_hit 10
chain_bolt action_chain_bolt 60
nuke (若存在) action_nuke 200
homing (若存在) modifier_homing 40

赋值规则spells.json 中未被 §2 列出的法术):按类型/Tier 给缺省——

  • LOGIC / 纯控制节点:0
  • 小修正(如 pierce+):~8
  • Tier1 ACTION510Tier2 ACTION2030Tier3 ACTION60+
  • 召唤/地面(poison_pool / summon_turret):~2535
  • 共鸣产物(plasma_storm,不在商店):0;且被 _consumed 标记的输入卡按 §2.4 不扣蓝 → 共鸣在 MVP 里整体免蓝,作为凑出组合的奖励(可接受;后续如需再收费另议)

实现时以实际 data/spells.json 的完整 id 列表为准逐条填 meta.mana_cost;本表为权威值锚点。


5. 边界与默认(已确认)

  • 开局/开波满蓝reset_for_run 置满;波间在商店持续回蓝。
  • 不存档:续玩从满蓝开始。
  • 蓝空施法:本次施法不产出任何东西,但 cast_timer 照常重置——形成"憋蓝"手感门槛。
  • nuke(200) > 基础 max(100):基础法杖无法施放核弹,须换更高蓝上限的 Core(有意的构建门槛)。
  • 换杖夹值set_mana_pool 把当前 mana 夹到新 [0, max]

6. 测试计划(Godot MCP 运行时实测,同 bug 修复流程)

  1. 回蓝mana 从低值随时间上升并封顶于 mana_max
  2. 逐节点扣蓝:满蓝下施放 [modifier_damage_plus(15), action_spark_bolt(5)]mana 减少 20。
  3. 停下跳过(首节点付不起)mana=3、deck [action_spark_bolt(5), ...] → 无子弹产出(BulletManager.get_active_count()==0)。
  4. 部分施放mana=8、deck [spark(5), spark(5)] + double_cast 令两发都尝试 → 第 1 发发射、第 2 发被跳过。
  5. HUD:蓝条 value 随施放/回蓝变化;截图确认蓝条渲染。
  6. 回归:既有法术链(伤害、pierce、persistent memory [0,0,1,0])在加入扣蓝后行为不变(付得起时)。

7. 涉及文件清单

文件 改动
scripts/autoloads/player_stats.gd +mana 状态/方法、reset/stats_changed 集成
scripts/domain/spell_system/core_definition.gd +base_mana_max/base_mana_regen
scripts/autoloads/wand_preset.gd 从 cores.json 读入两字段
scripts/domain/player_manager.gd equip 时置池、_physics_process 回蓝
scripts/domain/spell_system/spell_evaluator.gd execute_compiled 逐节点扣蓝门控
scenes/main/combat_s2.gd HUD 蓝条 + stats_changed 刷新
data/cores.json 每 Core +mana_max/mana_regen
data/spells.json 每法术 +mana_cost
translations/*.po (如需)UI_MANA 四语键

8. 完成定义 (DoD)

  • 上述 6 项测试在 Godot 4.6 运行时全部通过、0 编译错误。
  • 基础法杖满蓝 100、回蓝 5/s;连打 spark_bolt 会见蓝下降并在停火后回满。
  • 纯 Modifier 或蓝不足时不产出子弹(与既有 Deck 防呆互补)。
  • HUD 蓝条可见并实时反映蓝量。
  • 文档 numerical_design/core_wand_design 的 Mana 现状 callout 更新为"已实现(MVP"。