下载 App

插件化架构模块

07/033 浏览

本项目建议采用插件化设计,把规则、主题、美术、问题卡、多人同步拆开。这样可以先做最小可玩版本,再逐步启用八卦、黑白视觉和多人扩展。

模块分层

text复制
text
GameCore ├─ RuleSetModule │ ├─ RuleSet2P │ ├─ RuleSet3P │ └─ RuleSet4P ├─ TileModule ├─ QuestionCardModule ├─ BaguaThemeModule ├─ VisualThemeModule ├─ TileFacePlugin ├─ MultiplayerModule ├─ PersistenceModule └─ AIModule

GameCore

负责游戏主循环和状态流转。
职责:
  • 初始化游戏。
  • 管理当前模式。
  • 调用规则模块。
  • 调用问题卡模块。
  • 管理阶段:准备、发牌、提问、推测、结算。
不负责:
  • 具体 UI 样式。
  • 具体八卦文案。
  • 网络传输细节。

RuleSetModule

按人数拆分规则。

RuleSet2P

  • 每人 5 张。
  • 无中央目标。
  • 推测对象是对手手牌。
  • 普通问题只由对手回答。
  • 共享问题双方都回答。

RuleSet3P

  • 每人 5 张。
  • 中央 5 张。
  • 推测对象是中央牌。
  • 所有问题都由全体玩家回答。
  • 推测失败者退出竞争。

RuleSet4P

  • 每人 4 张。
  • 中央 4 张。
  • 禁用四人不适用问题卡。
  • 所有问题都由全体玩家回答。
  • 推测失败者退出竞争。

TileModule

负责数字板块。
职责:
  • 创建 20 张板块。
  • 洗牌。
  • 排序。
  • 比较序列是否一致。
  • 计算位置、颜色、奇偶、求和等基础信息。
建议数据:
lua复制
lua
Tile = { id = "W6", number = 6, color = "white", polarity = "yang" }

QuestionCardModule

负责问题卡。
职责:
  • 加载 21 张问题卡表。
  • 根据模式过滤卡牌。
  • 维护问题牌堆、公开区、弃牌堆。
  • 根据卡牌类型计算回答。
关键接口建议:
lua复制
lua
QuestionCardModule.GetAvailableCards(mode) QuestionCardModule.DrawOpenCards(count) QuestionCardModule.ResolveAnswer(card, hand, selectedParam) QuestionCardModule.IsDisabledInMode(card, mode)

BaguaThemeModule

负责八卦身份。
职责:
  • 根据数字返回卦名、符号、五行、方位。
  • 给问题卡生成八卦化标题。
  • 给数字板块生成显示名称。
它不参与胜负判定。

VisualThemeModule

负责视觉主题。
可拆成多个主题:
text复制
text
ClassicColorTheme 红蓝实体桌游风 YinYangCartoonTheme 黑白阴阳卡通风 InkMinimalTheme 极简水墨风
当前推荐:YinYangCartoonTheme。

TileFacePlugin

负责单张数字板块的牌面布局。
当前推荐插件:NoTextBlackWhiteTileFace。
职责:
  • 让牌面只显示大数字、卦象、卦名。
  • 通过白底黑字 / 黑底白字区分白牌和黑牌。
  • 禁止在牌面直接写“阳面 / 阴面 / 阳爻 / 阴爻”。
  • 保证同数字的白牌与黑牌卦象一致,只改变底色、线条和纹样。
建议配置:
lua复制
lua
TileFacePlugin = { name = "NoTextBlackWhiteTileFace", showPolarityText = false, showNumber = true, showTrigram = true, showTrigramName = true, showElement = false, showDirection = false, }
详见:07-无文字黑白牌面插件模块.md。

MultiplayerModule

负责多人通信。
职责:
  • 创建房间。
  • 加入房间。
  • 玩家准备。
  • 发送动作命令。
  • 接收完整快照。
  • 接收增量事件。
  • 重连恢复。
原则:客户端只提交动作,不提交最终状态。

PersistenceModule

负责持久化。
职责:
  • 保存玩家资料。
  • 保存房间临时状态。
  • 保存排行榜整数分数。
  • 处理断线 flush。
  • 防止初始化覆盖云端数据。

AIModule

后续可选模块。
职责:
  • 单人练习。
  • AI 对手。
  • 推理提示。
  • 自动记录可能性。
建议最后实现,不要在第一版加入。

插件启用配置建议

lua复制
lua
GameConfig = { rules = "standard", theme = "bagua", visual = "yinyang_cartoon", tileFace = "no_text_black_white", multiplayer = true, persistence = true, advancedBaguaQuestions = false, }

最小可玩组合

第一版推荐启用:
text复制
text
GameCore RuleSet2P TileModule QuestionCardModule BaguaThemeModule YinYangCartoonTheme NoTextBlackWhiteTileFace
暂缓:
text复制
text
RuleSet3P / RuleSet4P MultiplayerModule PersistenceModule AIModule AdvancedBaguaQuestions

