插件化隔离模型
05/122 浏览攻略
当前架构的隔离现状
已经隔离的部分 ✅ 尚未隔离的部分 ⚠️
───────────────────────── ────────────────────────────
cloud key 按 organId 命名 GameState — 全局单例,所有器官共享
food_queue_oral BeatSystem — 重置但只有一份实例
food_queue_stomach Enemy 池 — 清空但数据结构共用
food_queue_small_intestine Map — 替换但同一个对象
Config.OrganRules — 集中定义
问题的本质在于:现在器官之间是"清空后共用",不是"各自独立"。
口腔关卡结束 → 重置 BeatSystem → 胃关卡启动,用的是同一个 BeatSystem 实例。
真正的插件化隔离模型
每个器官是一个自包含的插件包,通道只认接口,不认实现:
┌──────────────────── 通道总线(Pipeline Bus)────────────────────┐
│ FoodPipeline.PushToOrgan / ConsumeFromOrgan / TransferToNext │
│ 唯一跨器官通信方式,其他模块不允许直接访问其他器官的数据 │
└─────────────────────────────────────────────────────────────────┘
│ │ │
┌────▼─────┐ ┌────▼─────┐ ┌────▼──────┐
│ 口腔插件 │ │ 胃插件 │ │ 小肠插件 │
│──────────│ │──────────│ │───────────│
│ 自己的 │ │ 自己的 │ │ 自己的 │
│ BeatCfg │ │ BeatCfg │ │ BeatCfg │
│ OrganRule│ │ OrganRule│ │ OrganRule │
│ 云端命名 │ │ 云端命名 │ │ 云端命名 │
│ 空间隔离 │ │ 空间隔离 │ │ 空间隔离 │
└──────────┘ └──────────┘ └───────────┘
│ │ │
organ.oral.* organ.stomach.* organ.small.*
(私有数据库) (私有数据库) (私有数据库)
数据隔离的三个层次
第一层:云端数据命名空间隔离
当前:food_queue_oral ← 只隔离了队列
理想:organ.oral.food_queue ← 所有器官数据统一前缀
organ.oral.beat_stats ← 节拍统计
organ.oral.damage_log ← 伤害记录
organ.stomach.food_queue
organ.stomach.beat_stats
每个器官只能读写 organ.{organId}.* 下的 key,跨器官读写必须通过 Pipeline Bus。
第二层:运行时状态隔离
当前:BeatSystem 是全局单例,reset 后给下一个器官用
理想:每个器官插件持有自己的 BeatContext:
oral.beatCtx = { beatCount, foodPool, phase, ... }
stomach.beatCtx = { beatCount, foodPool, phase, ... }
器官卸载时 beatCtx 销毁,不影响其他器官。
第三层:配置声明隔离
当前:Config.OrganRules 集中定义,所有器官写在一个表里
理想:每个器官插件声明自己的 manifest:
plugins/oral/manifest.lua → { rules, enemies, tileSet, beatConfig }
plugins/stomach/manifest.lua → { rules, enemies, tileSet, beatConfig }
主框架动态加载 manifest,而不是硬编码在 Config 里。
插件接口契约(每个器官必须实现的)
器官插件对外只暴露 4 个接口:
OnLoad(organId) ← 关卡加载时初始化自己的数据
OnFoodArrived(foodData) ← 接收来自上一器官的食材
OnFoodExiting(foodData) ← 食材离开前,决定传递给下一器官什么数据
OnUnload() ← 关卡结束时清理自己的状态
通道总线只调用这 4 个接口,从不直接访问插件内部数据。
和当前架构的差距
维度 当前 插件化目标 改造成本
云端数据 已按 organId 命名 统一前缀规范 低
运行时状态 共用实例 reset 各自持有 context 中
配置声明 集中在 Config.lua 分散到各自 manifest 中
器官接口 无明确契约 4 个标准接口 高(需重构)
跨器官通信 部分直接访问 只通过 Bus 中
结论:三个阶段对多人模式完全无影响
原因在于 CloudProxy 的设计:
FoodPipeline.lua
cloud:Set("food_queue_oral", data) ← 只传 key 字符串
│
▼
CloudProxy:Set(key, value)
│
├─ 多人模式 → client.CloudSave(key, value) ← 路由到 serverCloud
└─ 单机模式 → clientCloud:Set(key, value)
CloudProxy 是一个透明路由层,FoodPipeline 只管传 key,完全不知道底层走的是 serverCloud 还是 clientCloud。
逐阶段核实
阶段一:改 cloudKey 函数
lua
复制
-- 改前
local function cloudKey(organId)
return "food_queue_" .. organId -- "food_queue_oral"
end
-- 改后
local function cloudKey(organId)
return "organ." .. organId .. ".food_queue" -- "organ.oral.food_queue"
end
改的只是 key 的字符串格式。CloudProxy、Client.CloudSave、serverCloud 完全不感知 key 的命名规则,透传即可。多人模式零影响。
唯一需要注意的是:改名后旧 key 的存量数据会丢失(老队列 food_queue_oral 不会被自动迁移)。如果线上有正在积压的队列数据,需要一次性迁移脚本,或者选择在没有积压时切换。
阶段二:拆分 Config.OrganRules
这是纯本地代码结构调整,Config 数据最终读进内存后和现在一模一样,serverCloud 完全不涉及。多人模式零影响。
阶段三:BeatSystem 改 context 机制
BeatSystem 是纯本地运行时状态(内存中的 foodPool、phase、timer),不持久化到云端。多人模式零影响。
唯一需要协调的点
你们是 8 人多人模式,多个玩家的 food_queue_{organId} 数据存在同一个 serverCloud 里。
当前架构下,所有玩家共享同一个 food_queue_oral key,这意味着:
玩家A投喂 → 写入 food_queue_oral
玩家B投喂 → 写入 food_queue_oral(同一个 key,追加到同一列表)
玩家C进入口腔关卡 → 读取 food_queue_oral → 拿到所有人投喂的食材
这是设计上有意为之的(多人互喂、共享消化压力),还是你们希望每个玩家有自己的私有队列?这个问题和阶段一的命名空间改造直接相关,改之前值得确认一下。
多人数据拓扑图
投喂玩家 B ──┐
投喂玩家 C ──┼──→ body.{A的uid}.organ.oral.food_queue
投喂玩家 D ──┘ │
▼
扮演口腔的玩家 ──→ 【口腔关卡 TD】── 结算 ──→ body.{A的uid}.organ.oral.stats
│
▼ (食材通过出口传递)
扮演胃的玩家 ──→ 【胃关卡 TD】 ── 结算 ──→ body.{A的uid}.organ.stomach.stats
│
▼
body.{A的uid}.blood ← 血液指标汇总
body.{A的uid}.sensors ← 感官数据
│
▼
玩家 A(身体主)──→ 【大脑监控室】← 只读聚合所有 organ.*.stats
数据隔离设计:三个命名空间层级
body.{ownerUid}. ← 第一层:身体归属(谁的身体)
organ.{organId}. ← 第二层:器官隔离(哪个器官)
food_queue ← 食材待处理队列
stats ← 本器官当前状态(HP、伤害等)
beat_log ← 节拍结算记录
actor_uid ← 当前扮演者的 uid
blood. ← 跨器官汇总(血液指标)
glucose / lipid / sodium
sensors. ← 感官层汇总
taste / smell
每一层的读写权限不同:
数据路径 谁可以写 谁可以读
body.A.organ.oral.food_queue 所有投喂玩家 口腔扮演者
body.A.organ.oral.stats 口腔扮演者 玩家A(大脑)、所有人
body.A.organ.stomach.food_queue 口腔扮演者(传递) 胃扮演者
body.A.blood.* 各器官结算后写入 玩家A(大脑)只读
body.A.organ.*.actor_uid 玩家自己报名扮演时 排行榜展示
插件化在这里的完整含义
每个器官插件包含三个边界:
┌──────────────────── 口腔插件 ────────────────────┐
│ │
│ 私有输入: body.A.organ.oral.food_queue(只我读) │
│ 私有输出: body.A.organ.oral.stats(只我写) │
│ 下游通道: body.A.organ.stomach.food_queue(我写) │
│ │
│ 扮演者绑定: actor_uid(谁在控制这个器官) │
│ │
└───────────────────────────────────────────────────┘
胃/小肠/大肠结构完全一样,只是 organId 不同。这才是真正的插件化:每个器官有且只有一个输入队列、一个输出队列、一个状态表,边界清晰,互不干扰。
当前架构的最大风险点
基于你描述的玩法,现在最需要解决的不是三个阶段,而是一个更根本的问题:
当前 key 没有 ownerUid 层
现在:food_queue_oral
↑ 只有一份,所有玩家共用同一个 key
需要:body.{A的uid}.organ.oral.food_queue
↑ 每个身体主有自己独立的一份
如果 8 个人同时在线,每个人的身体是独立的(各自有自己的大脑监控室),那么现在所有人实际上在向同一个 food_queue_oral 写数据,会产生混淆。
因此阶段一的改造比之前说的更重要
lua
复制
-- 改前(所有玩家共享一个队列)
local function cloudKey(organId)
return "food_queue_" .. organId
end
-- 改后(每个身体主有独立命名空间)
local function cloudKey(ownerUid, organId, dataType)
return "body." .. ownerUid .. ".organ." .. organId .. "." .. dataType
end
-- 使用示例
cloudKey(12345, "oral", "food_queue") → "body.12345.organ.oral.food_queue"
cloudKey(12345, "oral", "stats") → "body.12345.organ.oral.stats"
这一个改动,直接决定了多人模式下各个玩家身体之间的数据隔离是否成立。



