# 模块联动规范 本文档定义模块间联动逻辑的处理方案:谁调用谁、变更怎么传播、如何保证一致性。联动场景包括「建筑需要弟子才有产出」「炼丹需输入药材→产出丹药」「消耗药材→库存减少」等跨系统交互。 ## 1. 方案组合结论 | 层 | 选择 | 理由 | | --- | --- | --- | | 主动操作联动(炼丹/交货/种植) | 直接调用 + 依赖注入 | 逻辑显式、可断点调试、单测零成本 | | 回合结算顺序 | 固定管线(MainGame 依次调用) | 消除时间耦合,顺序可预期 | | 状态变更通知(UI/统计/音效) | 信号广播(观察者,单向) | 不引入依赖,加监听者零改动 | | 全局事件总线 / 规则引擎 | 不做,数据形状预留 | P1~P3 联动类型仅个位数,通用引擎成本大于收益 | **明确不用的方案**: - 不用事件总线处理权威状态变更(如扣库存):请求-响应型联动需要双向信号,代码量反超直接调用,且结算顺序难追踪。事件广播只用于通知,不用于变更。 - 不用规则引擎全量执行联动:为 4 类联动(种植收获/炼丹/交货/修炼)写通用执行器是净亏损。P4 功法/术法批量进场(联动类型持续增长)时再评估抽成引擎。 ## 2. 联动场景的本质 | 场景 | 本质 | 参与方 | | --- | --- | --- | | 建筑需要弟子才有产出 | 前置条件检查(人手占用) | 生产系统 ↔ 弟子系统 | | 炼丹需输入药材→产出丹药 | 资源转换(事务) | 炼丹系统 ↔ 库存系统 | | 消耗药材→库存减少 | 库存变更 + 广播 | 调用方 → 库存 → UI/统计 | 统一模式:**A 向 B 提需求(检查)→ B 执行变更 → 变更通知所有关心者**。 ## 3. 依赖管理铁律 ### 3.1 依赖图无环(DAG) 库存、经济是叶子节点,谁也不依赖;生产类依赖库存/弟子;订单依赖库存+经济。**任何 Manager 不得依赖其上层**,出现环即设计错误。 ``` OrderManager ──→ InventoryManager(叶子) └──→ EconomyManager(叶子) AlchemyManager ─→ InventoryManager FieldManager ───→ InventoryManager DiscipleManager ─→ EconomyManager(修炼扣灵石) FoodManager ─────→ InventoryManager └─→ DiscipleManager ``` **已知隐患**(P2 启动前必须复查): - 忠诚/抽成:弟子抽成 → 经济结算方向仍是 弟子→经济,不构成环;但若经济系统反过来查询弟子(按忠诚发分红)会成环,届时改走信号通知。 - 收徒拜师礼:一次性收入走 EconomyManager,同向,无环。 ### 3.2 注入点唯一 所有 `setup()` 集中在 MainGame 一处,Manager 自身零 autoload 引用(TimeSystem/SaveSystem 除外按需)。这是依赖可管理的根源: ```gdscript # MainGame._ready() 中: economy_manager.setup() inventory_manager.setup() field_manager.setup(inventory_manager) alchemy_manager.setup(inventory_manager) food_manager.setup(disciple_manager, inventory_manager) order_manager.setup(inventory_manager, economy_manager) disciple_manager.setup(economy_manager) ``` 好处:测试场景只注入 Mock 依赖即可单独运行;依赖关系读一遍 MainGame 全貌可见。 ### 3.3 结算顺序管线化 `month_passed` 固定按序调用各 Manager 的 `settle_month()`,消除时间耦合: ``` 灵田(收获入库存) → 炼丹(自动生产) → 弟子(修炼结算) → 口粮(凡人弟子消耗) → 订单(过期检查) → 经济(固定收入) ``` 顺序依据:口粮必须在收获后扣(吃的是新粮);经济最后(汇总本月收支)。新增系统只插入管线,不改变既有顺序。 ## 4. 原子事务约定(check-take-give) 联动变更必须原子:先检查所有前置条件与材料,全部通过后再执行扣除与产出,**不允许扣一半失败**。 ### 4.1 收敛到库存层 「先检查后扣除」的手工纪律收敛为库存层的两个方法,调用方不可能写错: ```gdscript # InventoryManager func try_take(items: Dictionary) -> bool # 全量检查,够则返回 true func take(items: Dictionary) -> void # 实际扣除(try_take 通过后调用) func add_item(item_id: String, count: int) -> void func has_items(items: Dictionary) -> bool # 只查不扣(供 UI 显示可用性) ``` ### 4.2 调用方模板 ```gdscript # AlchemyManager.refine() —— 主动操作联动标准写法 func refine(recipe_id: String, disciple: Disciple) -> bool: var r: Dictionary = ContentDB.get_recipe(recipe_id) if disciple.alchemy_skill < r["skill_req"]: return false if not inventory.try_take(r["inputs"]): return false inventory.take(r["inputs"]) inventory.add_item(r["output_id"], r["output_count"]) pill_refined.emit(r["output_id"]) return true ``` ## 5. 单一写入口 每种数据只允许其归属 Manager 变更: | 数据 | 唯一写入口 | 外部只能 | | --- | --- | --- | | 库存物品 | `InventoryManager.take/add_item` | 调用这两个方法 | | 灵石余额 | `EconomyManager.spend/earn` | 调用这两个方法 | | 弟子属性 | `DiscipleManager.set_attr` | 调用该方法 | | 弟子增删 | `DiscipleManager.add/remove_disciple` | 调用该方法 | 违反此规则的直接后果:数据被谁改的不可追踪,信号漏发,UI 不同步。 ## 6. 信号分工 | 信号类型 | 用途 | 方向 | | --- | --- | --- | | Manager 发出的领域信号(`inventory_changed`/`pill_refined`…) | UI 刷新、统计、音效 | 后端 → 前端/其他监听者,单向 | | TimeSystem 的 `month_passed` 等 | 回合结算管线入口 | 已定,不改 | | UI 调用 Manager 公开方法 | 玩家操作 | 前端 → 后端,直接调用不走信号 | 原则:**信号只通知,不携带状态变更职责**。UI 收到信号后从后端读最新值,不依赖信号参数里的数据(参数仅作提示)。 ## 7. 规则引擎的预留(P4 退路) P1 定义配方数据时,形状直接写成 `requires / consumes / produces` 三段(逻辑在代码手写): ```gdscript { "id": "refine_peiyuan", "requires": [{"type": "disciple_skill", "skill": "alchemy", "min": 3}], "consumes": [{"item": "herb_lingzhi", "count": 2}], "produces": [{"item": "pill_peiyuan", "count": 1}], } ``` 将来联动类型膨胀(P4 功法/术法、炼丹失败率、双产出等)时,把各 Manager 手写逻辑抽成通用执行器(检查 requires → 扣 consumes → 给 produces,天然原子),**数据文件一行不用改**。 评估时机:新增联动类型需要改多个 Manager 的代码时,即考虑抽取。当前(P1)不抽取。 ## 8. 违背规范的常见症状 | 症状 | 根因 | 对应规范 | | --- | --- | --- | | 库存数量莫名变化 | 绕过 InventoryManager 直接改 | §5 单一写入口 | | 扣了一半材料失败 | 未先 try_take 全量检查 | §4 原子事务 | | 结算结果依赖按钮点击顺序 | 未走固定管线 | §3.3 结算顺序 | | Manager 互相调用成环 | 依赖方向失控 | §3.1 DAG | | 换 UI 后数据不刷新 | 变更未发信号 | §6 信号分工 |