设计收益

插件化之后可以做到:
  • 保留经典红蓝版本。
  • 一键切换黑白阴阳版本。
  • 二人规则和三四人规则互不干扰。
  • 八卦主题不污染核心数学逻辑。
  • 多人同步可单独测试。
  • 问题卡可独立扩展。
地图编辑器仿真 tick 重建抑制
UI 重建频率 = 事件分发的可靠性上限。 只要有一帧 SetRoot 销毁了正在处理 pointer 事件的控件,跨重建边界的 Down/Up 对就会被吞。高频仿真 tick 必须在 UI 边界按页面过滤。 关键代码(scripts/main.lua HandleUpdate) 四步走:先原地处理人员 → 聚合所有仿真 changed → 地图页强制压制 → 只放行显式/登录态变化。 -- ma
官方
4 赞
穿透层修复
经验一:CSS transform 穿透层修复模式 问题本质 当容器使用 CSS transform: translateX/Y/scale 平移/缩放子树时,即使父容器有 overflow = "hidden",引擎的命中检测可能不裁剪越界区域。如果 transform 后的子树有更高的 zIndex,它会在视觉范围外拦截其他兄弟节点的指针事件。 受影响的场景 地图编辑器(右键平移/滚轮缩放后)
官方
4 赞
弹窗内做"搜索 + 分类 + 网格"
经验 1:弹窗内做"搜索 + 分类 + 网格",用局部刷新保住输入焦点 为什么有效:搜索框是 UI.TextField,如果每次输入都 RebuildUI 重建整棵树,焦点会丢、光标乱跳。正确做法是只把"网格容器"这一棵子树的子节点 ClearChildren + AddChild,搜索框和分类标签不动。模块级变量 objectCategory_ 跨弹窗开关不重置,天然实现"记住上次分类"。 关键
官方
地图编辑器「墙」全链路增强
经验 1:子编辑器"连续操作" vs "一次性操作"——提交后别清空激活态 现象:放完一段墙,提示"可继续拖拽放置",但再拖没反应。 根因:CommitWall 提交后把 selectedItem=nil、tool="select",导致 IsActive() 返回 false,下次 HandlePointerDown 第一行就 return。提示语与行为矛盾。 模式:连续工具(墙、擦除)提交后只
官方
4 赞
【日常更新】支持2人、3人、4人对战截图
【日常更新】支持2人、3人、4人对战
优化了排行榜显示玩家昵称
3 赞
编辑器实体 zIndex 层级
问题 编辑器中墙段被房间面板覆盖不可见。原因:给房间设 zIndex=1 会创建 stacking context,其内部带背景色/TiledImage 的 floorLayer 作为 paint layer 在合成时盖过了同层级 zIndex=2 的墙段——差距太小不够可靠。 解决:底层不设 zIndex,上层用明显间距 错误做法(zIndex 间距太小 + 给底层也设 zIndex): --
官方
1 赞
右键拖拽平移
一、问题本质 不是平移公式写错,而是 UrhoX UI 库指针事件派发的两个不对称陷阱叠加: 陷阱 现象 根因文件 OnPointerMove 不向父冒泡 右键按下 isPanning_=true,但 move 事件永远到不了 mapView urhox-libs/UI/Core/Widget.lua(OnPointerDown/Up 有 parent 冒泡,OnPointerMove 没有) H
官方
4 赞
以鼠标为锚点缩放
核心数学原理 缩放前后,鼠标指向的那个世界坐标点必须保持在同一屏幕位置。 已知:旧缩放 oldZoom、新缩放 newZoom、鼠标锚点屏幕坐标 (ax, ay)、当前偏移 (panX, panY) 步骤1:算出旧缩放下鼠标指向的世界坐标 wx = (ax - panX) / oldZoom wy = (ay - panY) / oldZoom 步骤2:新缩放后反解新偏移,让 wx/wy
官方
1 赞
精灵平滑移动、墙碰撞体积
一、 渲染层用连续坐标(scripts/页面/校园地图.lua,三处统一): -- NPC 定位(第408-410行) local contLeft = (character.x or 0) * 5 - 18 - charLeft local contTop = (character.y or 0) * 5 - 36 - charTop -- 主角定位(第504-506行) local con
官方
1 赞
碰撞方案
核心教训 1. 格子像素必须匹配精灵尺寸 5px格子+36px精灵=7.2:1比例,任何碰撞方案(AABB/空气墙/单点/格中心)都无法同时满足"不穿墙+四面间距一致+不跳格"。对齐环世界32px后,36px精灵≈1.1格,问题根本解决。 2. 连续渲染 + 格级碰撞的分层设计 渲染用连续坐标 x*S-18(平滑不跳),碰撞用格级检测(不穿墙),二者解耦。 3. 精灵大于格子时,单点检测必然不对称
官方
1 赞