新增Disciple弟子数据类与DiscipleManager管理器及弟子属性设计文档
This commit is contained in:
+158
@@ -0,0 +1,158 @@
|
||||
# 模块联动规范
|
||||
|
||||
本文档定义模块间联动逻辑的处理方案:谁调用谁、变更怎么传播、如何保证一致性。联动场景包括「建筑需要弟子才有产出」「炼丹需输入药材→产出丹药」「消耗药材→库存减少」等跨系统交互。
|
||||
|
||||
## 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 信号分工 |
|
||||
Reference in New Issue
Block a user