# 二七王 · 前端 UI 资源与精灵清单 > **本文用途**:把二七王的完整游戏流程翻译成「前端需要哪些图片资源、声音、精灵、图层、群组、坐标」的**可执行规格书**,供美术出图与 gameabc 编辑器建资源使用。 > > **本文不是代码**。文中给出的 ID 是**规划建议值**(相应号段已核实为空闲,见 §0.3),用途是指导在编辑器里按此建立;**代码里引用的 ID 必须与编辑器中实际建成的完全一致**,若编辑器实际取值与本文不同,**以编辑器为准并回头修订本文**。前端红线明令「绝不编造 ID」,指的就是代码侧。 > > **权威源**:玩法规则以 `server/games/erqiwang/docs/design/design.md` 为准;下发字段以 `server/games/erqiwang/docs/protocol/packet_protocol.md` 为准;前端规范以 `docs/client/development-guide/` 为准。本文只做「规则/协议 → 界面资源」的映射,**不重新定义规则**。 > > **参考图**:`./uiref/*.png`,共 16 张,设计分辨率 1280×720。 --- ## 目录 - [0. 总则](#0-总则) - [0.0 术语(本项目修订版)](#00-术语本项目修订版优先于-designmd) - [0.1 设计基准与座位映射](#01-设计基准与座位映射) - [0.2 平台已提供、子游戏不重建的部分](#02-平台已提供子游戏不重建的部分) - [0.3 ID 号段规划](#03-id-号段规划) - [0.4 牌面资源与 cardId → 帧号映射(唯一权威转换)](#04-牌面资源与-cardid--帧号映射唯一权威转换) - [0.5 标注约定](#05-标注约定) - [1. 按阶段的界面清单](#1-按阶段的界面清单) - [2. 常驻 UI](#2-常驻-ui) - [3. 图片资源总表](#3-图片资源总表) - [4. 声音资源总表](#4-声音资源总表) - [5. 精灵结构表](#5-精灵结构表) - [6. 布局配置规范与配置清单](#6-布局配置规范与配置清单) - [6.0 与框架 AlignmentUtils 的对齐](#60-与框架-alignmentutils-的对齐) - [6.1 五种布局器(kind)](#61-五种布局器kind) - [6.2 座位变体 bySeat](#62-座位变体-byseat) - [6.3 文字样式 / 6.4 牌尺寸档](#63-文字样式-textstyle) - [6.5–6.9 各区配置清单](#65-常驻区配置) - [6.10 配置文件组织](#610-配置文件组织) - [6.11 与平台布局配置的衔接](#611-与平台布局配置的衔接) - [7. 待补充清单](#7-待补充清单) - [7.5 服务端待补项](#75-服务端待补项需改服务端才能实现的前端需求) --- ## 0. 总则 ### 0.0 术语(本项目修订版,优先于 design.md) **2026-08-25 修订**:原 design.md 里「暗牌 / 底牌」的命名与界面按钮倒挂,现统一为: | 术语 | 指哪 8 张 | design.md 原称 | 当前协议字段 | | --- | --- | --- | --- | | **底牌** | 发牌时**没有发给玩家**、留在桌面的 8 张 | ~~暗牌~~ | **`bottomcards`** | | **埋牌底牌** | 庄家埋牌时从手里扣下的 8 张 | ~~底牌~~ | **`burycards`**(结算包为 `bottom.cards`) | 其余术语(埋牌、扣底、甩牌、毙牌、垫牌、捡分、子、奖…)沿用 design.md §1 不变。注意「**扣底**」仍指 design §6.3 那条规则——闲家用主牌赢下最后一轮后翻开**埋牌底牌**计分;底栏那个 `扣底` 按钮是「查看埋牌底牌」的入口,两者同名但不同义。 > ✅ `design.md` 与 `packet_protocol.md` **已同步此变更**(S-5 完成),协议字段名也已拆分为 `bottomcards` / `burycards`(S-4 完成)。 #### 算奖 = 冲关 + 傍王 | 术语 | 范围 | 界面 | 字段 | | --- | --- | --- | --- | | **冲关** | design §8.1,**只计庄家**(旧称「常规算奖」) | 小局结算「**冲关分**」、「**冲关牌型**」按钮 | `chongguan`、`cards` | | **傍王** | design §8.3,可选规则,勾选后**庄闲都算**,每张王 1 奖 | 大局结算「傍王分」 | `wang`、`bangwang` | | **算奖** | **上位概念** = 冲关 + 傍王 | —— | `naward`(总奖数 N)、`grade_aw`(算奖得分) | 说「**冲关**」指庄家那一份,说「**算奖**」指两者合计。 > 小局结算的「冲关分」取 **`grade_cg`**(S-6 已把算奖得分拆成冲关 / 傍王两个分量),开傍王时也不会把傍王的贡献算进来。 #### 四类「公开信息」——名字像、含义差很远 | 术语 | 谁公开给谁 | 给的是什么 | 触发 | 界面位置 | 字段 | | --- | --- | --- | --- | --- | --- | | **亮牌** | 庄家 → 两个闲家 | **具体牌面**(庄家全部固定主牌) | 庄家埋牌后固定主牌达门槛(design §8.2) | 遮罩面板,同明牌版式(§1.6) | `liangpai.cards` | | **余主公示** | 全体 → 全体 | **只有数量**(剩余主牌数、主对数) | 任一玩家报无主(design §9) | 他家头像下 `主N` `对N` 角标(§2.5) | `seatlist[seat][4]`、`baozhu` | | **明牌** | 他家 → 请求者 | **具体牌面** | 可查牌 + **场上有人报无主**,三家均可点、随时可查(design §9) | 遮罩面板(§1.7 D-6) | `mingpai.others[].zhucards` | | **开底** | 桌面 → 所有玩家 | **具体牌面**(8 张底牌) | 70 分坐庄,**摸底之前** 3 秒(design §4) | 复用底牌区翻牌(§1.4 D-2) | `ancard3s` + `bottomcards` | 一句话记:**只有「余主公示」是统计类(给数字),「亮牌」「明牌」「开底」都给具体牌面**——区别在给谁的牌:亮牌给庄家自己的、明牌给他家的、开底给桌上那 8 张。 另有一组「底」字族专管发牌留桌的那 8 张牌: | 术语 | 含义 | 前端对应 | | --- | --- | --- | | **摸底** | 庄家坐定后把 8 张底牌摸起查看并入手牌 | 底牌区翻正面(非 70 分只有庄家看得到) | | **开底** | 70 分坐庄,摸底**之前**向全场翻开 3 秒 | 同上,但闲家侧也翻开 | | **查底牌** | 对局中随时回看这 8 张 | 底栏 `底牌` 按钮(§2.6) | | **扣底** | 末轮闲家用主牌赢下,翻开**埋牌底牌**计分 | 底栏 `扣底` 按钮看的是埋牌底牌 | > **「亮主」不是术语**——它是**选主界面的标题文案**(庄家选主牌花色那一步,见 `亮主.png`),与上表四项都无关。文档与代码里不要拿它指代任何规则。 > > 另:**自己**的主牌统计条(手牌上方 `x对 x主`,§2.5)**不属于上表任何一项**——那是本地统计、始终显示,不涉及"公开"。它在代码里叫 `ZHU_STAT_*`(群组 206);**2026-08-27 前叫 `LIANGPAI_*`,因与上表第一项「亮牌」同名异实而改名**,见 §0.3 群组表下的修订说明。 --- ### 0.1 设计基准与座位映射 | 项 | 值 | 来源 | | --- | --- | --- | | 设计分辨率 | **1280 × 720** | `client/output/gameabc_Project.json` `ScreenWidth/ScreenHeight`;11 张牌桌参考图均为 1280×720;叫分按钮面板与建房参考图为局部截图 | | 适配模式 | `ScreenFitMode: 1` | 同上 | | 帧率 | 30 fps | 同上 | | 玩家数 | **3 人** | design §2(三家各摸 28 张) | **座位映射**:服务端座位是绝对序号 `0/1/2`;前端一律把**自己**放在底部,另两家按 design §4 的**逆时针**顺序分居左上、右上。 ``` 右上 = (mySeat + 1) % 3 左上 = (mySeat + 2) % 3 ┌───────────┐ [左上玩家] │ 桌面中央 │ [右上玩家] └───────────┘ [ 自己 · 底部 ] ``` **方向已定论**(`server/games/erqiwang/class.paiju.js:264`): ```js get_nextseat: function(seat){ return (seat + 1) % 3; } ``` 即**下家 = `(seat + 1) % 3`**。逆时针出牌在屏幕视觉上「下家在右手边」(时钟 6 点逆时针走向 3 点),与中式牌桌「下家 = 右手边」的惯例一致。因此: | 显示位 | 座位 | | --- | --- | | 底部(自己) | `mySeat` | | **右上(下家)** | `(mySeat + 1) % 3` | | **左上(上家)** | `(mySeat + 2) % 3` | 本文各表均按此书写。轮庄「顺延到其下家」(design §4.6)走的也是同一个 `get_nextseat`。 ### 0.2 平台已提供、子游戏不重建的部分 参考图里有若干元素来自平台框架,**美术不必重复出图、编辑器不必重复建精灵**: | 元素 | 平台提供方式 | 精灵/资源 | 子游戏要做的 | | --- | --- | --- | --- | | 头像框 / 头像 / 昵称 / 分数 | `02_SubGame_Input.js` 转发壳 `updatePlayerInfoUI` 的 D 类默认渲染 | 底框 `346+ind`、头像框 `376+ind`、昵称 `406+ind`、分数 `436+ind`、头像图 `116+ind`、群组 `43+ind` | 只在 `01_SubGame_modify.js` 配置区调 `Game_Modify.PLAYER_INFO_LAYOUT` 的坐标(三人局用 ind0 南 / ind1 东 / ind3 西) | | 桌面聊天气泡 / 语音气泡 | 同上,`ShowChat` / `gameui_play_voice` / `gameui_stop_voice` | 聊天 背景 `552+ind` 文字 `582+ind`;语音 背景 `612+ind` 内容 `642+ind` | 只调 `Game_Modify.BUBBLE_LAYOUT` 坐标 | | 右侧语音圆钮 / 聊天圆钮 | 平台外壳 | 参考图右侧 (1210,310)/(1210,385) 两枚圆钮 | 无(仅坐标核对) | | Loading / Message / Confirm 全局弹窗 | 框架 `UIManager` 全局 UI | 由子游戏在 `UIManager.init` 注入精灵常量 | 若要用,需**另建**这三套弹窗精灵并注入;不用则跳过 | | 战绩列表 | 框架 `RecordView` 组件 + 平台入口精灵 `13` | `gameabc-framework/ui/RecordView.js` | 提供 `RecordView` 配置;战绩数据结构见协议「gameinfo1 / gameinfo2」 | | 帮助界面 | 平台 `GameUI.OpenHelp()`,入口精灵 `150` | —— | 提供二七王玩法说明文案 | | 创建房间界面**骨架** | 平台界面 **Layer 27 `CreateRoom_Layer`** + 约定精灵 `25`(确认/提交按钮) | 见 `01_SubGame_modify.js` 顶部注释 | **规则选项区要子游戏自建**(§1.1):渲染选项、拼 `roomtype` 串、注册 25 号点击后 `Net.Send_create_room` | | **解散申请 / 投票 / 同意拒绝** | **平台全包**:`Layer 420 CheckFree_Layer` + `GameUI.openCheck/closeCheck` + `Desk.self_apply_free_room` / `other_apply_free_room` / `self_agree_free_room` / `self_refuse_free_room` / `agreefree` / `free_room` | —— | **无**。子游戏只需处理解散**结算包**(§1.10),投票界面不用做 | | 高级选项 / 代开房 | `Layer 28 SeniorOptions_Layer` + `GameUI.openSeniorOptions` | —— | 无。它管短号 / 抽水 / 房间列表,**与玩法规则选项无关**,二七王的规则选项不走这里 | **平台已占用的图层一览**(读 `gameabc_Layer.json`,凡此列出者子游戏都不必重建): | 图层 | 名称 | 用途 | | --- | --- | --- | | 27 / 28 | `CreateRoom_Layer` / `SeniorOptions_Layer` | 创建房间 / 高级选项 | | 50 | `MainScene_Layer` | 牌桌主场景 | | 202 / 416 | `Player_Head_Score_Layer` / `MainScene_PlayerInfo_Layer` | 玩家头像与分数 | | 409 / 410 / 418 | `Chat_Voice_Layer` / `Chat_Text_Layer` / `ChatPannel_Layer` | 聊天与语音 | | 420 | `CheckFree_Layer` | **解散投票** | | 608 / 609 | `Help_Layer` / `Setting_Layer` | 帮助 / 设置 | | 612 | `Tips_Layer` | 通用提示 | | 613 | `updateRoomCard_Layer` | 房卡更新 | | 614 / 615 | `Loading_Layer` / `Reconnect_Layer` | 加载 / 断线重连 | | 616 / 619 | `Kick_Layer` / `BackHall_Layer` | 踢人 / 返回大厅 | | 620 | `recordLayer` | 战绩 | > 这些都落在框架保留段(1–100 / 201–300 / 401–500 / 601–700),与子游戏段 101–200 / 301–400 不冲突。 > ⚠️ 参考图底栏左侧的「头像 + 玩家昵称 + 10 + 庄」一行里,**头像/昵称/分数是平台的**,只有 **`庄` 印章**是二七王专属、需子游戏新建。左上/右上玩家位同理:头像框是平台的,**`庄` 印章、`主N`、`对N` 角标、状态文字**是子游戏的。 ### 0.3 ID 号段规划 **占用现状核实**(读 `client/output/gameabc_*.json`,日期 2026-08-24): | 类别 | 已占用 | 子游戏合法段 | 段内空闲情况 | | --- | --- | --- | --- | | 精灵 ObjectID | 1–3292 中 991 个 | **1001–3000** | 段内**仅 `3000` 被占**(`Spirit3000`,Layer 602 `inputPannel`,群组 87)→ 实际可用 **1001–2999** | | 图层 LayerID | 1–29, 50, 202, 402–420, 601–620 | 101–200 / 301–400 / 501–600 / 701+ | **全部空闲** | | 群组 GroupID | 0–79 | ≥ 201 | **全部空闲** | | 图片资源 ID | 1–439 | ≥ 501 | **全部空闲** | | 声音资源 ID | 1–5 | ≥ 101 | **全部空闲** | **二七王的分段规划**: #### 图层(Layer) | 图层 | 名称 | 用途 | | --- | --- | --- | | **101** | `EQW_Table_Static` | 牌桌常驻:顶部信息条、玩家位附加标记、**主牌统计条**、底栏(局数 + 功能钮) | | **102** | `EQW_Hand` | 自己手牌区(单排 / 埋牌双排) | | **103** | `EQW_TableCards` | 桌面牌:底牌区、三家已出牌区、埋牌底牌展示区 | | **104** | `EQW_Action` | 阶段操作区:叫分面板、选主/投降面板、埋牌/出牌操作条、倒计时 | | **105** | `EQW_Overlay` | 桌面浮层:提示气泡、报无主、甩错、捡分飘字、70 分底牌亮 3 秒 | | **302** | `EQW_Popup_AsetResult` | 小局结算 | | **303** | `EQW_Popup_Account` | 大局总结算 / 解散结算 | | **304** | `EQW_Popup_History` | 出牌历史面板 | | **305** | `EQW_Popup_MingPai` | 明牌面板 | | **306** | `EQW_Popup_Disband` | 解散投票面板 —— **T-2:是否由平台提供待确认** | > 建房规则选项区(§1.1)的精灵挂在**平台创建房间界面所在的图层**上,不新开图层;具体图层号需在编辑器里查证后回填(T-3)。 #### 群组(Group) | 群组 | 用途 | | --- | --- | | 201 | 顶部信息条(主 / 叫分 / 抓分) | | 202 / 203 / 204 | 左上家 / 右上家 / 自己 的附加标记(庄印章、主N对N、状态文字) | | 205 | 底栏功能按钮组(明牌 / 已出牌 / 上一轮 / 扣底 / 底牌) | | 206 | **主牌统计条**(自己的 `x对 x主`,§2.5)—— 常量名 `ZHU_STAT_BAR` | | 210 | 自己手牌 | | 211 | 底牌区(发牌留桌 8 张) | | 212 / 213 / 214 | 自己 / 左上家 / 右上家 的已出牌区 | | 215 | 埋牌底牌展示区(结算时 8 张) | | 220 | 叫分面板 | | 221 | 选主 / 投降面板 | | 222 | 埋牌操作条 | | 223 | 出牌操作条 | | 224 | 倒计时(三位置) | | 230 | 桌面浮层(气泡 / 飘字 / 报无主 / 甩错) | | 240 | 小局结算 | | 241 | 大局总结算 | | 242 | 出牌历史 | | 243 | 明牌面板 | | 244 | 冲关牌型叠加层(§5.6a) | | 250 | 建房规则选项 | > **2026-08-27 修订 · 群组 206 改名**:原名「亮牌信息条 / `LIANGPAI_BAR`」,实为 §2.5 的**自己的主牌统计条**(手牌上方 `x对 x主`,本地统计、有手牌时常显),与 design §8.2 的「**亮牌**」(庄家埋牌后向两个闲家亮出自己全部固定主牌的**具体牌面**,走遮罩面板、群组 242)**同名异实**——§0.0 已专门花一节区分「亮牌 / 余主公示 / 明牌 / 开底」四类公开信息,名字撞车极易把渲染写到错误的界面上。 > 故统一改名为**主牌统计条**:群组 `ZHU_STAT_BAR`、精灵 `ZHU_STAT_BG` / `ZHU_STAT_TEXT`、布局节点 `ZHU_STAT_BG`、图片资源 `BAR_ZHU_STAT`(605,ID 不变)。 > 「亮牌」一词自此**只指** design §8.2 的那件事(协议字段 `liangpai`、群组 242、`EQW_Anim.LIANGPAI_AUTO_CLOSE`),不再指本条。 #### 精灵(Sprite,1001–2999) | 号段 | 用途 | 预估用量 | | --- | --- | --- | | 1001–1099 | 顶部信息条 + 主牌统计条 + 底栏(局数、功能钮) | ~30 | | 1100–1199 | 三家玩家位附加标记 + 三处倒计时 | ~24 | | 1200–1249 | 自己手牌 36 张 + 牌上标记 | ~45 | | 1250–1299 | 底牌 8 张 + 埋牌底牌 8 张 | 16 | | 1300–1399 | 自己已出牌区(28 张 + 牌型标签) | ~30 | | 1400–1499 | 左上家已出牌区(28 张 + 牌型标签) | ~30 | | 1500–1599 | 右上家已出牌区(28 张 + 牌型标签) | ~30 | | 1600–1649 | 叫分面板 | ~46 | | 1650–1699 | 选主 / 投降面板 | ~21 | | 1700–1749 | 埋牌 / 出牌操作条 | ~8 | | 1750–1799 | 桌面浮层(气泡 / 飘字 / 报无主 / 甩错) | ~15 | | 1800–1819 | 小局结算面板 | ~20 | | 1820–1829 | 冲关牌型叠加层(**精灵复制**:遮罩 1 + 容器 3 + 模板 1 + 奖数 3) | **8** | | 1900–1949 | 大局总结算 / 解散结算 | 待设计 | | 1950–1999 | 出牌历史面板 | 待设计 | | 2000–2049 | 明牌面板 | 待设计 | | 2050–2099 | 建房规则选项(3 类别标题 + 8 选项 × 2 + 房卡附注) | 20 | | 2100–2999 | **预留** | —— | #### 图片资源(Image,≥501) | 号段 | 用途 | | --- | --- | | 501–510 | 牌面(3 套尺寸,见 §0.4) | | 511–530 | 花色图标(小 / 大两种尺寸) | | 531–570 | 按钮类 | | 571–600 | 标记类(庄印章、角标底、牌型标签、选中标记等) | | 601–630 | 面板底图 / 光晕 | | 631–650 | 数字与符号图 | | 651–680 | 提示气泡 / 报无主 / 甩错 | | 681–700 | 建房选项控件 | | 701+ | 预留 | #### 声音(Voice,≥101) 见 §4,整节标【待确认】。 ### 0.4 牌面资源与 cardId → 帧号映射(唯一权威转换) #### 帧排列(通用规范,跨游戏统一) 牌面资源统一按 **10 列 × 6 行 = 60 帧**排版,行优先编号: | 帧 | 内容 | | --- | --- | | 1–13 | **黑桃** A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K | | 14–26 | **红桃** A – K | | 27–39 | **梅花** A – K | | 40–52 | **方块** A – K | | 53 | 小王 | | 54 | 大王 | | 55–60 | **牌背 1 – 6**(预留多套牌背,二七王使用帧 **55**) | 二七王规则去掉了 3 和 4(design §2),因此**不引用**帧 `3, 4, 16, 17, 29, 30, 42, 43`;但资源仍按 60 帧通用排版**照常出图**,不留空、不重排——这套排版是所有牌类游戏共用的。 #### cardId → 帧号(前端只此一处实现) 服务端牌 id 的编码(`server/games/erqiwang/class.paiju.js:57`): ``` id = (deck - 1) * 54 + (flower - 1) * 13 + (number - 1) // deck 1..2, flower 1..4, number 1..13(1=A) 小王 id = (deck - 1) * 54 + 52 大王 id = (deck - 1) * 54 + 53 ``` 服务端 `flower` 编号是 **1 = 方块、2 = 梅花、3 = 红心、4 = 黑桃**(`class.pai.js:16`、协议 §6),因此 id 的花色段序为 **方块 → 梅花 → 红心 → 黑桃**,与美术帧的花色顺序(黑桃 → 红桃 → 梅花 → 方块)**正好相反**。点数在段内是一致的(都 A → K)。转换必须把花色段反过来数: ```js // 唯一权威转换:cardId → 牌面资源帧号。全前端只此一处实现,其余模块调用它。 function cardIdToFrame(cardId) { var n = cardId % 54; // 去掉副数,deck1 / deck2 共用同一帧 if (n === 52) { return 53; } // 小王 if (n === 53) { return 54; } // 大王 return (3 - Math.floor(n / 13)) * 13 + (n % 13) + 1; } var CARD_BACK_FRAME = 55; // 牌背(二七王固定用牌背 1) ``` **校验样例**(建资源后必须逐条对上): | cardId (deck1) | 牌 | `n = id % 54` | 期望帧 | | --- | --- | --- | --- | | 0 | 方块 A | 0 | **40** | | 4 | 方块 5 | 4 | **44** | | 12 | 方块 K | 12 | **52** | | 13 | 梅花 A | 13 | **27** | | 26 | 红桃 A | 26 | **14** | | 39 | 黑桃 A | 39 | **1** | | 51 | 黑桃 K | 51 | **13** | | 52 | 小王 | 52 | **53** | | 53 | 大王 | 53 | **54** | | 106 | 小王(deck2) | 52 | **53** | > deck1 与 deck2 的同一张牌**共用同一帧**(牌面完全相同)。区分两副牌靠 `cardId` 本身,不靠帧。 #### 三套尺寸 按 client 02 §2「不同显示区域尺寸不同,各自用一个独立资源 ID」的要求,开 3 套: | 资源 ID | 键名 | 用途 | 单帧尺寸 | 整图尺寸 (10×6) | | --- | --- | --- | --- | --- | | 501 | `CARD_FACE_L` | 自己手牌、底牌区 | **110 × 190** | 1100 × 1140 | | 502 | `CARD_FACE_M` | 三家已出牌区 | **90 × 155** | 900 × 930 | | 503 | `CARD_FACE_S` | 结算埋牌底牌区 | **50 × 70** | 500 × 420 | > 单帧尺寸为参考图实测估值(§6),出图前请与最终设计稿核对。三套的**帧排列完全一致**,只有尺寸不同,`cardIdToFrame` 对三套通用。 ### 0.5 标注约定 | 标记 | 含义 | | --- | --- | | 【图】 | 该界面/元素在 `./uiref/` 有参考图,规格按图写 | | 【待设计】 | 流程/协议要求存在,但**尚无设计稿**,本文只列出「必须有什么」,具体视觉待美术出稿后补 | | 【待确认】 | 规格本身尚未定论,需与规则设计者/美术确认,编号见 §7 | #### 怎么读参考图(重要) 参考图是**界面设计示意**,**只看版式、位置、样式,不看数值与状态组合**: | 图上的东西 | 是否可信 | | --- | --- | | 元素的**位置、尺寸、层级、配色、字体样式** | ✅ 按图做 | | 元素**长什么样**(按钮形态、角标形状、牌面排布方式) | ✅ 按图做 | | 具体**数值**(分数、张数、倍数、局数、ID…) | ❌ **一律是填充用的假数据**,不要据此反推规则或校验自洽性 | | **同时出现的互斥元素** | ❌ 是为了在一张图里把各种情况都标出来。典型:**多个玩家位同时挂庄标**——那是在告诉你「庄标在每个位置分别摆哪儿」,不是真会同时出现三个庄 | | 同一张图里**跨阶段并置**的状态 | ❌ 同上,属于叠画示意 | | 底栏右侧那排功能按钮 | ❌ 只是示意,**不代表该阶段的实际显隐**;显隐规则以 §2.6 为准 | > 所以:**发现图上数值算不通、或两个不该同时出现的东西同时出现,不要当成待确认项**——那是示意画法。真正需要确认的是「规则本身没定」的地方。 --- ## 1. 按阶段的界面清单 本节按 design §12.3 的阶段顺序走,每节说明**该阶段收哪些包、渲染什么、哪些 UI 显隐**。资源与精灵的完整表在 §3 / §5,本节只引用不重复。 ``` 建房(§1.1) → 发牌(§1.2) → 叫分坐庄(§1.3) → 看底牌·上庄(§1.4) → 选主/投降(§1.5) → 埋牌(§1.6) → 出牌对局(§1.7) → 末轮扣底 + 小局结算(§1.8) → 大局结算(§1.9) → 解散(§1.10) → 重连(§1.11) ``` ### 1.1 建房 / 房间设置 【图:创建房间选项的UI参考内容根据实际二七王游戏改动.png(**样式范式,非内容**)】 **平台提供界面骨架 + 确认按钮(精灵 25);子游戏提供规则选项区。** > ⚠️ 参考图是**另一个游戏**的建房选项(局数 8/16/24、人数 2/3/4、霸王 ×2/×4 等,二七王都没有)。 > 如文件名所示:**只照搬视觉范式与控件样式,内容一律按二七王的 `roomtype` 重写**。 #### 结构模型:类别标题 + 选项组 界面由**类别**纵向堆叠而成,每个类别是「类别标题 + 若干选项组」: ``` [类别标题] 选项组1 选项组2 … 牌局 ◉6局 ◯12局 ◉房主扣卡 ◯AA每人 【2张】 模式 ◉可查牌 ◯不查牌 规则 ▣傍王 ▣爬坡 ``` **选项组有三种类型**(这是通用控件规格,不只为二七王): | 类型 | 语义 | 用的选择框图 | | --- | --- | --- | | **单选** | 组内必选其一,互斥 | 单选(圆钮) | | **多选** | 组内各项独立开关,可全不选、可全选 | 复选(方框) | | **单选(可不选)** | 组内至多选一,也可一个都不选 | **复选(方框)**——因为它需要能表达「全不选」,视觉上不适合用单选圆钮 | **每个选项 = 选择框(图片精灵)+ 文字精灵**,两者成对出现。 **选择框图片资源为 2 × 2 = 4 帧**: | 帧 | 内容 | | --- | --- | | 1 | 单选 · 未选中 | | 2 | 单选 · 已选中 | | 3 | 复选 · 未选中 | | 4 | 复选 · 已选中 | 帧号计算:`frame = (useCheckbox ? 3 : 1) + (selected ? 1 : 0)`,其中 `useCheckbox = (类型 === 多选 || 类型 === 单选可不选)`。 **类别标题放在一张图上多帧**,一个标题一帧: | 帧 | 内容 | | --- | --- | | 1 | 牌局 | | 2 | 模式 | | 3 | 规则 | 其余视觉细节照搬参考图:标题青绿粗体、选中项文字变橙、灰色小字附注(参考图的 `【3张/人】`,二七王用来显示房卡消耗)、选项多时可折行。 #### 内容(二七王实际,对应 `roomtype` 5 位,协议 §0.5) | 类别(帧) | 选项组 | 类型 | 选项 | `roomtype` 位 | design | | --- | --- | --- | --- | --- | --- | | **牌局**(帧1) | 局数 | 单选 | **6 局** / 12 局 | 位 0 | §10.1 | | **牌局**(帧1) | 扣卡 | 单选 | **房主扣卡** / AA 每人扣卡 + 附注房卡数 | 位 1 | §10.1 | | **模式**(帧2) | 查牌 | 单选 | **可查牌** / 不查牌 | 位 4 | §9 / §10.2 | | **规则**(帧3) | 附加规则 | **多选** | 傍王、爬坡(可同时勾选,互不冲突) | 位 2、位 3 | §8.3 / §7.3 / §10.3 | (加粗为缺省值,即 `roomtype = "00000"`。三个类别正好对应 design §10.1 / §10.2 / §10.3 三个小节。) **二七王当前用不到「单选(可不选)」**——四个选项组里三个是单选、一个是多选。该类型仍须在控件层实现,供后续玩法与模板克隆使用。 **没有「人数」行**——二七王固定 3 人(design §2),不给玩家选。参考图里的「人数」「霸王」两行**不要照抄**。 **房卡消耗联动**(协议 §0.5,随「局数 + 扣卡」两行实时变化): | 扣卡方式 | 6 局 | 12 局 | | --- | --- | --- | | 房主扣卡 | 房主 2 张 | 房主 4 张 | | AA 每人扣卡 | 每人 1 张 | 每人 2 张 | #### 职责边界(D-1 已定) **子游戏只管「类别标签 + 选项」这一块内容区**。弹窗背景、标题、关闭按钮、确认按钮**都由平台提供**——所以 §5.8 只列内容区的精灵,不要重复建弹窗外壳。 #### 实现要点 - 交互:点击平台约定的 25 号精灵 → 拼 `roomtype` 串 → `Net.Send_create_room({agentid, playerid, gameid, roomtype})`。 - 精灵:群组 250,号段 2050–2099,**预置 20 个**(见 §5.8);挂在平台创建房间界面所在图层(T-3)。 - 资源:选择框(4 帧)、类别标题(3 帧)。 - **整个界面由一份「类别 → 选项组 → 选项」的配置数据驱动渲染**,不为每个选项写死代码。加一个选项 = 配置里加一条,不改渲染逻辑(工程总则 §5 OCP、§6 配置优先)。 - **`roomtype` 的拼串必须只在一处实现**——与服务端 `class.config.js` `parse()` 对称的 SSOT 要求,不得在多处按下标硬拼。每个选项在配置里自带 `bit` 与 `value`,拼串就是遍历配置,无需按下标硬编码。 - 房卡数**不要硬编码在 UI 代码里**,做成「局数 × 扣卡方式 → 房卡数」的配置表(工程总则 §6)。 配置数据形态(示意,落地位置见 §6.10): ```js EQW_RoomOptions = { categories: [ { titleFrame: 1, /* 牌局 */ groups: [ { key: 'aset', type: 'radio', bit: 0, items: [ {label:'6局', value:'0'}, {label:'12局', value:'1'} ] }, { key: 'card', type: 'radio', bit: 1, note: 'cardCost', items: [ {label:'房主扣卡', value:'0'}, {label:'AA每人扣卡', value:'1'} ] } ]}, { titleFrame: 2, /* 模式 */ groups: [ { key: 'peek', type: 'radio', bit: 4, items: [ {label:'可查牌', value:'0'}, {label:'不查牌', value:'1'} ] } ]}, { titleFrame: 3, /* 规则 */ groups: [ { key: 'rules', type: 'checkbox', items: [ {label:'傍王', bit:2}, {label:'爬坡', bit:3} ] } // 多选:bit 落在每一项上 ]} ] }; ``` > `type` 取 `'radio'` / `'checkbox'` / `'radioOptional'`(单选可不选)。选择框帧号由 `type` 推出,见上文公式——**渲染代码不认识「傍王」「爬坡」这些具体玩法**,只认识三种控件类型,这样框架侧保持游戏中立、子游戏只提供配置。 ### 1.2 发牌 `fapai` 【图:等待叫分.png】 **收包字段**:`asetidx`(当前局数)、`asetcount`(总局数)、`cards`(自己 28 张)、`seat`(当前等待叫分者)、`countdown`。 **渲染**: | 要做的事 | 涉及 UI | | --- | --- | | 底栏局数显示 `1/4 局` | §2.6 底栏 · 局数文字 | | 自己手牌 28 张,按主牌顺序排序后铺开 | §2.7 手牌区(群组 210) | | 桌面中央 8 张底牌**背面** | 底牌区(群组 211,帧 = 牌背 55) | | 顶部信息条清零:主 = 无、叫分 = 0、抓分 = 0 | §2.1 顶部信息条 | | 三家玩家位状态文字清空,`主N/对N` 角标隐藏 | §2.2 玩家位附加标记 | | 倒计时显示在 `seat` 对应的位置 | §2.4 倒计时 | - **发牌动画(需要做)**:**从右往左铺开**。须遵守 client 02 §3「数据优先、表现延后」——先 `setHandCards()` 把 28 张写进 `this.data`,再播铺开动画;动画的开始/结束/出错回调里**只刷界面、不写核心数据**。动画缺失或卡住时,数据与界面仍须正确。动画时长、每张延迟进配置(§6.7)。 - **排序**:手牌排序规则(主牌在左还是在右、副牌花色顺序)由前端自行决定,**但选主前后主牌集合会变**,选主后必须重排。见 §7 T-5。 ### 1.3 叫分坐庄 `jiaofen` 【图:等待叫分.png / 自己叫分.png / 自己已经叫分轮到下家叫分.png】 **收包字段**:`seat`(叫分者)、`call`(0 = 不叫)、`currcall`(当前叫到的分)、`multiple`(该叫分对应基础子数)、`nextseat`、`countdown`。 **三种界面状态**: | 状态 | 参考图 | 表现 | | --- | --- | --- | | 等待他家叫分 | `等待叫分.png` | 中央保持 8 张底牌背面;倒计时数字显示在**当前叫分者**的位置旁;已叫分的玩家位显示金色 `60分` 大字,未叫的为空 | | 轮到自己叫分 | `自己叫分.png` | 中央弹出**叫分面板**(14 档按钮,两行 × 7)+ `不叫` 按钮;顶部一条提示 `70分坐庄才可投降`;倒计时移到自己操作区 | | 自己已叫、轮到下家 | `自己已经叫分轮到下家叫分.png` | 叫分面板收起,中央显示自己刚叫的 `50分` 金色大字;已表态的玩家位显示 `不叫` 白色大字 | **叫分面板规格**(群组 220,号段 1600–1649): - 14 档:`70 65 60 55 50 45 40 35 30 25 20 15 10 5`,两行 × 7(行1 = 70…40,行2 = 35…5)。 - 每档 3 个精灵:按钮底(**含角标底,同一张图**)+ 分数数字精灵 + 角标子数文字精灵。 - 角标显示该档的 `multiple`,**随房间「爬坡」开关取值**(协议 §3): - 常规算子:65→2、60→3、55→4、**50 及以下统一 →6**、70→2 - 爬坡:65→2、60→3、55→4、50→6、45→7、40→8、35→9、30→10、25→11、20→12、15→13、10→14、5→15、70→2 **按钮配色:按档位位置固定,不随状态或数据变** 【图:叫分按钮面板.png】 | 位置 | 档位 | 按钮底 | 角标底 | | --- | --- | --- | --- | | 1–4 | 70 / 65 / 60 / 55 | 浅黄 | 蓝 | | 5–7 | 50 / 45 / 40 | 金黄 | 绿 | | 8–14 | 35 / 30 / 25 / 20 / 15 / 10 / 5 | 橙 | 深橙 | > ⚠️ 参考图里角标写的 `1子`/`2子`/`4子` 是**填充假数据**(§0.5),恰好与三组配色一一对应,**不要据此以为「颜色随子数变」**——颜色按档位位置写死,角标数值才随爬坡开关变。叫分越低(承诺越苛刻、子数越高)颜色越热。 > > 反证:常规算子下 50/45/40 与 35…5 的 `multiple` 同为 6 子,但参考图里它们是两种颜色。 **灰色 = 已经叫过的分数**(不可再叫)。**颜色只因「不可叫」变灰,不随子数档位变化。** **按钮底与右上角角标(不含文字)画在同一张图上**,即一个精灵一帧就同时包含按钮底色和角标底色。共 **4 帧**: | 帧 | 内容 | | --- | --- | | 1 | 浅黄按钮底 + 蓝角标底(高分档 70/65/60/55) | | 2 | 金黄按钮底 + 绿角标底(中分档 50/45/40) | | 3 | 橙按钮底 + 深橙角标底(低分档 35…5) | | 4 | **灰按钮底 + 灰角标底(已叫过 / 不可选)** | **两种数字,两种精灵**: | 元素 | 精灵类型 | 说明 | | --- | --- | --- | | **叫分的分数**(70/65/…/5) | **多帧图数字精灵**(资源 `NUM_CALL_SCORE`,`SpriteManager.setNumberImage`) | 带描边、需与图一致的美术字;**一个精灵显示整串**(两位数不拆精灵) | | **角标的子数**(`2子`/`7子`…) | **文字精灵**(`SpriteManager.setText`) | 随爬坡开关动态变,值域 2–15,用文字精灵最省 | 故每档 **3 个精灵**:按钮底(含角标底)+ 分数数字精灵 + 角标文字精灵。 > **2026-08-26 补充(档位分数的实现方式,务必照做)**:14 档里 **13 档是两位数**(70…10),而每档只有**一个**分数精灵——这是**对的**,不要拆成十位/个位两个精灵。 > 显示走平台既有接口 `SpriteManager.setNumberImage(spriteId, '70', charWidth)`:整串文本交给一个多帧图片精灵,引擎按字符逐帧渲染,精灵宽度按**字符数 × `charWidth`** 自动调整。 > **不要用 `setFrame` 逐位切**——`setFrame` 是"整个精灵显示第 N 帧",一次只能显示一位,用它做多位数必然要拆精灵。 > 资源 `NUM_CALL_SCORE` 须按 §3.6 的 **16 帧固定帧序**出图;`charWidth` 取 `EQW_Layout.NUM_STYLE.CALL_SCORE.charWidth`(§6.8,业务代码不写裸值)。 - 可选性规则(design §4.2,**由服务端权威 `currcall` 决定,前端只据其置灰、不自行推导规则**): - 暂定庄家首叫:5–70 全部可选,**无 `不叫`**; - 其后:只有**严格低于 `currcall`** 的档位可选,`不叫` 可选。 - 交互:点击某档 → 发 `jiaofen { seat, call }`;点 `不叫` → 发 `jiaofen { seat, call: 0 }`。 - **悲观 UI**:点击后**只发包**,面板的收起与状态文字的出现一律等 `jiaofen` 推送包到达再做(前端 05 §6)。唯一允许的乐观处理是按钮本身的防连点禁用。 **上方提示条** `70分坐庄才可投降`:静态文字条,仅在叫分面板展开时显示。 ### 1.4 看底牌 / 上庄 `shangzhuang` 【部分待设计】 **收包字段**:`seat`、`call`、`banker`、`grade`(庄家叫分)、`multiple`、`bottomcards`、`ancard3s`、`cards`、`countdown`、`touxiang`。 | 分支 | 条件 | 表现 | | --- | --- | --- | | 常规坐庄 | 无 `ancard3s` | 庄家收到 `bottomcards`(8 张)与 `cards`(36 张)→ 底牌翻正面短暂展示后并入手牌;闲家**收不到** `bottomcards`,底牌区直接收起 | | **70 分坐庄** | `ancard3s === 1` | **全场三家都收到 `bottomcards`** → 8 张底牌翻正面**向所有玩家亮 3 秒**(design §4/§7.1),3 秒后庄家摸入手牌、闲家侧收起 | - **开底(D-2 已定)**:**复用「庄家摸底时翻开底牌」那套 UI**(见 `叫分确定庄.png`——中央 8 张牌由背面翻正面),**不要倒计时**。 两种情形用的是**同一套界面**,唯一差别是**谁能看到**: | 情形 | 谁能看到这 3 秒 | | --- | --- | | 非 70 分坐庄(只有**摸底**) | **只有庄家自己**(闲家侧那 8 张保持背面 / 直接收起) | | **70 分坐庄**(**开底** → 摸底) | **全场三人都能看到** 3 秒 | 因此**不需要单独的「公示」面板**,只是同一套翻牌表现的可见范围不同——精灵复用 §5.3 群组 211(底牌区),无需新增。 【图:等待庄家选主.png】即闲家视角下的**开底**:中央 8 张底牌翻正面摊开,下方一条 `等待庄家选主` 状态提示条(见 §2.8)。 > 该图把「开底摊牌」与「等待庄家选主」两个状态画在了一起,属**叠画示意**(见 §0.5「怎么读参考图」)。实际先后仍按 design §4:**开底 3 秒 → 庄家摸底入手 → 选主**,摸底后底牌区即收起。 - 上庄后统一更新:顶部信息条 `叫分` 列 = `grade`、角标 = `multiple`;`banker` 对应玩家位显示 **`庄` 印章**(含底栏自己位)。 - 三处 `庄` 印章是三个独立精灵(左上 / 右上 / 底栏),按 `banker` 显隐其一。 - **重连不重放**:协议明确 70 分的 3 秒亮牌是上庄时的一次性事件,`deskinfo` 重连**不重放**——前端不得在重连时补播。 ### 1.5 选主 / 投降 `xuanzhu` · `touxiang` 【图:亮主.png】 **同一决策点、二选一、互斥**(design §4.4)。 **选主面板规格**(群组 221,号段 1650–1699): > **2026-08-26 修订(其一)**:美术侧实际出图方式与原规格不符——花色按钮改为**一张图 5 帧**(帧1–4=四花色按钮,每帧已含按钮底+花色图标+右上角蓝色角标底;帧5=投降按钮,已含按钮底+「投降」字),花色图标、角标底、投降文字不再单独出精灵。按此改写以下条目,代码同步见 `ImageResources.js`/`Sprites_Action.js`/`Layout_Action.js`。 > > **2026-08-26 修订(其二)**:张数数字用平台既有的 `SpriteManager.setNumberImage`(§3.6)——一个精灵就能显示整串 `26张`,故**每花色一个数字精灵**(`MAIN_SUIT_COUNT_1..4` = 1674–1677);当日早前定的「十位/个位各一个精灵」(8 个)**作废**,空出的 1678–1681 不复用。 - 标题斜角条:`亮主` 金色字 + 倒计时数字。 - **4 个花色按钮**:每个按钮由**一个精灵**呈现,帧 = 花色序号 1–4(服务端 flower 编号),该帧已含「按钮底 + 花色图标 + 右上角蓝色角标底」;角标底之上叠加一个**对数文字精灵**(`N对`)。 - 按钮下方叠加**张数数字精灵**:该花色在庄家手中的**总张数**——两副牌单花色最多 26 张(两位数),用 `SpriteManager.setNumberImage(spriteId, '26张', charWidth)` 在**一个**精灵上显示整串(无需拆十位/个位),资源见 §3.6 的 `NUM_MAIN_SUIT_COUNT`(第16帧=`张`)、`charWidth` 见 §6.8 的 `NUM_STYLE.MAIN_SUIT_COUNT` - 右上角标文字 = 该花色的**对子数**(design §4/§11 明确要求) - **张数与对子数都由前端据 `MyCards`/`cards` 自行统计**,协议 §「ChooseMain」明确「服务端不额外下发」。 - **投降按钮**:仅 `touxiang === 1`(即叫分 70)时显示,与 4 个花色按钮并列;按钮为**一个精灵**(`BTN_SUIT_CHOOSE` 帧5,已含底与「投降」字),不再单独出文字精灵。 - 交互:点花色 → `xuanzhu { seat, flower }`(flower:1方块 2梅花 3红心 4黑桃);点投降 → `touxiang { seat }`。 - **投降的后果**:直接跳到结算(§1.8),**不选主、不埋牌、不出牌**。 **收到 `xuanzhu` 推送后**(字段 `banker`、`flower`、`countdown`、`cards`): - 顶部信息条 `主` 列显示 `flower` 对应的花色图标; - **手牌必须按新的主牌集合重排**(正2/正7 升格为主牌,见 design §3); - 选主面板收起,进入埋牌。 ### 1.6 埋牌 `maipai` 【图:庄家埋牌.png】 **仅庄家**,闲家此阶段等待。 **界面变化**: - 手牌从**单排 36 张**改为**双排**(上排 17 + 下排 19,实测估值)。 - 操作条(群组 222):`提示` 按钮 + 倒计时 + **`埋牌 (N/8)`** 按钮,N 为已选张数,`N < 8` 时按钮置灰不可提交。 - 选中的牌**上浮**表示待埋。 - 牌面上的三种标记(资源 `MARK_CARD_GROUP` 3 帧): - **`拖` 红色圆标** = 该牌属于一组**拖拉机** - **橙色五角星** = **正 2 / 正 7**(选定花色的 2 和 7,design §3 里大小仅次于双王) - **蓝色五角星** = 除正2正7外的**其他主牌**(双王、副2、副7、主花色普通牌) 三种标记互斥、每张牌至多一个;由前端据 `flower`(主牌花色)在本地标注,**不需要服务端下发**。 **收到 `maipai` 推送后**(字段 `cards`、`bottomcards`、`seat`、`countdown`、`liangpai`): - 庄家手牌回到单排 28 张; - **闲家**若收到 `liangpai`(仅可查牌模式 + 庄家达标),弹出下方「亮牌」**遮罩面板**(群组 242,精灵 `HIST_*`,见 §5.7b / §6.9c)——**不是** §2.5 那条主牌统计条(原表述「渲染 §2.5 亮牌信息条」是同名异实造成的笔误,2026-08-27 订正); - 进入出牌,`seat` = 首出者(必为庄家)。 **亮牌**(design §8.2,协议 `liangpai`): 庄家埋牌后、出牌开始前,若手中**固定主牌**达门槛,就向两个闲家亮出**自己全部固定主牌的具体牌面**。 | 项 | 说明 | | --- | --- | | 触发门槛(任一) | 固定主牌总数 ≥10 / 王 ≥3 / 7 ≥6 / 2 ≥6 | | 固定主牌 | 双王 + 全部花色的 2 + 全部花色的 7;**不含**主花色普通牌 A/K/Q/J/10/9/8/6/5 | | 亮出内容 | `liangpai.cards`,已按主牌序排好。**固定的一份**,不随被哪条门槛触发而增减 | | 叫分限制 | **无**,任何叫分档位都适用 | | 查牌模式 | 仅可查牌模式下发;不查牌时闲家收不到该字段 | | 谁能看 | 只有两个闲家收到(庄家自己的牌自己本来就看得见) | **展示形式**:与**明牌**同一套版式(半透黑遮罩 + 一排牌,照 `冲关牌型显示.png`),只是这里只有一排(庄家的)。可与 §5.7b 的明牌面板复用同一套精灵与渲染,仅数据源不同。 > **三个遮罩面板的关闭方式各不相同,别写混**: > > | 面板 | 关闭方式 | 遮罩是否可点 | > | --- | --- | --- | > | **亮牌**(本节) | **仅自动时长**,到点自动收起 | ❌ 不注册点击 | > | **冲关牌型**(§1.8) | **点遮罩关闭** | ✅ 是关闭热区 | > | **明牌 / 出牌历史**(§1.7) | 点遮罩关闭;明牌另可再点按钮切换 | ✅ 是关闭热区 | **数量前端自己数**——协议不再下发 `zhu`/`zhupair`/`wang`/`qi`/`er` 等统计,需要显示「x 张王」之类就从 `cards` 里算。 **展示时机(T-34 已定)**:**埋牌后、出牌前**——庄家埋牌完成、服务端下发 `maipai` 包(其中带 `liangpai`)时弹出,出牌开始前收起。 - 与 design §8.2「庄家埋牌后……出牌开始前」的规则时点一致。 - 这是**一次性弹出**,不是常驻入口。**关闭方式:只按自动时长关闭,不做点击关闭**——遮罩不注册点击事件。时长进配置(`liangpaiAutoClose`,默认值待定)。 - 只有**闲家**会收到 `liangpai` 并弹出;庄家自己不弹(自己的牌自己看得见)。 - **不阻塞出牌**:面板关闭与否不影响服务端推进,出牌包照常到达;若面板还开着就收到了出牌推送,直接收起面板即可。 ### 1.7 出牌对局 `chupai1` / `chupai2` / `chupai3` 【图:出牌.png / 出牌2.png】 这是最复杂的阶段。 **出牌操作条**(群组 223):倒计时 + `出牌` 按钮(选中牌数为 0 时置灰)+ **`提示` 按钮(出牌阶段必须有)**。 **自动选中**:某些情况下(如跟牌时存在**必出牌**、或整手只有唯一合法出法),服务端会在下发包里带上**应当选中的牌**,前端收到后**自动把这些牌选中**(上浮),玩家确认后点 `出牌` 即可。 > ⚠️ **该字段目前协议里没有,需服务端补**——见 §7.5 **S-2**。好消息是服务端已经算过了:`class.arith.js:737` `get_followcard()` 返回 `{ mustcard, cancard, cantype }`,其中 `mustcard` 就是「必出的牌列表」,现在只用于内部校验(`class.arith.js:1055`)、未下发。补一个下发字段即可,无需新增算法。 > > 这条**不违反**「请求包只带意图」红线——方向是**服务端 → 前端**的建议选中,前端仍只回传 `cards`;且合法性最终仍由服务端校验。 **自己出牌**:点手牌选中(上浮)→ 点 `出牌` → 发 `chupai { seat, cards }`。 - `cards` 必须是**非空、元素为 0–107 整数、互不重复**的数组(协议 §0.2),否则服务端按 `PARAM` 拒绝。 - **只发意图,不发结论**:不回传牌型、不回传「我最大」之类判定。 - 前端可用 `shared/` 做本地预校验与提示,**结果不回传**。 **收 `chupai1`(首家出牌)**,字段最丰富: | 字段 | 渲染用途 | | --- | --- | | `seat` / `cards` | 在该家已出牌区摆牌(资源 `CARD_FACE_M`) | | `cardtype` | 牌型标签(`>100` 单张 / `>200` 对子 / `>300` 拖拉机) | | `shuai` | **甩牌**分量构成 `{tractors:[], pairs, singles}`——**甩牌的真实结构只能读它,不得据 `cardtype` 反推**(协议 §11 明确警告) | | `shuaicuo` | **甩错**标志(= 1):整套甩牌收回,本轮只强制打出最小一张(design §5.4.5)。表现见下方「甩错提示」 | | `flower` / `count` | 本轮首出花色与张数,供本地跟牌提示 | | `nextseat` / `countdown` | 倒计时移到下一家 | | `seatlist` | **仅可查牌模式**:三家牌况,用于 §2.2 的 `主N` / `对N` 角标 | | `baozhu` | 是否已有**任一**玩家**报无主**:= 1 时触发余主公示(各家主牌数/对数),并让**三家**的底栏 `明牌` 按钮可用 | | `cardsinhand` | **仅出牌者本人有**:出牌后剩余手牌,据此重画手牌区 | **收 `chupai2` / `chupai3`**:字段类似,`chupai3` 额外带 `maxseat`(本轮谁最大)与 `grade`(本轮闲家得分,仅闲家有得分时才有)。 **一轮结束的表现**: - `maxseat` 高亮,随后三家出牌区收牌; - 若有 `grade`,播捡分飘字(`+20分` 金色,参考图 `出牌2.png` 中央 `20分`)→ 同时更新顶部信息条 `抓分` 列累计值; - 下一轮由 `maxseat` 先出。 **牌型标签**(参考图 `出牌2.png` 左下角橙色 `拖拉机`):需要一套标签资源,至少含 `单张` / `对子` / `拖拉机` / `甩牌` / `毙` / `垫`【待确认 T-11:标签种类以美术稿为准;「毙/垫/混合出牌」是否也要标】。 **闲家提示 `tishi`** 【图:自己是闲家时,底下的踩有分没分按钮.png】(design §11): - 闲家在出牌阶段可向**对家**(另一闲家)发三种提示:`1 = 踩`(我能大过庄家)、`2 = 没分`、`3 = 有分`。 - 发送方**收不到成功回执**(协议 §13.7),只有失败才回包。 - 接收方(对家)收到 `tishi { seat, tip }` → 在发送者的玩家位弹出气泡。 - 庄家无对家,不提供此功能。 - **自己发的提示需要本地回显**:服务端只把提示转发给对家、不回执给发送者,所以**发送者自己的气泡由前端在点击时本地弹出**。 > 这是「悲观 UI」红线的一处**受控例外**,成立的理由是:提示**不含任何对局状态**,服务端明确「不校验提示真实性」(协议 §13.7、design §11),它既不改阶段、不改控制权、不改分数,纯属玩家间的主观喊话。因此本地回显不会造成与服务端状态不一致。仍须遵守:失败回包(`RULE`/`STEP`/`PARAM`)到达时要把本地回显撤掉。 **发起入口**:底栏三个按钮 `踩` `有分` `没分`,**位置就是庄标的位置**——自己是闲家时没有 `庄` 印章,这块区域正好空出来给这三个按钮。 | 自己的身份 | 底栏该区域显示 | | --- | --- | | 庄家 | `庄` 印章(1 个精灵) | | 闲家 | `踩` `有分` `没分`(3 个按钮) | 两者**互斥**,同一块区域二选一。注意按钮顺序是 **踩 → 有分 → 没分**,与协议 `tip` 编号(1踩 / 2没分 / 3有分)**不同**,映射不要按位置下标推。 **气泡资源**:`3 × 2 = 6 帧`。 | | 箭头向左 | 箭头向右 | | --- | --- | --- | | 踩 | 帧 1 | 帧 4 | | 没分 | 帧 2 | 帧 5 | | 有分 | 帧 3 | 帧 6 | **箭头朝向按头像相对气泡的位置选**:头像在气泡**左**侧 → 用**箭头向左**的帧;头像在气泡**右**侧 → 用**箭头向右**的帧。三个座位的取用见 §6.5。 **甩错提示(D-3 已定)** 【图:强甩失败.png】: - 收到 `shuaicuo === 1` 时,用**通用状态提示条**(§2.8)显示 **`强甩失败`**,取「即时反馈类」位置预设。 - **不做收回动画**——甩出的牌不演"飞回手里",直接按服务端下发的 `cards`(那张被强制打出的最小主牌单张)落牌即可。 - 提示条自动淡出,时长进配置。 **报无主 → 余主公示(D-4 已定)**: - **不做全场提示**。报无主本身没有独立的提示表现。 - 唯一的界面变化是**余主公示**生效:他家的 `主N` / `对N` 角标出现(服务端 `baozhu=1` 并整表刷新 `seatlist`,仅可查牌模式)。详见 §2.5。 - 自己的主牌统计条**本来就一直显示**,不因报无主而变化。 **明牌**(design §9.3): - **按钮显示条件(D-6 已定,按 design 手册)**:**可查牌模式 + 出牌阶段 + 已有任一玩家报无主**(`baozhu == 1`)→ **三家都显示** `明牌` 按钮,且**可随时查看**、不限次数。 > 判定的是「场上**有人**报无主」,不是「**自己**报无主」——报无主的那位自己也能看。与 design §9.3「一旦有玩家的主牌全部打空……为**全体三人**(含刚刚报无主的这位玩家自己)显示……同时界面上会出现一个『明牌』按钮」一致,也与服务端 `mingpai` 的受理条件 `have_baofu()` 一致。 - 点击 → 发 `mingpai { seat }` → 收 `mingpai { seat, others: [{seat, zhucards}] }` → 弹出面板展示另两家全部未出主牌的**具体牌面**。 - **再点一次取消是纯前端开关**,不再请求服务端(协议 §13.6)。 - **展示形式(D-6 已定)**:**照 `冲关牌型显示.png` 那一套**——半透黑遮罩 + 两家各一排牌(`CARD_FACE_L`、`fan` 重叠排列),点遮罩关闭。与冲关牌型叠加层是同一种版式,可复用同一套渲染逻辑,仅数据源与座位数不同(明牌是**另两家**,冲关牌型是**三家**)。 - 面板:图层 305,群组 243;牌**用精灵复制**(张数不定,同 §5.6a 的判据)。 **出牌历史**(design §9.1): - 仅可查牌模式;底栏 `出牌历史` / `上一轮` 两个按钮(§2.6)。 - 数据来自重连包 `pushlist`(按轮次 × 3 家),**出牌过程中前端需自行累积**(`chupai1/2/3` 逐包记录)。 - **展示形式(D-7 已定)**:**不按轮次分组**。把每家出过的牌全部**摊平聚合到该玩家名下**,再**按手牌那套主牌序排序**(同 §7.2 T-5 的 `order_cards` 顺序),三家各显示一排——版式**照 `冲关牌型显示.png`**(遮罩 + 三家各一排牌)。 > 前端自行摊平 + 排序即可,**不需要改服务端**:`pushlist` 已含全部轮次数据,且各轮内部已按本局主牌花色排好。 - `上一轮` 按钮则只取**最近一轮**的三家出牌(`pushlist` 末尾一项),版式同上。 - 面板:图层 304,群组 242;牌**用精灵复制**(张数不定)。 - **不可查牌模式下这两个按钮整体不出现**(design §9)。 **失败回包**(协议 §0.2):任何请求被拒都会回同名 rpc 的 `{ success: false, errcode }`。前端一律 `if (!data.success)` 判断,据 `errcode` 提示: | errcode | 含义 | 建议提示 | | --- | --- | --- | | 1 PLAYER | 玩家/房间/座位校验不过 | 「身份校验失败」 | | 2 NODESK | 牌桌或牌局不存在 | 「牌局不存在」 | | 3 STEP | 当前阶段不允许 | 「当前阶段不能这样操作」 | | 4 SEAT | 位置不符 / 还没轮到 | 「还没轮到你」 | | 5 PARAM | 参数非法 | 「出牌数据有误」 | | 6 RULE | 规则不允许 | 「这样出不符合规则」 | 复用框架 `UIManager.showMessage`,无需专门资源。 ### 1.8 末轮扣底 + 小局结算 `jiesuan` 【图:小局结算.png】 **结算包三种来源**(协议 §14):正常出牌结算(`chupai` + `bottom` + `aset`)、投降结算(只 `aset`)、解散结算(见 §1.10,投递方式完全不同)。 **扣底表现**(`bottom` 分组,design §6.3): - `cards`(8 张**埋牌底牌**)**恒有** → 埋牌底牌区(群组 215,资源 `CARD_FACE_S`)翻开展示,design §11 明确「牌局结束需要亮出底牌」; - `multiple` / `grade1` / `grade2` **仅闲家扣底时才有** → 展示 `底牌 20 × 4` 之类的翻倍算式。 - 文案格式:`叫分{aset.multiple}子 底牌 {bottom.grade1}x{bottom.multiple}`——**`x` 后面是扣底倍数**(design §6.3:单张 ×1、主对 ×2、N 连对 ×2N)。参考图 `底牌 20x0` 即底牌有 20 分、扣底倍数 0(本局闲家未扣底)。 - 闲家未扣底时 `bottom` 只有 `cards`,`multiple`/`grade1`/`grade2` 都不下发 → 倍数位显示 **0**。 **三家结算数字**(`aset.seatlist`,每家两行): | 行 | 内容 | 字段 | 样式 | | --- | --- | --- | --- | | 大字 | 本局总分 | `grade` | `+20` 橙色(赢)/ `-10` 蓝色(输),底带光晕 | | 小字左 | **`牌局分 N`** | `grade_jf`(捡分子数得分) | 白标签 + 数值 | | 小字右 | **`冲关分 N`** | **`grade_cg`**(算奖得分里的冲关分量,S-6 新增;开傍王时不含傍王那部分) | 白标签 + 数值 | > 两个标签的文案已由更新后的 `小局结算.png` 确定(原图两个都写「牌局分」是 mock 重复,现已修正为 `牌局分` / `冲关分`)。 **两个按钮**: | 按钮 | 位置 | 作用 | | --- | --- | --- | | `冲关牌型`(蓝色) | 中央偏左 | 打开冲关牌型叠加层(见下) | | `下一局`(金黄) | 中央偏右 | 发 `zhunbei { seat }` 进入下一局(§1.12) | #### 「冲关」是什么(`chongguan` 字段的准确定义) 服务端 `class.arith.js:1277` `get_chongguan(mainflower, cards)`。`chongguan` 即 design §8.1 **冲关**的奖数(该规则旧称「常规算奖」,现统一叫冲关)。函数返回三项: | 返回字段 | 含义 | 去向 | | --- | --- | --- | | `count` | 关数 = 冲关奖数 | `aset.seatlist[i].chongguan` | | `wang` | 手牌中的王数 | `aset.seatlist[i].wang`(傍王按此计奖) | | `cards` | **王牌 + 所有参与冲关的牌**,已按主牌序从大到小排好 | `aset.seatlist[i].cards` → **冲关牌型叠加层要显示的就是它** | 计数规则(与 design §8.1 逐条对应): 1. **前置**:`cards.length < 28` 直接返回 0 —— 手牌不足 28 张不参与冲关。 2. **三王 / 四王**:≥3 王 → `count = 1`;= 4 王 → `count = 3`。 3. **连对链续奖**:**必须先满足 ≥3 王**(整段逻辑在 `if (_wlist.length >= 3)` 内)。从小王编码 `9553` 起,沿主牌序往下逐对扫描,每遇到一个与上一节**位置相邻**的对子就 `count + 1`,链条为 `正7 → 副7 → 正2 → 副2 → 主A → 主K → …`,断则止。**没有三王就没有连对链奖**。 4. **六/七/八个 7**:`count += 张数 − 5`(6→1、7→2、8→3 奖)。 5. **六/七/八个 2**:同上。 6. **固定主牌 ≥10 张不产生冲关奖**——它只触发 design §8.2 的「亮牌」,代码里有明确注释。§1.6 的亮牌与这里的冲关是两回事。 > 前端**不重算冲关**,`chongguan` / `wang` / `naward` / `cards` 一律直接取服务端下发值(服务端权威,红线)。上面的规则只用于理解要显示什么。 **冲关牌型叠加层** 【图:冲关牌型显示.png】: 点 `冲关牌型` 后,在结算面板之上盖一层**半透黑遮罩**,三家各展示自己的 `aset.seatlist[i].cards`,资源用 `CARD_FACE_L`、重叠排列。`下一局` 按钮在遮罩层上**继续可见可点**,`冲关牌型` 按钮本身被遮住。 **关闭方式:整块全屏半透明黑遮罩本身就是关闭热区,点击遮罩即关闭。** 不另设关闭按钮。因此 `CG_MASK` 必须注册点击事件,且 `下一局` 的层级要高于它、点它不被遮罩吃掉。 - 三家牌位:左上家、右上家在各自头像内侧;自己在中央偏下。 - 布局配置见 §6.7。 **牌用精灵复制生成,不预置固定数量**(见 §5.6a):`cards` 的张数**没有小上限**——四王 + 一条长连对链 + 八个 7 + 八个 2 叠加时可达 20 余张。预置精灵既会不够用,又在绝大多数局里白占 ID。 **`aset` 其余字段的用途**: | 字段 | 用途 | | --- | --- | | `banker` / `call` / `flower` | 顶部信息条最终态 | | `multiple` | 基础子数 | | `grade` | 闲家最终捡分(含扣底)→ 顶部 `抓分` 列 | | `upgrade` | **判定倍率(带符号)**:`3` 大光 / `2` 小光 / `1` 过庄 / `-N` 升 N 级(倒庄)/ `-99` 投降 / `0` 解散。**结算面板不显示判定文字**——判定结果由**动画**呈现,见 §1.8「判定结果动画」 | | `bangwang` / `climb` | 本局规则开关,用于结算面板上标注 | | **`cards`** | **冲关牌型叠加层**的牌(见上) | | `chongguan` / `wang` / `naward` | 冲关奖数 / 王数 / **总奖数**。叠加层**需要并列显示奖数**——以 `naward`(该家总奖数 N,8.4 节的支付基数)为主;`chongguan` / `wang` 作为明细可选展示 | #### 判定结果动画(D-8) **结算面板上不做判定文字**。大光 / 小光 / 过庄 / 升N级 / 投降这几个判定,由**动画**在两个时机呈现: | 时机 | 数据来源 | 说明 | | --- | --- | --- | | **对局过程中** | `curmultiple`(随 `chupai1/2/3` 下发,S-1) | 捡分变化导致判定档位跳变时提示,例如从「大光」掉到「小光」、或闲家捡够分数「过庄→升级」 | | **结算前** | `aset.upgrade`(结算包,带符号) | 末轮打完、结算面板出现**之前**播一次最终判定 | **动画播放遵守「数据优先、表现延后」**(client 02 §3):先 `setXxx` 把结算数据写进 `this.data`,再播动画;动画的开始/结束/出错回调里**只刷界面、不写核心数据**。动画缺失或卡住时,结算面板与数据仍须正确。 > `curmultiple` 是**带符号**的(S-8 已改):`3/2/1` = 庄家大光/小光/过庄,`-N` = 闲家升 N 级,`0` = 叫分未定。判定动画按符号 + 绝对值两维映射即可,与结算包 `aset.upgrade` 同一套逻辑。 **投降结算**:只有 `aset`,无 `chupai`、无 `bottom` → 结算面板必须容忍**底牌区缺失**,不能因为读不到 `bottom.cards` 就崩或显示空框。 ### 1.9 大局总结算 `account` 【图:大局结算.png】 - 仅当打到最后一局(`idx >= asetcount`)时,`jiesuan` 包才带 `account` 分组。 - 界面:图层 303,群组 241。**三条玩家栏用精灵复制生成**(见 §5.7a)。 **版式**(弹窗面板,圆角): | 区 | 内容 | | --- | --- | | 标题栏(青绿底) | `二七王-3人 房号{roomcode} 共{asetcount}局 {开战时间}` + 右上角关闭钮 | | 玩家栏 ×3(按名次排序,行间分隔线) | 名次徽章 → 头像 → 昵称 / `ID:{playerid}`(两行)→ `基础分{n}` / `总得分{n}`(两行)→ `冲关分{n}` → `傍王分{n}` → 右侧大字总分 | | 规则栏(浅绿底) | `规则:可查牌、傍王、爬坡算子` —— 据 `roomtype` 位串拼出已启用项 | | 面板外底部 | `再来一局`(黄) `分享好友`(蓝) `复制战绩`(蓝) `退出房间`(红) | - **名次徽章**:3 帧(1 金 / 2 橙 / 3 蓝,花瓣形),按总分排名取帧。 - **总分大字**:正分橙色带 `+`、负分蓝色带 `-`,复用 §3.6 的 `NUM_RESULT_WIN` / `NUM_RESULT_LOSE`。 > **四项分数已由 S-6 补齐**:`account` 改为对象数组,含 `grade_jf_total`(基础分)/ `grade_cg_total`(冲关分)/ `grade_bw_total`(傍王分)/ `score`(总分),三项之和恒等于 `score`。 ### 1.10 解散结算 `free_room` 【待设计】 **投递方式与 §1.8 完全不同**(协议 §14.1),前端必须单独处理: ``` { app:"youle", route:"room", rpc:"free_room", data:{ seats:[], deskfree:{ rpc:"jiesuan", data:{ success, aset, account } } } } ``` - **rpc 是平台的 `free_room`、route 是 `room`**,不是 `erqiwang/jiesuan`; - 真实取值路径是 **`data.deskfree.data.aset`** 与 `data.deskfree.data.account`(多一层 `data` 包装); - 外层 `data.success` 由平台填,子游戏的 `success` 在 `data.deskfree.data.success`; - 解散局 `aset.multiple = 0`、`upgrade = 0`、各家 `grade = 0`,`account` 恒有; - **`data.deskfree` 可能缺失**(开战后、首局发牌前解散时 `get_disbandRoom` 返回 `null`)→ 前端必须容忍。 界面复用 §1.9 的大局总结算面板。 > **解散的申请/投票/同意拒绝界面由平台全包**(`Layer 420 CheckFree_Layer` + `GameUI.openCheck` + `Desk.self_apply_free_room` / `other_apply_free_room` / `self_agree_free_room` / `self_refuse_free_room` / `agreefree`),子游戏**不做投票界面**,只处理上面这个结算包。 ### 1.11 断线重连 `deskinfo` **不新增任何界面**。重连的本质是「恢复数据 → 调各 UI 组件 set-refresh 恢复界面」,**复用与正常流程完全相同的一条重画路径**(前端 04 §3.3 / 05 §6)。 平台把 `deskinfo` 挂在 `pack.data.deskinfo` 下,按当前 `step` 只带对应分组: | `step` | 分组 | 恢复到 §1.x | | --- | --- | --- | | 1 | `CallRun`(`seat` / `countdown` / `nowcall` / `multiple` / `call[3]`) | §1.3 叫分 | | 2 | `ChooseMain`(`banker` / `call` / `multiple` / `countdown` / `bottomcards` 仅庄家 / `touxiang`) | §1.5 选主 | | 3 | `BuryCards`(+ `flower`) | §1.6 埋牌 | | 5 | `PushCards`(`playproc` / `seatlist` / `liangpai` / `pushlist` / `grade` / `gradecards` / `bottomcards` 仅庄家) | §1.7 出牌 | | 6 | `Balance`(`readystate` / `aset`) | §1.8 结算 | 另有 `count` / `idx` / `PlayerInfo[3]` / `MyCards` 全阶段通用。 **关键点**: - `playproc.cards`(当前轮桌面上的牌)**两种查牌模式下都有** —— 否则后出的人无从跟牌; - `pushlist`(往轮历史)与 `seatlist` **仅可查牌模式有** —— 不查牌模式下前端不得渲染出牌历史; - 70 分的 3 秒亮底牌**不重放**; - **验收判据**(前端 05 §6.2):任意时刻丢弃 `this.data`、仅凭最近一次服务端快照重画,界面必须完全一致。做不到即「前端私存了状态」或「服务端漏发了字段」,两者都要修。 ### 1.12 准备 `zhunbei` 小局结算后各家点 `下一局` 进入下一局(§1.8),发 `zhunbei { seat }`。收 `zhunbei { seat }` → 该家玩家位显示「已准备」标记——**用平台的准备标识**(§0.2),子游戏不另做资源。 --- ## 2. 常驻 UI 跨阶段常驻的部件,图层 101。 ### 2.1 顶部信息条 【图:全部】 位置 x≈486–762, y≈4–72。三列表格式。 | 列 | 表头 | 数据 | 数据来源 | | --- | --- | --- | --- | | 1 | `主` | 花色图标 | `xuanzhu.flower` / `aset.flower`(1方块 2梅花 3红心 4黑桃);选主前显示占位 | | 2 | `叫分`(绿字) | 分数 + 蓝色角标 | `shangzhuang.grade` + `multiple` | | 3 | `抓分`(橙字) | 分数 + 橙色角标 | 分数 = 闲家累计捡分(逐轮 `chupai3.grade` 累加,结算时以 `aset.grade` 为准);角标 = **`Math.abs(curmultiple)`**(该字段带符号,角标只取大小,见 S-8) | 【待确认 T-16】:`抓分` 列的角标在 `等待叫分.png` 里是 `1子`、在 `小局结算.png` 里是 `1倍`,两图不一致。推定为 `aset.upgrade` 判定倍率(仅结算后有意义),需确认。 - 精灵:群组 201,号段 1001–1029。 - 资源:表格底图、花色小图标(4 帧)、角标底(2 种配色)。 ### 2.2 玩家位附加标记 【图:全部】 **平台负责头像/昵称/分数**(§0.2),子游戏在其旁叠加: | 元素 | 数据来源 | 显隐规则 | | --- | --- | --- | | **`庄` 印章** | `shangzhuang.banker` / `aset.banker` | 三家各一个精灵,按 `banker` 显隐其一(含底栏自己位) | | **`主N` 角标**(黄底黑字) | `seatlist[seat][4][0]` 剩余主牌数 | **仅可查牌模式 + 已报无主**;初始 `[-1,-1]` 时隐藏 | | **`对N` 角标**(白底黑字) | `seatlist[seat][4][1]` 剩余主对数 | 同上 | | **状态文字区** | 叫分阶段:`60分` 金色 / `不叫` 白色 | 叫分阶段显示,进入选主后清空 | - 精灵:群组 202(左上)/ 203(右上)/ 204(自己),号段 1100–1149。 ### 2.3 已出牌区 【图:出牌2.png】 三家各一个区域,展示本轮打出的牌(资源 `CARD_FACE_M`)。 - 自己:中央偏下 - 右上家(下家):右上,头像内侧 - **左上家(上家):与右上家左右镜像**——排列方式、牌尺寸、标签位置全部参照右上家,只是 x 方向对称、牌的堆叠朝向相反。已确认无需单独设计稿。 - 每张牌可叠加角标:`庄`(橙色斜标)、`大`(大王圆标)等 - 区域左下角:**牌型标签**(`拖拉机` 等,§1.7) - 三家的布局参数走同一份配置的 `bySeat` 三个变体,见 §6.5 - **每家预置 28 个牌精灵**(= 单次出牌张数上限,甩牌最多可甩满手牌)。号段每家 100 个,容量充裕,因此**不使用动态复制精灵**,避免 `SpriteCopyUtils` 的清理负担。 - 精灵:群组 212 / 213 / 214,号段 1300–1599。 ### 2.4 倒计时 【图:多张】 参考图里倒计时数字(`12` / `15` / `20`)出现在**当前操作者的位置旁**: - 轮到左上家 → 左上家头像右侧(x≈195–270, y≈165–235) - 轮到自己 → 自己操作区左侧(x≈445–520, y≈345–400) - 轮到右上家 → 对称位置 因此建 **3 个倒计时精灵**,按当前操作者显隐其一。 - 数据来源:各包的 `countdown`。 - **只做展示,不触发任何操作**(design §11 / 协议 §0.3):倒计时归零后**不自动叫分/选主/埋牌/出牌**,也不判负、不跳过。前端**不得**在归零时做任何乐观界面推进(不跳阶段、不清控制权),一切仍等服务端推送。 - **归零后停在 `0` 继续显示,不隐藏**:协议 §0.3 明确归零不触发任何动作、牌局原地等待该玩家;隐藏会被误读为「已超时/已跳过」。 - 精灵:群组 224,号段 1150–1159。 - **精灵类型(2026-08-27 订正)**:3 个倒计时精灵均为**多帧图数字精灵**(非文字精灵)——玩家视线焦点、视觉权重最高的数字,参考图是带描边/渐变的美术字,文字精灵做不出这个效果。显示走 `SpriteManager.setNumberImage`,资源 `NUM_COUNTDOWN`(§3.6),纯秒数无后缀。见 §5.4 / §6.8。 ### 2.5 自己的主牌统计 + 余主公示(他家)【图:多张】 **这是两套不同的东西,别混为一谈**: | 谁的 | 显示在 | 何时显示 | 数据来源 | | --- | --- | --- | --- | | **自己的主牌统计** | 手牌**上方偏左**(x≈35 y≈400 那条) | **有手牌时始终显示**,与报无主无关 | 前端据 `flower` 本地统计自己手牌 | | **他家的主牌数 / 对数**(**余主公示**) | 左上、右上家**头像下方**的 `主N` `对N` 角标 | **仅可查牌模式 + 已报无主**(design §9) | `seatlist[seat][4]` = `[剩余主牌数, 剩余主对数]` | **自己的主牌统计条**(群组 206 `ZHU_STAT_BAR`,精灵 `ZHU_STAT_BG` / `ZHU_STAT_TEXT`): - 文案 **`x对 x主`**(暂定,见 §7.2 T-9),例如 `3对 12主`。 - **始终显示、本地计算**——自己的手牌自己完全可见,不需要服务端下发,也不受查牌模式与报无主影响。 - 参考图里的 `主牌对子: 1对` 即此条。 - **2026-08-27 改名**:本条原名「亮牌条 / `LIANGPAI_*`」,与下方「亮牌」同名异实,已统一改为 `ZHU_STAT_*`(详见 §0.3 群组表下的修订说明)。 **余主公示:他家的 `主N` / `对N` 角标**(群组 202 / 203,结构见 §2.2): - 一个**图片底 + 一个文字精灵**,两家各一组。 - 初始 `[-1,-1]` 时隐藏;一旦全场有人报无主,服务端整表刷新 `seatlist` 并下发,两家的角标同时出现。 - 不可查牌模式下**永不出现**。 > **「亮牌」(`liangpai`)与上面两者都不是一回事**:它是 design §8.2 的规则——庄家达门槛时向**闲家**亮出自己**全部固定主牌的具体牌面**,用遮罩面板展示(§1.6,群组 **242**,复用 `HIST_*` 精灵),不是这里的统计条。 > 正因两者曾经同名,本条统计条在 2026-08-27 改名为 `ZHU_STAT_*`;代码里凡 `LIANGPAI` 字样自此**只属于**亮牌那一侧(协议字段 `liangpai`、`EQW_Anim.LIANGPAI_AUTO_CLOSE`)。 ### 2.6 底栏 【图:全部】 y≈648–720 深色条。左侧头像/昵称/分数由平台渲染(§0.2),子游戏负责: | 元素 | 位置(实测估值) | 显隐规则 | | --- | --- | --- | | `庄` 印章 | x≈338–370 | **自己是庄**时显示 | | `踩` / `有分` / `没分` 按钮 | x≈335–573(占用同一区域) | **自己是闲家 + 出牌阶段**时显示,与 `庄` 印章互斥(§1.7) | | 局数 `1/4 局` | 居中 x≈635–700 | 常驻,`asetidx / asetcount` | **右侧功能按钮共 5 个**,按情况显隐(不是全部常驻): | 按钮 | 看什么 | 显示条件 | | --- | --- | --- | | `上一轮` | **上一轮**所有人出的牌 | 可查牌 + 出牌阶段 + 已打过至少一轮 | | `出牌历史` | 所有人**出过的全部牌** | 可查牌 + 出牌阶段 | | `明牌` | 另两家手中全部未出主牌的具体牌面 | 可查牌 + 出牌阶段 + **场上已有人报无主**(`baozhu==1`)→ **三家都显示** | | **`扣底`** | **埋牌底牌**:庄家埋下的 8 张 | **仅庄家** + 已埋牌之后 | | **`底牌`**(**查底牌**) | **底牌**:发牌时没发给玩家、留在桌面的 8 张 | 庄家全程;闲家仅**开底**过的局(70 分坐庄)(S-3) | > ### 术语已统一:按钮名 = 术语名 > > **本项目采用修订后的术语**(2026-08-25 定,取代 design.md 原表述): > > | 新术语 | 指哪 8 张 | design.md 原称 | > | --- | --- | --- | > | **底牌** | 发牌时**没有发给玩家**、留在桌面的 8 张 | ~~暗牌~~ | > | **埋牌底牌** | 庄家埋牌时从手里扣下的 8 张 | ~~底牌~~ | > > 改后底栏按钮**名实相符**:`底牌` 按钮看底牌、`扣底` 按钮看埋牌底牌,不再有命名倒挂。 > > **协议字段也已拆开**(S-4 已完成),前端按字段名取值即可,不必再按所在包判断语义: > > | 按钮 | 展示 | 协议字段 | > | --- | --- | --- | > | `底牌` | 底牌(发牌留桌 8 张) | **`bottomcards`** —— `shangzhuang`(庄家恒有,闲家仅 70 分时有)、`ChooseMain`/`BuryCards`(仅庄家) | > | `扣底` | 埋牌底牌(庄家埋的 8 张) | **`burycards`** —— `maipai`、`PushCards`(仅庄家);结算包为 `bottom.cards` | > > **实现要求**:前端内部变量仍建议用 `bottomCards` / `buryCards` 区分,与字段名一一对应。已列入验收清单(§7.3)。 - 精灵:群组 205,号段 1040–1069(5 个功能按钮 × 2 精灵 = 10,`扣底` 为 1056/1057,2026-08-27 补齐;另有 3 个提示按钮 × 2 精灵)。 ### 2.7 手牌区 【图:全部】 | 形态 | 张数 | 位置(实测估值) | | --- | --- | --- | | **单排**(叫分 / 出牌) | 最多 36 | y≈455–645,x≈35–1250,重叠排列 | | **双排**(庄家埋牌) | 36(17 + 19) | 上排 y≈285–455,下排 y≈455–645 | - 建 **36 个手牌精灵**(庄家摸底牌后的上限),资源 `CARD_FACE_L`。 - 张数少于 36 时隐藏多余精灵并重算间距。 - 选中态:牌**上浮**(Y 偏移),另可叠加选中标记。 - 牌上标记(`拖` 圆标、⭐、🔵)见 T-8。 - 精灵:群组 210,号段 1200–1249。 ### 2.8 状态提示条(通用)【图:等待庄家选主.png / 强甩失败提示.png】 一条**深色半透圆角条 + 居中白字**,用于显示当前在等谁、或一次性的即时反馈。整个牌局复用**同一个**精灵,只换文字与位置。 | 用途 | 文案示例 | 位置(实测估值) | | --- | --- | --- | | **等待类**(常驻到状态结束) | `等待庄家选主`、`等待庄家埋牌`、`等待XX叫分` | 桌面中央 x≈500 y≈373 w≈320 h≈47 | | **即时反馈类**(自动淡出) | `强甩失败` | 桌面中央偏上 x≈462 y≈172 w≈363 h≈50 | - 两类**共用一套精灵**(`BAR_TOAST` + 文字精灵),位置由布局配置的两个预设切换。 - 文案表进配置,不在代码里写死字符串。 - 精灵:图层 105 群组 230,`1760` 条底 + `1761` 文字。 #### 文案清单(T-33 已定) **全部 3 条**。 | # | 文案 | 类型 | 谁看到 | 显示区间 | | --- | --- | --- | --- | --- | | 1 | **`等待庄家选主`** | 状态类 | 两个闲家 | 收 `shangzhuang` 后 → 收 `xuanzhu` 前(§1.5) | | 2 | **`等待庄家埋牌`** | 状态类 | 两个闲家 | 收 `xuanzhu` 后 → 收 `maipai` 前(§1.6) | | 3 | **`强甩失败`** | 即时反馈 | 全场 | 收到 `chupai1.shuaicuo == 1`,自动淡出(§1.7) | > 前 2 条是「状态类」——常驻到该状态结束、由收包驱动收起;第 3 条是「即时反馈类」——自动淡出。 > 两类**共用同一套精灵**(`BAR_TOAST` + 文字),只切位置预设与文案。 **明确不做的**(已确认): | 不做 | 理由 | | --- | --- | | 叫分等待 | **倒计时已经指明在等谁**——它就显示在当前叫分者的位置旁(§2.4) | | 出牌等待 | 同上;且一局约 84 次切换,弹条只会吵 | | 准备等待 | **平台自带已准备 / 未准备标识**(§0.2),不重复造 | | 玩家掉线 / 重连提示 | **平台已覆盖**(`Layer 615 Reconnect_Layer` 等),子游戏不实现 | | **开底提示** | **画面本身已经说清楚了**——闲家看到底牌由背面翻成正面,就是开底;再加一行字是多余的 | 文案表进配置,不在代码里写死字符串。 > 原 §5.5 的 `BAR_SHUAICUO`(1754/1759)并入本部件——甩错只是这条通用提示条的一种文案,不必单独出图。 --- ## 3. 图片资源总表 > 给美术清点用。**按钮一律只出 1 帧普通态**——点击的缩放/透明反馈由框架统一处理(client 02 §2)。 > 尺寸列为参考图实测估值,出图前请与最终设计稿核对。 ### 3.1 牌面(3 套,见 §0.4) | ID | 键名 | 用途 | 帧数 | 帧说明 | 单帧尺寸 | 整图 | | --- | --- | --- | --- | --- | --- | --- | | 501 | `CARD_FACE_L` | 手牌、底牌 | **60** | 见 §0.4 帧表(10×6 通用排版) | 110 × 190 | 1100 × 1140 | | 502 | `CARD_FACE_M` | 已出牌区 | **60** | 同上 | 90 × 155 | 900 × 930 | | 503 | `CARD_FACE_S` | 结算埋牌底牌区 | **60** | 同上 | 50 × 70 | 500 × 420 | ### 3.2 花色图标 | ID | 键名 | 用途 | 帧数 | 帧说明 | 尺寸 | | --- | --- | --- | --- | --- | --- | | 511 | `SUIT_ICON_S` | 顶部信息条 `主` 列 | 4 | 帧1=方块♦ 帧2=梅花♣ 帧3=红心♥ 帧4=黑桃♠(**按服务端 flower 编号 1–4 排序**) | 30 × 30 | | ~~512~~ | ~~`SUIT_ICON_L`~~ | **不需要**:选主面板花色图标已并入 `BTN_SUIT_CHOOSE` 帧1–4(2026-08-26,美术改一张图 5 帧出图) | —— | —— | —— | > ⚠️ 花色图标的帧序按**服务端 flower 编号**(1方块 2梅花 3红心 4黑桃),与牌面资源的花色顺序**不同**。两者互不换算,各自独立。 ### 3.3 按钮 | ID | 键名 | 用途 | 帧数 | 帧说明 | 尺寸 | | --- | --- | --- | --- | --- | --- | | 531 | `BTN_CALL_SCORE` | 叫分档位按钮底 **+ 右上角角标底(同一张图,不含文字)** | **4** | 帧1=浅黄+蓝角标(70/65/60/55) 帧2=金黄+绿角标(50/45/40) 帧3=橙+深橙角标(35…5) 帧4=**灰**(已叫过/不可选)。**颜色按档位位置固定,只因不可叫变灰** | 74 × 56 | | 532 | `BTN_NO_CALL` | `不叫` | 1 | 蓝色渐变 | 184 × 57 | | 533 | `BTN_SUIT_CHOOSE` | **选主 / 投降面板按钮**(花色按钮 + 投降按钮,一张图 5 帧) | **5** | **帧1–4=四花色按钮,每帧已含「按钮底 + 花色图标 + 右上角蓝色角标底」,花色序按服务端 flower 编号(帧1=方块♦ 帧2=梅花♣ 帧3=红心♥ 帧4=黑桃♠);帧5=投降按钮,已含「按钮底 + 投降二字」**(2026-08-26 修订:原为按钮底单帧,花色图标/角标底/投降文字各自单独出精灵;美术侧实际按一张图 5 帧出图,改按此表) | 128 × 55 | | ~~534~~ | ~~`BTN_SURRENDER`~~ | **不需要**:投降按钮已并入 `BTN_SUIT_CHOOSE` 帧5(2026-08-26) | —— | —— | —— | | 535 | `BTN_TIP` | `提示` | 1 | 蓝色渐变 | 180 × 63 | | 536 | `BTN_BURY` | `埋牌 (N/8)` | 2 | 帧1=可提交(金黄) 帧2=置灰(未满 8 张) | 180 × 63 | | 537 | `BTN_PLAY` | `出牌` | 2 | 帧1=可出 帧2=置灰 | 157 × 55 | | 538 | `BTN_BOTTOM_FUNC` | 底栏功能钮底(明牌/已出牌/上一轮/底牌共用) | 1 | 深色圆角 | 88 × 30 | | ~~539~~ | ~~`BTN_READY`~~ | **不需要**:准备标识用平台的(§0.2) | —— | —— | | 540 | `BTN_TISHI` | 底栏 `踩` / `有分` / `没分` 按钮底(三者共用) | 1 | 深色圆角,同 `BTN_BOTTOM_FUNC` 风格但更窄 | 52 × 30 | | 541 | `BTN_NEXT_ASET` | 结算 `下一局` | 1 | 金黄渐变 | 180 × 65 | | 542 | `BTN_CG_CARDS` | 结算 `冲关牌型` | 1 | 蓝色渐变 | 183 × 63 | | 543 | `BTN_ACCOUNT_AGAIN` | 大局结算 `再来一局` | 1 | 黄色渐变 | 190 × 60 | | 544 | `BTN_ACCOUNT_SHARE` | 大局结算 `分享好友` | 1 | 蓝色渐变 | 190 × 60 | | 545 | `BTN_ACCOUNT_COPY` | 大局结算 `复制战绩` | 1 | 蓝色渐变 | 190 × 60 | | 546 | `BTN_ACCOUNT_EXIT` | 大局结算 `退出房间` | 1 | 红色渐变 | 190 × 60 | | 547 | `BTN_ACC_CLOSE` | 大局结算标题栏右上角关闭 | 1 | —— | 36 × 36 | ### 3.4 标记类 | ID | 键名 | 用途 | 帧数 | 帧说明 | 尺寸 | | --- | --- | --- | --- | --- | --- | | 571 | `MARK_BANKER` | `庄` 红色印章 | 1 | —— | 40 × 40 | | 572 | `MARK_BANKER_CORNER` | 牌角 `庄` 橙色斜标 | 1 | —— | 28 × 28 | | 573 | `MARK_JOKER_BIG` | 大王 `大` 圆标 | 2 | 帧1=大王 帧2=小王 | 34 × 34 | | 574 | `BADGE_ZHU_PAIR` | `主N` / `对N` 角标底 | 2 | 帧1=黄底(主) 帧2=白底(对) | 44 × 20 | | 575 | `BADGE_MULTIPLE` | 子数/倍数角标底 | 2 | 帧1=蓝底 帧2=橙底 | 34 × 18 | | 576 | `LABEL_CARDTYPE` | 牌型标签 | **1** | **只做「毙」一种**(前端只展示这一个标签)。数据层仍保留 毙 / 垫 / 混合出牌 三种标识以备扩展,但当前不出图、不显示 | 76 × 24 | | 577 | `MARK_CARD_GROUP` | 手牌牌型标记 | **3** | **帧1=`拖`(拖拉机)帧2=橙色五角星(正2 / 正7)帧3=蓝色五角星(除正2正7外的其他主牌)** | 26 × 26 | | 578 | `MARK_SELECTED` | 手牌选中标记 | 1 | 待定(也可只用上浮,不出图) | 待定 | | 579 | `BADGE_RANK` | 大局结算名次徽章(花瓣形) | **3** | 帧1=第1名(金) 帧2=第2名(橙) 帧3=第3名(蓝) | 44 × 44 | ### 3.5 面板底图 | ID | 键名 | 用途 | 帧数 | 尺寸 | | --- | --- | --- | --- | --- | | 601 | `PANEL_TOP_INFO` | 顶部三列信息条底 | 1 | 276 × 68 | | 602 | `PANEL_CALL_SCORE` | 叫分面板底 | 1 | 575 × 170 | | 603 | `BAR_HINT_TOP` | `70分坐庄才可投降` 提示条底 | 1 | 345 × 33 | | 604 | `BAR_CHOOSE_MAIN` | `亮主` 斜角标题条 | 1 | 425 × 50 | | 605 | `BAR_ZHU_STAT` | 主牌统计条底(半透圆角,§2.5;2026-08-27 由 `BAR_LIANGPAI` 改名,ID 不变) | 1 | 210 × 35 | | 606 | `PANEL_BOTTOM_CARDS` | 结算底牌区面板底 | 1 | 420 × 148 | | 607 | `GLOW_RESULT` | 结算分数光晕背景 | 2 | 帧1=赢(橙) 帧2=输(蓝) | 225 × 100 | | 608 | `BAR_FOOTER` | 底栏深色条 | 1 | 1280 × 72 | | 609 | `MASK_DIM` | 半透黑遮罩(冲关牌型 / 出牌历史 / 明牌 共用) | 1 | 1280 × 720(纯色可拉伸,出 1 张小图即可) | | 610 | `PANEL_ACCOUNT` | 大局总结算面板底(圆角浅灰绿) | 1 | 1100 × 540 | | 611 | `BAR_ACCOUNT_TITLE` | 大局结算标题栏(青绿底,含圆角上沿) | 1 | 1100 × 56 | | 612 | `BAR_ACCOUNT_RULE` | 大局结算底部规则栏(浅绿底,含圆角下沿) | 1 | 1100 × 62 | | 613 | `LINE_DIVIDER` | 玩家栏之间的分隔线 | 1 | 1060 × 2 | ### 3.6 数字与符号 > **2026-08-26 修订(重要,出图规范订正)**:数字类资源 631/632/633/635/636 一律出成 **16 帧、固定帧序 `0123456789.+-x/p`** 的多帧图片资源。 > 缘由:平台的 `SpriteManager.setNumberImage(spriteId, text, charWidth)` 就是**为多位美术字数字准备的现成接口**——把整串文本设给**一个**多帧图片精灵,引擎按字符逐帧渲染,并按**字符数 × `charWidth`** 自动调整精灵宽度。所以**一个精灵就能显示一整串数字**:既不用拆十位/个位,也不用 `setFrame` 逐位切(`setFrame` 是"整个精灵显示第 N 帧",只适合状态/花色/牌面这类"选其一")。 > 帧号由字符**直接换算**(`'0'–'9'` → 帧1–10,后缀字 → 帧16),因此帧序**不可重排、不可省略占位**;用不到的帧位也要按顺序留出来,否则帧号对不上、画面串位。 > 三种显示机制的选用判据见前端文档 `02-渲染与UI组件体系.md`;`charWidth`/`charHeight` 等数值在 `EQW_Layout.NUM_STYLE.*`(`LayoutConstants.js`)集中配置,本表只记出图规格。 **固定帧序(所有 NUM_\* 通用)**: | 帧 | 1–10 | 11 | 12 | 13 | 14 | 15 | 16 | | --- | --- | --- | --- | --- | --- | --- | --- | | 内容 | `0`–`9` | `.` | `+` | `-` | `x` | `/` | **可变后缀**(该套资源自己的后缀字:`分`/`倍`/`张`) | | ID | 键名 | 用途 | 帧数 | 第 16 帧(后缀) | 单字符尺寸 | 整图尺寸 | | --- | --- | --- | --- | --- | --- | --- | | 631 | `NUM_COUNTDOWN` | 倒计时数字 | **16** | **无后缀**(出空白占位) | 44 × 60 | **704 × 60**(16 × 44) | | 635 | `NUM_CALL_SCORE` | **叫分档位的分数数字**(深棕描边美术字) | **16** | 【**待美术与规则确认**】档位只显示分数数字;若日后要显示 `70分` 则此帧出 `分` | 30 × 40 | **480 × 40**(16 × 30) | | 632 | `NUM_RESULT_WIN` | 结算得分数字(赢,橙)——用到帧12 `+`、帧13 `-` | **16** | 【**待美术与规则确认**】结算大字目前只显示数字,也可能是 `分` | 46 × 62 | **736 × 62**(16 × 46) | | 633 | `NUM_RESULT_LOSE` | 结算得分数字(输,蓝) | **16** | 同 632(含待确认项),仅配色不同 | 46 × 62 | **736 × 62**(16 × 46) | | 634 | `ANI_JUDGE` | **判定结果动画**(大光 / 小光 / 过庄 / 升N级 / 投降)——帧动画资源,播放参数进 `AnimationConfigs`【待设计 D-8】。**注意:这是帧动画,不适用本节的 16 帧数字规范** | 待定 | —— | 待定 | 待定 | | 636 | `NUM_MAIN_SUIT_COUNT` | **选主面板花色张数数字**(美术单独出图,字号配色与以上几套不同;**一个花色一个精灵**) | **16** | **`张`**(显示形如 `26张`) | 24 × 32(估值) | **384 × 32**(16 × 24) | > **出图要求**:16 帧**等宽**(后缀字与数字同宽,因为宽度按"字符数 × 单字宽"算);帧序严格照上表,用不到的帧位(如某些套的 `.` `x` `/`)**仍要占位**。 > **带描边/渐变的美术字用多帧图数字精灵 + `setNumberImage`**(倒计时、结算得分、叫分分数、选主张数),文字精灵做不出这种效果——631–636 都要出图。 > **纯数据型小字用文字精灵**(`SpriteManager.setText` / `setTextWithWidth`):叫分角标的子数(值域 2–15,随爬坡开关变)、`主N`/`对N` 角标、局数、主牌统计条文案等。 > **2026-08-26 新增 636**:选主张数原计划走文字精灵,但美术侧单独出了一套数字图(字号配色不同于既有几套);同日一度定为「十位/个位各一个精灵」,**现已撤销**——`setNumberImage` 一个精灵就能显示 `26张`。 ### 3.7 浮层与气泡 | ID | 键名 | 用途 | 帧数 | 帧说明 | 尺寸 | | --- | --- | --- | --- | --- | --- | | 651 | `BUBBLE_TISHI` | 踩 / 没分 / 有分 提示气泡 | **6**(3×2) | **帧1=踩·箭头向左 帧2=没分·箭头向左 帧3=有分·箭头向左 帧4=踩·箭头向右 帧5=没分·箭头向右 帧6=有分·箭头向右**。帧序内层是 `tip`(1踩/2没分/3有分)、外层是箭头朝向 → `frame = (arrowRight ? 3 : 0) + tip`。**箭头向左 = 头像在气泡左侧**、箭头向右 = 头像在气泡右侧 | 待定 | | ~~652~~ | ~~`TXT_BAOZHU`~~ | **不需要**:报无主不做全场提示(D-4),只让他家 `主N`/`对N` 角标出现 | —— | —— | —— | | 653 | `BAR_TOAST` | **通用状态提示条**底(深色半透圆角,白字居中)——`等待庄家选主` / `强甩失败` 等共用(§2.8) | 1 | 可九宫拉伸,按文案长度变宽 | 363 × 50 | | 654 | `TXT_GRADE_FLOAT` | 捡分飘字底(`20分` 金色) | 1 | 待定 | 待定 | | ~~655~~ | ~~`PANEL_BOTTOM_3S`~~ | **不需要**:底牌 3 秒展示复用庄家翻底牌那套 UI(D-2),仅可见范围不同 | —— | —— | —— | ### 3.8 建房选项 | ID | 键名 | 用途 | 帧数 | 帧说明 | 尺寸 | | --- | --- | --- | --- | --- | --- | | 681 | `ROOM_OPT_BOX` | 建房**选择框**(单选钮 + 复选框合一) | **4**(2×2) | **帧1=单选·未选中(灰空心环) 帧2=单选·已选中(橙实心环) 帧3=复选·未选中(浅绿空方块) 帧4=复选·已选中**。帧号 = `(useCheckbox ? 3 : 1) + (selected ? 1 : 0)`;「单选(可不选)」用复选帧 3/4 | 22 × 22 | | 682 | `ROOM_CAT_TITLE` | 建房**类别标题**(一张图多帧) | **3** | 帧1=牌局 帧2=模式 帧3=规则 | 60 × 24 | | 683 | `ROW_ROOM_OPT` | 选项行底(可选,参考图为无底透明) | 1 | —— | 待定 | --- ## 4. 声音资源总表 【整节待确认 T-20】 design 与协议均**未定义任何音效**。以下是按牌类游戏常规推导的候选清单,**未经确认,不得直接作为出音依据**: | ID | 键名 | 触发时机 | | --- | --- | --- | | 101 | `SND_DEAL` | 发牌 | | 102 | `SND_CALL_SCORE` | 叫分 | | 103 | `SND_NO_CALL` | 不叫 | | 104 | `SND_BE_BANKER` | 上庄 | | 105 | `SND_CHOOSE_MAIN` | 选主 | | 106 | `SND_SURRENDER` | 投降 | | 107 | `SND_BURY` | 埋牌 | | 108 | `SND_PLAY_CARD` | 出牌 | | 109 | `SND_SHUAI` | 甩牌 | | 110 | `SND_SHUAICUO` | 甩错 | | 111 | `SND_BI` | 毙牌 | | 112 | `SND_WIN_ROUND` | 赢下一轮 / 捡分 | | 113 | `SND_BAOZHU` | 报无主 | | 114 | `SND_TISHI_1/2/3` | 踩 / 没分 / 有分 | | 115 | `SND_KOUDI` | 扣底 | | 116 | `SND_RESULT_WIN` | 小局结算 · 赢 | | 117 | `SND_RESULT_LOSE` | 小局结算 · 输 | | 118 | `SND_COUNTDOWN_TICK` | 倒计时最后 N 秒 | | 119 | `SND_BTN_CLICK` | 通用按钮点击 | **要确认的**:是否需要男/女声区分、是否需要方言语音包、`SND_TISHI` 是否用真人语音、倒计时提示音从第几秒开始。 --- ## 5. 精灵结构表 > 格式遵循 `gameabc-framework/templates/GameView_Sprites.template.js`。 > **ID 为规划建议值**,编辑器建成后须逐个回填校准(§0 前言)。 > 每条注明:类型(图片/文字/容器/热区)· 用途 · 资源键 · 帧说明。 ### 5.1 Layer 101 · 牌桌常驻 | 群组 | 精灵 ID | 键名 | 类型 | 用途 | 资源 / 帧 | | --- | --- | --- | --- | --- | --- | | **201** 顶部信息条 | 1001 | `TOP_INFO_BG` | 图片 | 三列表格底 | `PANEL_TOP_INFO` | | | 1002 | `TOP_SUIT_ICON` | 图片 | `主` 列花色 | `SUIT_ICON_S` 帧 = flower | | | 1003 | `TOP_CALL_TEXT` | 文字 | 叫分数值 | —— | | | 1004 | `TOP_CALL_BADGE_BG` | 图片 | 叫分角标底 | `BADGE_MULTIPLE` 帧1 | | | 1005 | `TOP_CALL_BADGE_TEXT` | 文字 | `N子` | —— | | | 1006 | `TOP_GRADE_TEXT` | 文字 | 抓分数值 | —— | | | 1007 | `TOP_GRADE_BADGE_BG` | 图片 | 抓分角标底 | `BADGE_MULTIPLE` 帧2 | | | 1008 | `TOP_GRADE_BADGE_TEXT` | 文字 | `N倍`【T-16】 | —— | | **206** 主牌统计条 | 1030 | `ZHU_STAT_BG` | 图片 | 半透圆角条 | `BAR_ZHU_STAT` | | | 1031 | `ZHU_STAT_TEXT` | 文字 | 自己主牌统计文案 `x对 x主` | —— | | **205** 底栏 | 1040 | `FOOTER_BG` | 图片 | 底栏深色条 | `BAR_FOOTER` | | | 1041 | `FOOTER_ASET_TEXT` | 文字 | `1/4 局` | —— | | | 1042 | `BTN_MINGPAI` | 图片 | `明牌` | `BTN_BOTTOM_FUNC` | | | 1043 | `BTN_MINGPAI_TEXT` | 文字 | 按钮文案 | —— | | | 1044 | `BTN_HISTORY` | 图片 | `已出牌` | `BTN_BOTTOM_FUNC` | | | 1045 | `BTN_HISTORY_TEXT` | 文字 | —— | —— | | | 1046 | `BTN_LAST_ROUND` | 图片 | `上一轮` | `BTN_BOTTOM_FUNC` | | | 1047 | `BTN_LAST_ROUND_TEXT` | 文字 | —— | —— | | | 1048 | `BTN_BOTTOM_CARDS` | 图片 | `底牌` | `BTN_BOTTOM_FUNC` | | | 1049 | `BTN_BOTTOM_CARDS_TEXT` | 文字 | —— | —— | | | 1050 | `BTN_TISHI_CAI` | 图片 | `踩`(闲家,占庄标区) | `BTN_TISHI` | | | 1051 | `BTN_TISHI_CAI_TEXT` | 文字 | `踩` | —— | | | 1052 | `BTN_TISHI_HAS` | 图片 | `有分` | `BTN_TISHI` | | | 1053 | `BTN_TISHI_HAS_TEXT` | 文字 | `有分` | —— | | | 1054 | `BTN_TISHI_NONE` | 图片 | `没分` | `BTN_TISHI` | | | 1055 | `BTN_TISHI_NONE_TEXT` | 文字 | `没分` | —— | | | **1056** | `BTN_BURY_CARDS` | 图片 | **`扣底`**(看**埋牌底牌**,2026-08-27 补齐) | `BTN_BOTTOM_FUNC` | | | **1057** | `BTN_BURY_CARDS_TEXT` | 文字 | 按钮文案 `扣底` | —— | | **202** 左上家标记 | 1100 | `P_LEFT_BANKER` | 图片 | `庄` 印章 | `MARK_BANKER` | | | 1101 | `P_LEFT_ZHU_BG` | 图片 | `主N` 角标底 | `BADGE_ZHU_PAIR` 帧1 | | | 1102 | `P_LEFT_ZHU_TEXT` | 文字 | 剩余主牌数 | —— | | | 1103 | `P_LEFT_PAIR_BG` | 图片 | `对N` 角标底 | `BADGE_ZHU_PAIR` 帧2 | | | 1104 | `P_LEFT_PAIR_TEXT` | 文字 | 剩余主对数 | —— | | | 1105 | `P_LEFT_STATUS_TEXT` | 文字 | `60分` / `不叫` | —— | | **203** 右上家标记 | 1110–1115 | `P_RIGHT_*` | | 结构同 202 | | | **204** 自己标记 | 1120 | `P_SELF_BANKER` | 图片 | 底栏 `庄` 印章 | `MARK_BANKER` | | | 1121 | `P_SELF_STATUS_TEXT` | 文字 | `50分` / `不叫` | —— | ### 5.2 Layer 102 · 手牌区(群组 210) | 精灵 ID | 键名 | 类型 | 用途 | 资源 / 帧 | | --- | --- | --- | --- | --- | | 1200–1235 | `HAND_CARD_1` … `HAND_CARD_36` | 图片 ×36 | 自己手牌 | `CARD_FACE_L`,帧 = `cardIdToFrame(id)` | | 2100–2135 | `HAND_MARK_1` … `HAND_MARK_36` | 图片 ×36 | 手牌牌面标记【T-8】,与 `HAND_CARD_1..36` **一一对应**(`HAND_MARK_i` 属于 `HAND_CARD_i`) | `MARK_CARD_GROUP` | > **2026-08-26 修订**:原表写「牌型**分组**标记 ×8,1236–1243」,与本文 §1.6 的「三种标记互斥、**每张牌**至多一个」相矛盾——按分组理解只需 ≤8 个(一手至多 8 组拖拉机),按每张牌理解则需与手牌等量。 > **裁决按「每张牌」**:§1.6 是行为规格、比本表的措辞更权威,且标记的三种取值(`拖` / 正2正7 / 其他主牌)本就是逐张判定的属性,而非只标在拖拉机组上。实测一手 28 张牌通常带 13 个以上标记、36 张埋牌手更多,8 个远远不够。 > 号段改为 **2100–2135**(1236–1243 段太窄容不下 36 个,且其后 1250 起已被底牌区占用);`1236–1243` 作废、不再分配。前端 `config/Sprites_Cards.js` 已按此落地,`core/CardMark.js` 亦按每张牌返回标记。 ### 5.3 Layer 103 · 桌面牌 | 群组 | 精灵 ID | 键名 | 类型 | 用途 | 资源 / 帧 | | --- | --- | --- | --- | --- | --- | | **211** 底牌 | 1250–1257 | `BOTTOM_CARD_1..8` | 图片 ×8 | 底牌(发牌留桌 8 张);未揭示时帧 = 55 牌背,70 分公示 / 庄家查看时翻正面 | `CARD_FACE_L` | | **215** 埋牌底牌 | 1258–1265 | `BURY_CARD_1..8` | 图片 ×8 | 结算时亮出的埋牌底牌 | `CARD_FACE_S` | | **212** 自己出牌 | 1300–1327 | `PLAY_SELF_1..28` | 图片 ×28 | 本轮打出的牌 | `CARD_FACE_M` | | | 1328 | `PLAY_SELF_TYPE_BG` | 图片 | 牌型标签 | `LABEL_CARDTYPE` | | | 1329 | `PLAY_SELF_TYPE_TEXT` | 文字 | 标签文案 | —— | | **213** 左上家出牌 | 1400–1429 | `PLAY_LEFT_*` | | 结构同 212 | | | **214** 右上家出牌 | 1500–1529 | `PLAY_RIGHT_*` | | 结构同 212 | | ### 5.4 Layer 104 · 阶段操作区 | 群组 | 精灵 ID | 键名 | 类型 | 用途 | 资源 / 帧 | | --- | --- | --- | --- | --- | --- | | **220** 叫分面板 | 1600 | `CALL_PANEL_BG` | 图片 | 面板底 | `PANEL_CALL_SCORE` | | | 1601 | `CALL_HINT_BG` | 图片 | 提示条底 | `BAR_HINT_TOP` | | | 1602 | `CALL_HINT_TEXT` | 文字 | `70分坐庄才可投降` | —— | | | 1603–1616 | `CALL_BTN_1..14` | 图片 ×14 | 档位按钮底(**含角标底**,70…5) | `BTN_CALL_SCORE` 帧1–3 按档位位置固定 / 帧4 灰 | | | 1617–1630 | `CALL_BTN_NUM_1..14` | **多帧图数字精灵** ×14 | 档位分数(70…5),**一个精灵显示整串**(`setNumberImage`,两位数不拆精灵;2026-08-26,见 §1.3/§3.6) | `NUM_CALL_SCORE`(16 帧)+ `NUM_STYLE.CALL_SCORE.charWidth` | | | 1631–1644 | `CALL_BTN_BADGE_1..14` | **文字** ×14 | 该档 `multiple` 子数(`2子`…`15子`),随爬坡开关变 | —— | | | 1645 | `CALL_BTN_NONE` | 图片 | `不叫` | `BTN_NO_CALL` | | | 1646 | `CALL_BTN_NONE_TEXT` | 文字 | —— | —— | | **221** 选主面板(2026-08-26 修订:美术改一张图 5 帧出图,花色图标/角标底/投降文字并入按钮帧,见 §1.5/§3.3) | 1650 | `MAIN_TITLE_BG` | 图片 | `亮主` 斜角条 | `BAR_CHOOSE_MAIN` | | | 1651 | `MAIN_TITLE_TEXT` | 文字 | `亮主` | —— | | | 1652–1655 | `MAIN_SUIT_BTN_1..4` | 图片 ×4 | 花色按钮(**已含按钮底+花色图标+角标底**),帧=花色序1–4 | `BTN_SUIT_CHOOSE` 帧1–4 | | | 1668–1671 | `MAIN_SUIT_PAIR_TEXT_1..4` | 文字 ×4 | `N对`,叠在对应按钮角标底之上 | —— | | | 1674–1677 | `MAIN_SUIT_COUNT_1..4` | **多帧图数字精灵** ×4 | 该花色在庄家手中的**张数**,显示 `26张`(**一个花色一个精灵**,`setNumberImage`;2026-08-26 由十位/个位 8 个精灵改回 4 个,见 §3.6) | `NUM_MAIN_SUIT_COUNT`(16 帧,第16帧=`张`)+ `NUM_STYLE.MAIN_SUIT_COUNT.charWidth` | | | 1672 | `MAIN_BTN_SURRENDER` | 图片 | `投降`(**已含按钮底+文字**) | `BTN_SUIT_CHOOSE` 帧5 | | | ~~1656–1659 / 1660–1663 / 1664–1667 / 1673~~ | ~~`MAIN_SUIT_ICON_1..4` / 原 `MAIN_SUIT_COUNT_1..4`(文字精灵)/ `MAIN_SUIT_PAIR_BG_1..4` / `MAIN_BTN_SURRENDER_TEXT`~~ | —— | **不需要**:分别并入按钮帧、或改为上方 1674–1677 的数字精灵,空出的 ID 不复用 | —— | | | ~~1678–1681~~ | ~~`MAIN_SUIT_COUNT_3/4_TENS/ONES`~~ | —— | **不需要**(2026-08-26):`setNumberImage` 一个精灵就能显示整串,十位/个位拆分作废,空出的 4 个 ID 不复用 | —— | | **222** 埋牌操作条 | 1700 | `BURY_BTN_TIP` | 图片 | `提示` | `BTN_TIP` | | | 1701 | `BURY_BTN_TIP_TEXT` | 文字 | —— | —— | | | 1702 | `BURY_BTN_SUBMIT` | 图片 | `埋牌` | `BTN_BURY` 帧1可提交/帧2灰 | | | 1703 | `BURY_BTN_SUBMIT_TEXT` | 文字 | `埋牌 (N/8)` | —— | | **223** 出牌操作条 | 1710 | `PLAY_BTN_SUBMIT` | 图片 | `出牌` | `BTN_PLAY` 帧1可出/帧2灰 | | | 1711 | `PLAY_BTN_SUBMIT_TEXT` | 文字 | —— | —— | | | 1712 | `PLAY_BTN_TIP` | 图片 | `提示`(出牌阶段必备) | `BTN_TIP` | | | 1713 | `PLAY_BTN_TIP_TEXT` | 文字 | 按钮文案 | —— | | **224** 倒计时(2026-08-27 订正:由文字精灵改为多帧图数字精灵,见 §2.4/§6.8) | 1150 | `CD_LEFT` | **多帧图数字精灵** | 左上家位倒计时 | `NUM_COUNTDOWN`(16 帧)+ `NUM_STYLE.COUNTDOWN.charWidth` | | | 1151 | `CD_RIGHT` | **多帧图数字精灵** | 右上家位倒计时 | `NUM_COUNTDOWN`(16 帧)+ `NUM_STYLE.COUNTDOWN.charWidth` | | | 1152 | `CD_SELF` | **多帧图数字精灵** | 自己操作区倒计时 | `NUM_COUNTDOWN`(16 帧)+ `NUM_STYLE.COUNTDOWN.charWidth` | ### 5.5 Layer 105 · 桌面浮层(群组 230) | 精灵 ID | 键名 | 类型 | 用途 | 资源 / 帧 | | --- | --- | --- | --- | --- | | 1750–1752 | `BUBBLE_TISHI_LEFT/RIGHT/SELF` | 图片 ×3 | 踩/没分/有分 气泡(按发送者位置) | `BUBBLE_TISHI` 帧 = tip | | ~~1753~~ | ~~`TXT_BAOZHU`~~ | —— | **不需要**(D-4) | —— | | ~~1754 / 1759~~ | ~~`BAR_SHUAICUO`~~ | —— | 并入下方通用状态提示条 | —— | | 1760 | `BAR_TOAST` | 图片 | 通用状态提示条底(§2.8) | `BAR_TOAST` | | 1761 | `TXT_TOAST` | 文字 | 提示文案(等待类 / 即时反馈类共用) | —— | | 1755 | `GRADE_FLOAT_BG` | 图片 | 捡分飘字底 | `TXT_GRADE_FLOAT` | | 1756 | `GRADE_FLOAT_TEXT` | 文字 | `20分` | —— | | ~~1757 / 1758~~ | ~~`BOTTOM_3S_*`~~ | —— | **不需要**(D-2:底牌 3 秒展示复用底牌区翻牌,仅可见范围不同) | —— | ### 5.6 Layer 302 · 小局结算(群组 240) | 精灵 ID | 键名 | 类型 | 用途 | 资源 / 帧 | | --- | --- | --- | --- | --- | | 1800 | `RESULT_BOTTOM_PANEL` | 图片 | 底牌区面板底 | `PANEL_BOTTOM_CARDS` | | 1801 | `RESULT_BOTTOM_TEXT` | 文字 | `叫分{multiple}子 底牌 {grade1}x{bottom.multiple}` | —— | | 1802–1804 | `RESULT_GLOW_LEFT/RIGHT/SELF` | 图片 ×3 | 三家分数光晕 | `GLOW_RESULT` 帧1赢/帧2输 | | 1805–1807 | `RESULT_SCORE_LEFT/RIGHT/SELF` | **多帧图数字精灵** ×3(2026-08-27 由文字精灵改,见 §1.9/§6.9) | 三家总分(`aset.seatlist[i].grade`),正分橙带 `+`/负分蓝带 `-`,与 `ACC_TPL_SCORE_NUM`(1914)同一套资源与用法 | `NUM_RESULT_WIN`(赢)/ `NUM_RESULT_LOSE`(输)+ `NUM_STYLE.RESULT_WIN/RESULT_LOSE.charWidth` | | 1808–1810 | `RESULT_JF_LEFT/RIGHT/SELF` | 文字 ×3 | 捡分子数得分 `grade_jf`【T-13】 | —— | | 1811–1813 | `RESULT_AW_LEFT/RIGHT/SELF` | 文字 ×3 | 算奖得分 `grade_aw`【T-13】 | —— | | ~~1814~~ | ~~`RESULT_JUDGE_TEXT`~~ | —— | **不需要**:结算面板不显示判定文字,改由动画呈现(§1.8) | —— | | 1815 | `RESULT_BTN_CG` | 图片 | `冲关牌型` 按钮 | `BTN_CG_CARDS` | | 1816 | `RESULT_BTN_CG_TEXT` | 文字 | 按钮文案 | —— | | 1817 | `RESULT_BTN_NEXT` | 图片 | `下一局` 按钮 | `BTN_NEXT_ASET` | | 1818 | `RESULT_BTN_NEXT_TEXT` | 文字 | 按钮文案 | —— | > 底牌 8 张精灵复用 §5.3 群组 215(1258–1265),不在此重复。 ### 5.6a 冲关牌型叠加层(Layer 302,群组 **244**,**精灵复制**) **本层的牌用 `SpriteCopyUtils` 运行时复制,不预置固定张数。** `aset.seatlist[i].cards` 的长度不定(四王 + 长连对链 + 八个 7 + 八个 2 可达 20 余张),预置既可能不够、又在多数局里白占 ID。 **只需在编辑器里建 5 个精灵**(3 个容器 + 1 个牌模板 + 1 个遮罩): | 精灵 ID | 键名 | 类型 | 用途 | 资源 / 帧 | | --- | --- | --- | --- | --- | | 1820 | `CG_MASK` | 图片 | 半透黑遮罩(拉伸满屏,兼作点击关闭热区) | `MASK_DIM` | | 1821 | `CG_BOX_LEFT` | **容器** | 左上家冲关牌容器(复制的父精灵) | —— | | 1822 | `CG_BOX_RIGHT` | **容器** | 右上家冲关牌容器 | —— | | 1823 | `CG_BOX_SELF` | **容器** | 自己冲关牌容器 | —— | | 1824 | `CG_CARD_TPL` | **模板** | 冲关牌模板(编辑器里放好、默认隐藏,只作样式来源) | `CARD_FACE_L` | | 1825–1827 | `CG_COUNT_LEFT/RIGHT/SELF` | 文字 ×3 | **奖数**,与该家的冲关牌**并列显示**(取 `aset.seatlist[i].naward`;是否另列 `chongguan`/`wang` 明细见下) | —— | **生成方式**: ```js // 每个座位一个容器,tag 用牌在 cards 里的下标(父精灵内唯一即可) var pos = LayoutSolver.solve(EQW_Layout.CG_CARDS, seatKey, cards.length); // fan 布局,见 §6.7 for (var i = 0; i < cards.length; i++) { var sid = SpriteCopyUtils.create(boxId, sprites.CG_CARD_TPL, pos[i].x, pos[i].y, i); SpriteManager.setFrame(sid, cardIdToFrame(cards[i])); // §0.4 } ``` **清理是硬要求**:关闭叠加层 / 组件 `onDestroy` 时必须 `SpriteCopyUtils.removeRange(boxId, 0, cards.length)`,否则复制精灵残留(client 02 §6)。 > `RESULT_BTN_NEXT`(1817/1818)在遮罩之上仍需可见可点 —— 其图层内层级须高于 `CG_MASK`。 #### 预置 vs 精灵复制的选用判据 | 判据 | 预置固定精灵 | 精灵复制 | | --- | --- | --- | | 数量 | 有明确且不大的上限 | 上限不定或很大 | | 常态占用率 | 多数时候都在用 | 多数时候只用极少几个 | | 逐个控制 | 需要独立 z 序 / 动画 / 命名引用 | 只需批量摆位 + 设帧 | | 清理成本 | 无 | 必须显式 `remove`,忘了就残留 | 据此,本清单的选用——**全项目只有「冲关牌型」一处用复制,其余一律预置**: | 区域 | 方式 | 理由 | | --- | --- | --- | | **冲关牌型** | **复制** | 张数不定、无小上限,多数局用不满 | | 自己手牌(36) | 预置 | 上限确定;需逐张选中上浮、独立动画与点击 | | 已出牌区(28 × 3) | 预置 | 需叠压 z 序与出牌动画,逐张独立控制 | | 底牌(8)/ 埋牌底牌(8) | 预置 | 固定 8 张 | | 建房规则选项 | 预置 | 选项集固定(4 组 8 项),数量小 | > 精灵段 1001–2999 有近 2000 个空位(§0.3),预置的 ID 开销不构成压力;复制只用在「数量真的不定」的地方,避免给每处都背上显式清理的负担。 ### 5.7a Layer 303 · 大局总结算(群组 241,**玩家栏用精灵复制**) 三条玩家栏结构完全相同、仅数据不同,用 `SpriteCopyUtils` 复制生成;面板外壳与按钮预置。 | 精灵 ID | 键名 | 类型 | 用途 | 资源 / 帧 | | --- | --- | --- | --- | --- | | 1900 | `ACC_PANEL_BG` | 图片 | 面板底(圆角浅灰绿) | `PANEL_ACCOUNT` | | 1901 | `ACC_TITLE_BAR` | 图片 | 标题栏(青绿底) | `BAR_ACCOUNT_TITLE` | | 1902 | `ACC_TITLE_TEXT` | 文字 | `二七王-3人 房号… 共…局 …` | —— | | 1903 | `ACC_BTN_CLOSE` | 图片 | 右上角关闭 | `BTN_ACC_CLOSE` | | 1904 | `ACC_RULE_BAR` | 图片 | 底部规则栏(浅绿底) | `BAR_ACCOUNT_RULE` | | 1905 | `ACC_RULE_TEXT` | 文字 | `规则:可查牌、傍王、爬坡算子` | —— | | **1910** | `ACC_ROW_CONTAINER` | **容器** | 玩家栏的复制父精灵 | —— | | 1911 | `ACC_TPL_RANK` | **模板** | 名次徽章 | `BADGE_RANK`,帧 = 名次 1–3 | | 1912 | `ACC_TPL_AVATAR` | **模板** | 头像 | 运行时贴玩家头像 | | 1913 | `ACC_TPL_TEXT` | **模板** | 通用文字(昵称/ID/各项分数,一行一个) | —— | | 1914 | `ACC_TPL_SCORE_NUM` | **模板** | 右侧总分大字(数字精灵) | `NUM_RESULT_WIN` / `NUM_RESULT_LOSE` | | 1915 | `ACC_TPL_DIVIDER` | **模板** | 行间分隔线 | `LINE_DIVIDER` | | 1920–1927 | `ACC_BTN_*` | 图片×4 + 文字×4 | `再来一局` / `分享好友` / `复制战绩` / `退出房间` | `BTN_ACCOUNT_*` | **tag 分段**(`SpriteCopyUtils.createTagManager`):`rank 1–20` / `avatar 21–40` / `text 41–140`(每行 6 条文字)/ `score 141–160` / `divider 161–180`。 **清理**:关闭面板时 `removeRange` 清掉各段。 > 解散结算复用本面板(协议 §14.1,解散包也带 `account`),只是 `aset` 各项为 0。 > > **布局配置见 §6.9a**(2026-08-27 补齐;此前本 View 没有任何布局节点)。 ### 5.7b Layer 304 / 305 · 出牌历史与明牌(群组 242 / 243,**牌用精灵复制**) 两者版式相同(遮罩 + 每家一排牌,照 `冲关牌型显示.png`),差别只在数据源与家数,**共用同一套渲染**: | 精灵 ID | 键名 | 类型 | 用途 | 资源 / 帧 | | --- | --- | --- | --- | --- | | 1950 | `HIST_MASK` | 图片 | 半透黑遮罩(兼关闭热区) | `MASK_DIM` | | 1951–1953 | `HIST_BOX_LEFT/RIGHT/SELF` | **容器** ×3 | 每家一个牌容器 | —— | | 1954 | `HIST_CARD_TPL` | **模板** | 牌模板 | `CARD_FACE_L` | | 1955–1957 | `HIST_LABEL_LEFT/RIGHT/SELF` | 文字 ×3 | 座位标签(昵称 / 张数) | —— | | 2000 | `MING_MASK` | 图片 | 明牌遮罩 | `MASK_DIM` | | 2001–2002 | `MING_BOX_A/B` | **容器** ×2 | 另两家各一个容器(明牌只看他家) | —— | | 2003 | `MING_CARD_TPL` | **模板** | 牌模板 | `CARD_FACE_L` | | 2004–2005 | `MING_LABEL_A/B` | 文字 ×2 | 座位标签 | —— | - **出牌历史**:三家都显示,数据 = `pushlist` 摊平聚合到各家、按主牌序排序(§1.7 D-7)。 - **上一轮**:同一套精灵,数据只取 `pushlist` 末尾一项。 - **明牌**:只显示另两家,数据 = `mingpai.others[].zhucards`(§1.7 D-6)。 - **亮牌**:只显示一排(庄家的固定主牌),数据 = `liangpai.cards`(§1.6)。复用同一套遮罩 + 容器 + 牌模板,仅数据源与排数不同。 ⚠ 这里的「亮牌」是 design §8.2 那件事,**与 §2.5 手牌上方的主牌统计条(群组 206 `ZHU_STAT_*`)无关**——后者 2026-08-27 前也叫「亮牌条」,已改名以消歧义。 > **布局配置见 §6.9c**(2026-08-27 补齐;此前 `HIST_*` / `MING_*` 都没有布局节点)。 > > 解散投票由平台提供(Layer 420),子游戏无需精灵。 ### 5.8 建房规则选项(群组 250,2050–2099,预置) **3 个类别、4 个选项组、8 个选项**(§1.1)。选项集固定,全部预置。 | 精灵 ID | 键名 | 类型 | 用途 | 资源 / 帧 | | --- | --- | --- | --- | --- | | 2050–2052 | `ROOM_CAT_TITLE_1..3` | 图片 ×3 | 类别标题(牌局 / 模式 / 规则) | `ROOM_CAT_TITLE`,帧 = 1 / 2 / 3 | | 2053–2054 | `ROOM_BOX_ASET_1..2` | 图片 ×2 | 局数选择框(6局 / 12局) | `ROOM_OPT_BOX`,单选帧 1–2 | | 2055–2056 | `ROOM_TEXT_ASET_1..2` | 文字 ×2 | 局数选项文字 | —— | | 2057–2058 | `ROOM_BOX_CARD_1..2` | 图片 ×2 | 扣卡选择框(房主 / AA) | `ROOM_OPT_BOX`,单选帧 1–2 | | 2059–2060 | `ROOM_TEXT_CARD_1..2` | 文字 ×2 | 扣卡选项文字 | —— | | 2061 | `ROOM_TEXT_CARD_COST` | 文字 | 房卡消耗附注(灰色小字,随局数 × 扣卡联动) | —— | | 2062–2063 | `ROOM_BOX_PEEK_1..2` | 图片 ×2 | 查牌选择框(可查 / 不查) | `ROOM_OPT_BOX`,单选帧 1–2 | | 2064–2065 | `ROOM_TEXT_PEEK_1..2` | 文字 ×2 | 查牌选项文字 | —— | | 2066–2067 | `ROOM_BOX_RULE_1..2` | 图片 ×2 | 附加规则选择框(傍王 / 爬坡) | `ROOM_OPT_BOX`,**复选帧 3–4** | | 2068–2069 | `ROOM_TEXT_RULE_1..2` | 文字 ×2 | 附加规则选项文字 | —— | 共 20 个精灵。挂在平台创建房间界面所在图层(T-3)。布局配置见 §6.9b。 > 精灵虽预置,**渲染仍由 §1.1 的配置数据驱动**:配置里每个选项声明自己用哪个精灵键、`type`(决定选择框帧号)、`bit`/`value`(决定 `roomtype` 拼串)。渲染代码不认识「傍王」「爬坡」这些具体玩法名。 --- ## 6. 布局配置规范与配置清单 > **硬要求:所有布局参数一律外提为配置,业务代码零裸值。** > 位置、大小、排列方式、间距、对齐、朝向、缩放、文字样式——凡是「摆在哪、多大、怎么排」的参数, > 全部写进布局配置文件,代码只读配置。这既是工程总则 §6「配置优先于硬编码」,也是 client 02 §2 > 「布局文件是纯数据:无函数、无副作用」的要求。 > > ⚠️ 本节所有数值均为参考图(1280×720)目视实测估值,误差约 ±5px,**是给美术和编辑器的起点、不是最终值**。 > 设计稿到位后必须整体复核。**但布局器类型(`kind`)与参数结构是设计决定,不随数值变动。** ### 6.0 与框架 `AlignmentUtils` 的对齐 参数命名**直接采用框架 `gameabc-framework/ui/AlignmentUtils.js` 的入参名**,配置对象可原样喂进框架方法,中间零转换: | 配置的 `kind` | 落到框架的方法 | | --- | --- | | `line` | `AlignmentUtils.distribute({direction, anchorX, anchorY, count, itemWidth, itemHeight, spacing, anchor})` | | `fan` | 先算出 `spacing`,再走同一个 `AlignmentUtils.distribute` | | `attach`(`corner` 为空) | `AlignmentUtils.alignSpriteToSprite(targetId, spriteId, hAlign, vAlign, offset)` | | `attach`(`corner` 有值) | `AlignmentUtils.alignSpriteCornerTopLeft/TopRight/BottomLeft/BottomRight(targetId, spriteId, offset)` | | `grid` | 逐行调 `distributeHorizontally` | | `point` | 直接 `SpriteManager.setPosition` | 对齐常量沿用框架的 `AlignmentUtils.ALIGN`:水平 `left/center/right`、垂直 `top/middle/bottom`。 > 精灵锚点**恒在左上角、不可更改**(AlignmentUtils 文件头注释),所有对齐都是基于锚点的换算。配置里写的 `anchorX/anchorY` 是**对齐基准点**,不是精灵左上角坐标。 ### 6.1 五种布局器(`kind`) 每个部件的配置必须声明 `kind`,`kind` 决定它合法的参数集。 #### `point` — 定点 单个元素放在固定位置。 ```js { kind: 'point', x: 486, y: 4, w: 276, h: 68 } // 可选:scale(倍数,业务单位)、opacity(0.0–1.0) ``` #### `line` — 等距排列 数量可变但**间距固定**的一排(底牌 8 张、埋牌底牌 8 张)。 ```js { kind: 'line', direction: 'horizontal', anchorX: 655, anchorY: 110, // 对齐基准点 itemWidth: 110, itemHeight: 190, spacing: -36, // 负值 = 重叠 anchor: 'center' } // left | center | right ``` `count` 由运行时数据给出,不进配置。 **逐项不等宽**:给出可选的 `items` 时逐项取宽,`itemWidth` 失效——用于底栏功能钮组(74/87/93/78)与埋牌操作条(180/60/180)这类宽度不一的排: ```js { kind: 'line', direction: 'horizontal', anchorX: 1250, anchorY: 672, itemHeight: 30, spacing: 15, anchor: 'right', items: [ {key:'BTN_MINGPAI', width:74}, {key:'BTN_HISTORY', width:87}, {key:'BTN_LAST_ROUND', width:93}, {key:'BTN_BOTTOM_CARDS', width:78} ] } ``` #### `fan` — 自适应压缩排 张数可变、**总宽受限**、需要动态压缩间距的牌排(手牌、出牌区、冲关牌)。 ```js { kind: 'fan', direction: 'horizontal', anchorX: 640, anchorY: 455, maxWidth: 1215, // 可用总宽上限 itemWidth: 110, itemHeight: 190, spacingMax: 0, // 张数少时的最大间距(0 = 紧贴不重叠) spacingMin: -84, // 张数多时的最大重叠量(负值) anchor: 'center', overlapFrom: 'left' } // left = 右边的牌压在左边的牌之上 ``` 间距算法(**唯一实现,不散落**): ``` count <= 1 → spacing 不参与,直接按 anchor 摆一张 otherwise → raw = (maxWidth - itemWidth) / (count - 1) - itemWidth spacing = clamp(raw, spacingMin, spacingMax) ``` `overlapFrom` 决定 z 序方向:`'left'` 时后面的牌盖住前面的(自己手牌、右上家);`'right'` 时反过来(左上家镜像用)。 #### `grid` — 网格 固定行列的按钮阵(叫分 14 档 = 7×2)。 ```js { kind: 'grid', anchorX: 640, anchorY: 160, cols: 7, rows: 2, itemWidth: 74, itemHeight: 56, spacingX: 7, spacingY: 24, anchor: 'center', fillOrder: 'row' } // row = 先填满一行再换行 ``` #### `attach` — 相对贴附 贴在另一个精灵上的从属元素(角标贴按钮、庄标贴头像、气泡贴头像)。 ```js { kind: 'attach', target: 'P_LEFT_AVATAR', // 目标精灵的键名,不写 ID hAlign: 'right', vAlign: 'top', offsetX: 8, offsetY: -4, corner: 'topRight' } // 可选;有值 = 贴到目标【外侧】角,无值 = 在目标【内部】对齐 ``` > `target` 一律写**键名**而非数字 ID——ID 回填时只改一处映射表,配置文件不用动。 ### 6.2 座位变体 `bySeat` 三个显示位上的同名部件共用一份配置,差异写在 `bySeat` 里;未列出的字段继承外层 base。 ```js PLAY_AREA: { kind: 'fan', direction: 'horizontal', // ← base:三家共同的部分 itemWidth: 90, itemHeight: 155, // (牌尺寸、压缩规则三家一致) maxWidth: 170, spacingMax: -35, spacingMin: -62, bySeat: { SELF: { anchorX: 650, anchorY: 265, anchor: 'center', overlapFrom: 'left' }, RIGHT: { anchorX: 1022, anchorY: 120, anchor: 'center', overlapFrom: 'left' }, LEFT: { anchorX: 258, anchorY: 120, anchor: 'center', overlapFrom: 'right' } // 与 RIGHT 左右镜像 } } ``` 座位键固定为 `SELF` / `LEFT` / `RIGHT`(显示位,不是服务端座位号)。服务端座位 → 显示位的换算见 §0.1,**只在一处实现**。 ### 6.3 文字样式 `textStyle` 文字精灵的样式也进配置,不在代码里写字号颜色。 ```js { fontSize: 24, color: '#FFFFFF', align: 'center', maxWidth: 130, bold: false } ``` ### 6.4 牌尺寸档 三套牌面尺寸集中定义,各区域**引用档名**,不重复写数字。 ```js CARD_SIZE: { L: { w: 110, h: 190, res: 'CARD_FACE_L' }, // 手牌、底牌、冲关牌 M: { w: 90, h: 155, res: 'CARD_FACE_M' }, // 已出牌区 S: { w: 50, h: 70, res: 'CARD_FACE_S' } // 结算埋牌底牌 } ``` 改牌面尺寸只动这一处,所有引用它的区域自动跟随。 --- ### 6.5 常驻区配置 | 部件 | kind | 关键参数(实测估值) | | --- | --- | --- | | 顶部信息条底 | `point` | `x:486 y:4 w:276 h:68` | | 顶部 · 花色图标 | `attach` | `target:TOP_INFO_BG, hAlign:left, vAlign:bottom, offsetX:20, offsetY:-8` | | 顶部 · 叫分角标 | `attach` | `target:TOP_CALL_TEXT, corner:topRight, offsetX:4, offsetY:6` | | 顶部 · 抓分角标 | `attach` | `target:TOP_GRADE_TEXT, corner:topRight, offsetX:4, offsetY:6` | | 主牌统计条(节点 `ZHU_STAT_BG`) | `point` | `x:35 y:400 w:210 h:35` | | 底栏条 | `point` | `x:0 y:648 w:1280 h:72` | | 底栏 · 局数文字 | `point` | `x:635 y:672 w:65 h:26`,`textStyle{fontSize:20, align:center}` | | 底栏 · 功能钮组(明牌/已出牌/上一轮/**扣底**/底牌) | `line` + `items` | `direction:horizontal, anchorX:1250, anchorY:672, itemHeight:30, spacing:15, anchor:right`,`items:[74,87,93,78†,78]`(逐项不等宽,见 §6.1)。†`扣底` 的 78 是估值(2026-08-27 补项,本表原只列 4 项)——参考图(等待叫分.png)底栏当时也只画出 `底牌` 一项;依据取自 `底牌` 实测的 78,不能笼统说「两字按钮 = 78」:同为两字的 `明牌` 实测只有 74,两者不同,待设计稿复核 | | 底栏 · 庄标 | `point` | `x:338 y:668 w:32 h:34` | | 底栏 · 提示按钮组(踩/有分/没分) | `line` | `direction:horizontal, anchorX:335, anchorY:672, itemWidth:52, itemHeight:30, spacing:26, anchor:left` | | 右侧语音钮(平台) | `point` | `x:1210 y:310 w:56 h:56` | | 右侧聊天钮(平台) | `point` | `x:1210 y:385 w:56 h:56` | ### 6.6 玩家位配置(含气泡朝向) 平台负责头像/昵称/分数,子游戏的附加标记全部 `attach` 到平台头像框上——这样平台布局一调,标记自动跟随。 | 部件 | kind | 参数 | | --- | --- | --- | | 头像框(平台) | `point` | `bySeat{ LEFT:{x:22,y:122}, RIGHT:{x:1160,y:122}, SELF:{x:22,y:645} }`,`w:91 h:118`(SELF 为 84×53)。**需同步写进 `Game_Modify.PLAYER_INFO_LAYOUT`**(§6.11) | | `庄` 印章 | `attach` | `bySeat{ LEFT:{corner:'topRight',offsetX:7,offsetY:8}, RIGHT:{corner:'topLeft',offsetX:-40,offsetY:8}, SELF:{point x:338 y:668} }` | | `主N` 角标 | `attach` | `target:头像框, corner:bottomLeft, offsetX:0, offsetY:6, w:40 h:19` | | `对N` 角标 | `attach` | `target:主N角标, hAlign:right, vAlign:middle, offsetX:44, offsetY:0` | | 状态文字(`60分`/`不叫`) | `point` | `bySeat{ LEFT:{x:170,y:165}, RIGHT:{x:980,y:165} }`,`w:130 h:70`,`textStyle{fontSize:46, align:center}` | | 倒计时(多帧图数字精灵,2026-08-27 订正) | `point` | `bySeat{ LEFT:{x:195,y:165}, RIGHT:{x:1010,y:165}, SELF:{x:445,y:345} }`,`h:NUM_STYLE.COUNTDOWN.charHeight(60)`;`w` 运行时注入(1–2 位数字,`setNumberImage` 按字符数 × `charWidth` 自动改宽) | **提示气泡的朝向与位置**(资源 `BUBBLE_TISHI` 6 帧,§3.7): | 气泡属于 | 头像在气泡的 | 用帧 | 贴附方式 | | --- | --- | --- | --- | | 左上家 | **左**侧 | 箭头向左(帧 1–3) | `attach{ target:LEFT头像框, hAlign:right, vAlign:middle, offsetX:+10 }` | | 右上家 | **右**侧 | 箭头向右(帧 4–6) | `attach{ target:RIGHT头像框, hAlign:left, vAlign:middle, offsetX:-10 }`(气泡向左展开) | | 自己 | **左**侧 | 箭头向左(帧 1–3) | `attach{ target:SELF头像, hAlign:right, vAlign:top, offsetX:+10, offsetY:-70 }` | 帧号计算:`frame = (arrowRight ? 3 : 0) + tip`(`tip`:1踩 / 2没分 / 3有分)。 朝向本身也应是**配置项**(`arrowRight: true|false`)而非代码里按座位硬判。 > 自己发的提示,本人**看不到自己的气泡**(协议 §13.7 不回执,且只转发给对家)。表格里列出 SELF 的气泡配置,是为了「另一闲家看到我的气泡」时——那时在**对方屏幕上**我处于 LEFT 或 RIGHT 位。SELF 位的气泡实际不会出现【待确认 T-26:是否需要给自己一个本地回显】。 ### 6.7 牌区配置 | 区域 | kind | 参数(实测估值) | | --- | --- | --- | | **自己手牌 · 单排** | `fan` | `anchorX:640 anchorY:455 maxWidth:1215 size:L spacingMax:0 spacingMin:-78 anchor:center overlapFrom:left`。36 张时算出 `spacing = 1215−110)/35 − 110 ≈ −78`,即每张露 32px,与参考图实测 33px 吻合 | | **自己手牌 · 双排(埋牌)** | `fan` ×2 | 上排 `anchorX:644 anchorY:285 maxWidth:928 size:L spacingMin:-65`;下排 `anchorX:644 anchorY:455 maxWidth:928 size:L spacingMin:-65`。**分行策略:上排 `floor(n/2)`、下排 `ceil(n/2)`**(36 张 = 18/18)。参考图的 17/19 属填充画法、不作依据(§0.5) | | 手牌 · 选中上浮 | —— | `selectedOffsetY: -40`(配置项,不写死) | | **底牌区** | `line` | `anchorX:655 anchorY:110 size:L spacing:-36 anchor:center`(8 张) | | **埋牌底牌区(结算)** | `line` | `anchorX:640 anchorY:163 size:S spacing:2 anchor:center`(8 张) | | **已出牌区** | `fan` | 见 §6.2 的 `bySeat` 示例(三家共用 base,LEFT 镜像) | | 已出牌 · 牌型标签 | `attach` | `target:该区首张牌, corner:bottomLeft, offsetX:0, offsetY:-24` | | 已出牌 · 牌角 `庄`/`大` 标 | `attach` | `target:对应牌, corner:topRight, offsetX:-28, offsetY:0` | | **冲关牌(叠加层)** | `fan` | `size:L spacingMax:-40 spacingMin:-88 maxWidth:420`;`bySeat{ LEFT:{anchorX:260,anchorY:120}, RIGHT:{anchorX:1035,anchorY:120}, SELF:{anchorX:640,anchorY:400} }`。**精灵复制**(§5.6a):求解结果直接作为 `SpriteCopyUtils.create` 的 `x/y`(相对容器)。`spacingMin` 放宽到 `-88`(即最窄只露 22px)以容纳 20+ 张的极端情况 | ### 6.8 阶段操作区配置 > **2026-08-26 修订(其一)**:选主面板花色图标、对数角标底已并入按钮帧(见 §1.5/§3.3),对应布局节点删除。 > **2026-08-26 修订(其二)**:数字统一走 `SpriteManager.setNumberImage`(§3.6)后,一个多位数只占一个精灵——花色张数的「十位 / 个位」两个节点**合并为一个** `MAIN_SUIT_COUNT`。 > 两个数字节点的 `h` 恒为单字高,直接引用 `EQW_Layout.NUM_STYLE.*.charHeight`(SSOT:数值只在 `LayoutConstants.js` 定义一处,布局文件不再手抄);`w` 则**留到运行时注入**——`setNumberImage` 会按「字符数 × `charWidth`」自动改精灵宽,位数变宽度就变。 | 部件 | kind | 参数(实测估值) | | --- | --- | --- | | 叫分 · 面板底 | `point` | `x:355 y:140 w:575 h:170` | | 叫分 · 提示条 | `point` | `x:470 y:100 w:345 h:33` | | **叫分 · 14 档按钮** | `grid` | `anchorX:640 anchorY:160 cols:7 rows:2 itemWidth:74 itemHeight:56 spacingX:7 spacingY:24 anchor:center fillOrder:row` | | 叫分 · 档位分数数字 | `attach` | `target:对应档位按钮, hAlign:center, vAlign:middle, offsetY:4, h:NUM_STYLE.CALL_SCORE.charHeight(40)`;`w` 运行时注入(= 字符数 × 30,由 `setNumberImage` 自动设) | | 叫分 · 档位子数角标 | `attach` | `target:对应档位按钮, corner:topRight, offsetX:-30, offsetY:2` | | 叫分 · `不叫` 按钮 | `point` | `x:553 y:345 w:184 h:57` | | 叫分 · 自己叫分大字 | `point` | `x:570 y:320 w:140 h:70`,`textStyle{fontSize:52, align:center}` | | 选主 · 标题斜角条 | `point` | `x:330 y:315 w:425 h:50` | | **选主 · 4 花色 + 投降** | `line` | `direction:horizontal anchorX:330 anchorY:373 itemWidth:128 itemHeight:55 spacing:9 anchor:left`(共 5 项;投降不显示时只排 4 项,`anchor:left` 保证前 4 个位置不变) | | 选主 · 张数 | `attach` | `target:对应花色按钮, hAlign:center, vAlign:bottom, offsetY:-6, h:NUM_STYLE.MAIN_SUIT_COUNT.charHeight(32)`;`w` 运行时注入(`9张`=2 字符、`26张`=3 字符,宽度随之变)。一个花色一个精灵 | | 选主 · 对数角标文字 | `attach` | `target:对应花色按钮, corner:topRight, offsetX:-36, offsetY:2`(按钮帧内已含角标底,本节点只定位叠加文字) | | 埋牌 · 操作条 | `line` + `items` | `direction:horizontal anchorX:640 anchorY:165 itemHeight:63 spacing:45 anchor:center`,`items:[180,60,180]`(提示 / 倒计时 / 埋牌) | | 出牌 · 操作条 | `line` + `items` | `direction:horizontal anchorX:446 anchorY:345 itemHeight:55 spacing:35 anchor:left`,`items:[75,157,180‡]`(倒计时 / 出牌 / **提示**)。‡`提示` 的 180 是估值(2026-08-27 补项,本表原漏了 §1.7 明确要求的 `提示` 按钮)——与埋牌条的 `提示` 同资源 `BTN_TIP`(§3.3 实测 180×63),埋牌条 `items` 首项亦取 180,故沿用;顺序照 §1.7 行文。⚠ 本条 `itemHeight:55` 对齐的是 `BTN_PLAY`(157×55),而 `BTN_TIP` 原生高 63,高度差待设计稿裁定。**2026-08-27 再订正**:补「提示」项后若沿用 `anchor:center`,整排重新居中会把已有的倒计时/出牌两项相对原先(仅 2 项时)左移 107.5px,导致「出牌」按钮偏离参考图 `出牌.png` 的实测位置(556–711)。改为 `anchor:left, anchorX:446`(= 参考图出牌按钮实测起点 556 − 倒计时宽 75 − 间距 35),求解结果:倒计时 x=446、出牌 x=556(与实测吻合)、提示 x=748 | | 捡分飘字 | `point` | `x:570 y:180 w:170 h:55`;另配 `floatRise:-60`、`floatDuration:800`(动画参数也进配置) | | 状态提示条 · 等待类 | `point` | `x:500 y:373 w:320 h:47`(§2.8) | | 状态提示条 · 即时反馈类 | `point` | `x:462 y:172 w:363 h:50`;另配 `toastDuration:1500` | **多帧图数字样式配置 `EQW_Layout.NUM_STYLE`**(2026-08-26 新增,在 `LayoutConstants.js`,与 `CARD_SIZE`/`TEXT_STYLE` 并列)——`setNumberImage` 所需数值的**唯一出处**,业务代码不许内联: | 键 | `res`(资源键名) | `charWidth` | `charHeight` | `suffix`(第16帧后缀) | 典型显示 | | --- | --- | --- | --- | --- | --- | | `COUNTDOWN` | `NUM_COUNTDOWN` | 44 | 60 | `''`(无) | `15` | | `RESULT_WIN` | `NUM_RESULT_WIN` | 46 | 62 | `''`(第16帧待确认) | `+120` | | `RESULT_LOSE` | `NUM_RESULT_LOSE` | 46 | 62 | `''`(同上) | `-120` | | `CALL_SCORE` | `NUM_CALL_SCORE` | 30 | 40 | `''`(第16帧待确认) | `70` | | `MAIN_SUIT_COUNT` | `NUM_MAIN_SUIT_COUNT` | 24 | 32 | `张` | `26张` | > - `charWidth` 是 `setNumberImage` 的入参,**精灵宽度 = 字符数 × `charWidth`**(该接口自动设),所以布局节点的 `w` 是运行时值、不写死在配置里。 > - `charHeight` 供布局节点定 `h`(`setNumberImage` 不改高度)。 > - `suffix` 只能是 `''` / `分` / `倍` / `张`——接口内部把这三个字统一编码到第 16 帧,写别的字不会被识别。 > - `res` 存的是**资源键名**而非 ID(与 `CARD_SIZE.res` 同一约定):布局文件是纯数据、不引用其他模块,调用方用 `EQW_Images[style.res]` 取真实 ID。 ### 6.9 结算区配置 | 部件 | kind | 参数(实测估值) | | --- | --- | --- | | 底牌面板底 | `point` | `x:430 y:140 w:420 h:148` | | 底牌说明文字 | `attach` | `target:底牌面板底, hAlign:center, vAlign:bottom, offsetY:-14` | | **三家分数光晕** | `point` | `bySeat{ LEFT:{x:175,y:150}, RIGHT:{x:865,y:150}, SELF:{x:490,y:340} }`,`w:225 h:100` | | 三家总分大字(多帧图数字精灵,2026-08-27 由文字精灵改,见 §1.9/§5.6) | `attach` | `target:对应光晕, hAlign:center, vAlign:middle`,`h:NUM_STYLE.RESULT_WIN.charHeight(62)`(与 `RESULT_LOSE` 相同);`w` 运行时注入 | | 三家 `牌局分` | `attach` | `target:对应光晕, hAlign:left, vAlign:bottom, offsetX:-15, offsetY:24` | | 三家 `冲关分` | `attach` | `target:对应光晕, hAlign:right, vAlign:bottom, offsetX:15, offsetY:24` | | `冲关牌型` 按钮 | `point` | `x:535 y:515 w:183 h:63` | | `下一局` 按钮 | `point` | `x:835 y:518 w:180 h:65` | | 冲关遮罩 | `point` | `x:0 y:0 w:1280 h:720`,`opacity:0.72`(透明度也是配置项) | #### 6.9a 大局总结算配置(§1.9 / §5.7a,2026-08-27 补齐) > 本表原先**整块缺失**——`Layout_Result.js` 里一个 `ACC_*` 节点都没有,实现时极易在渲染代码里就地硬编码坐标(违反 §6 零裸值)。 > **坐标全部是 `uiref/大局结算.png` 的目视实测估值(±5px)**,`w`/`h` 取 §3.5 / §3.3 的资源尺寸(那是权威值)。**待设计稿复核**。 > **玩家栏是精灵复制**(§5.7a),故不逐行写死,而是给「容器 + 行节点(行高即 `itemHeight`)+ 行内模板贴附到行矩形」;行数由数据给出(`ctx.count`),不进配置。 > ⚠ `ACC_ROW` 及贴附其上的节点求出的坐标都是**相对 `ACC_ROW_CONTAINER` 的**——复制精灵是容器的子精灵,`x/y` 本就相对容器,求解结果可直接喂 `SpriteCopyUtils.create`(与 §5.6a 冲关牌同一用法)。 | 部件 | 节点名 | kind | 参数(目视实测估值) | | --- | --- | --- | --- | | 面板底 | `ACC_PANEL_BG` | `point` | `x:80 y:88 w:1100 h:540` | | 标题栏 | `ACC_TITLE_BAR` | `attach` | `target:ACC_PANEL_BG, corner:topLeft, offset(0,0), w:1100 h:56` | | 标题文字 | `ACC_TITLE_TEXT` | `attach` | `target:ACC_TITLE_BAR, hAlign:left, vAlign:middle, offsetX:30`;`w/h` 运行时注入(文案长度不定) | | 关闭按钮 | `ACC_BTN_CLOSE` | `attach` | `target:ACC_TITLE_BAR, corner:topRight, offset(-20,10), w:36 h:36` | | 规则栏 | `ACC_RULE_BAR` | `attach` | `target:ACC_PANEL_BG, corner:bottomLeft, offset(0,0), w:1100 h:62` | | 规则文字 | `ACC_RULE_TEXT` | `attach` | `target:ACC_RULE_BAR, hAlign:left, vAlign:middle, offsetX:30`;`w/h` 运行时注入 | | 玩家栏**容器** | `ACC_ROW_CONTAINER` | `point` | `x:80 y:144 w:1100 h:422`(标题栏下沿 → 规则栏上沿) | | 玩家栏**行** | `ACC_ROW` | `line` | `direction:vertical, anchorX:0, anchorY:14, itemWidth:1100, **itemHeight:132(行高)**, spacing:0, anchor:top`(相对容器;行数 = `ctx.count`) | | 行内 · 名次徽章 | `ACC_TPL_RANK` | `attach` | `target:ACC_ROW, hAlign:left, vAlign:middle, offsetX:28, w:44 h:44` | | 行内 · 头像 | `ACC_TPL_AVATAR` | `attach` | `target:ACC_ROW, hAlign:left, vAlign:middle, offsetX:92, w:80 h:80` | | 行内 · 文字栅格(6 条文字的槽位) | `ACC_ROW_TEXT_GRID` | `grid` | `anchorX:192, cols:4, rows:2, itemWidth:176, itemHeight:34, spacingX:0, spacingY:10, anchor:left, fillOrder:row`;`anchorY` 运行时注入(随所在行浮动)。列序:昵称/`ID:` · 基础分/总得分 · 冲关分 · 傍王分(第 2 行仅前两列有文字)。**估值精度**:本节点是等距列模型(相对容器 192/368/544/720),参考图目视实测列 x 约 272/455/634/800(列距 183/179/166,并不等距),偏差约 10px,比本节头部通用的「±5px」更大,待设计稿复核 | | 行内 · 右侧总分大字 | `ACC_TPL_SCORE_NUM` | `attach` | `target:ACC_ROW, hAlign:right, vAlign:middle, offsetX:-25, h:NUM_STYLE.RESULT_WIN.charHeight(62)`;`w` 运行时注入 | | 行内 · 分隔线 | `ACC_TPL_DIVIDER` | `attach` | `target:ACC_ROW, hAlign:center, vAlign:bottom, offset(0,0), w:1060 h:2`(`vAlign:bottom` 的语义是"目标底边之下",见 §6.0) | | 面板**外**底部 4 按钮 | `ACC_FUNC_BUTTONS` | `line` | `direction:horizontal, anchorX:345, anchorY:640, itemWidth:190, itemHeight:60, spacing:25, anchor:left`(参考图里这排不居中于屏幕、整体偏右,故用 `anchor:left` 定起点) | #### 6.9b 建房规则选项配置(§1.1) 布局是**两层嵌套**:类别竖排,类别内的选项组横排。 | 部件 | kind | 参数(按参考图比例估,实际以平台建房界面尺寸为准) | | --- | --- | --- | | 类别(竖排) | `line` | `direction:vertical, anchorX:0, anchorY:0, itemHeight:32, spacing:8, anchor:left`;`itemHeight` 为**单行**类别高,折行类别按实际行数累加 | | 类别标题 | `attach` | `target:所在类别行, hAlign:left, vAlign:middle, offsetX:0`;`ROOM_CAT_TITLE` 帧 = 类别序号 | | 类别内选项槽(横排) | `line` | `direction:horizontal, anchorX:70, itemWidth:150, spacing:0, anchor:left`(`itemWidth` = 一个「选择框 + 文字」的槽宽) | | 选项组之间的额外间隔 | —— | `groupGap: 40`(同类别下两个选项组之间比组内选项多留的间距,配置项) | | 选择框 | `attach` | `target:所在选项槽, hAlign:left, vAlign:middle` | | 选项文字 | `attach` | `target:同槽的选择框, hAlign:right, vAlign:middle, offsetX:8` | | 房卡附注 | `attach` | `target:扣卡组最后一个选项, hAlign:right, vAlign:middle, offsetX:20`,`textStyle{fontSize:16, color:'#999999'}` | | 折行 | `grid` | 选项数超过 `maxPerRow` 时改用 `grid{cols:maxPerRow, spacingX:0, spacingY:8, fillOrder:'row'}`;`maxPerRow: 4`(配置项,参考图「玩法」行即 4 列折 2 行)。二七王每组最多 2 项,不会触发 | **文字选中态也是配置**(单选选中时文字变橙): ```js ROOM_OPT_TEXT: { normal: { fontSize: 18, color: '#DDDDDD' }, selected: { fontSize: 18, color: '#E8873A' } }, ROOM_CAT_TITLE_STYLE: { fontSize: 18, color: '#7FBFB0', bold: true, align: 'right', maxWidth: 60 } ``` 不要在代码里写死 `'#E8873A'`。 #### 6.9c 出牌历史 / 亮牌 · 明牌配置(§5.7b,2026-08-27 补齐) > 本表同样原先**整块缺失**(`HIST_*` / `MING_*` 一个节点都没有)。 > 两者与冲关牌型叠加层是**同一种版式**(半透黑遮罩 + 每家一排重叠大牌,参考图同为 `uiref/冲关牌型显示.png`),故照 §6.7 的 `CHONGGUAN_FAN` 同构给出,三家锚点沿用冲关牌的 `LEFT 260,120 / RIGHT 1035,120 / SELF 640,400`。 > **与冲关牌的唯一差别是张数上限**:出牌历史摊平后可达 28 张以上,`maxWidth:420` 装不下。 > **2026-08-27 订正**:原估值 `maxWidth:500` 的推导算错了——`fan` 的间距公式是 `spacing = max(spacingMin, min(spacingMax, (maxWidth-itemWidth)/(count-1) - itemWidth))`,代入 `maxWidth 500 / itemWidth 110 / count 28` 得 `(500-110)/27-110 ≈ -95.56`,**没有**触到 `spacingMin:-96` 的下限;而 `fan` 的总宽恒等于 `maxWidth`(只要 spacing 未被下限截断),并不是旧注释算的「488」。`RIGHT` 座位锚点 `anchorX:1035` 配合总宽 500、`anchor:center`,整排右边缘落在 `1035+250=1285`,**超出 1280 设计画布 5px**(`LEFT`/`SELF` 因锚点更靠左未暴露)。 > **2026-08-27 再订正(裁定 maxWidth:480)**:曾先收窄到 `maxWidth:490` 让 `RIGHT` 恰好贴住 1280,但零余量太脆弱(这些锚点本身是参考图目视实测、±5px 误差),且 490 时 `spacingMin:-96` 永远不触底、是个死参数,与「每张最窄只露 14px」这条约束名存实亡;旧注释原写的「每张露 14px、总宽 488」正是 `spacingMin` 触底后的结果,说明 488 才是当初想要的值,`maxWidth:500` 是与注释不符的笔误。改为 `maxWidth:480`:代入公式 `(480-110)/27-110 ≈ -96.30`,**触及** `spacingMin:-96` 下限,实际 spacing 截断为 `-96`(不再等于 raw),总宽 `= 28×110 + 27×(-96) = 488`(不再等于 `maxWidth`,因为 spacing 已被下限截断)。`spacingMin:-96` 就此真正生效、不再是死参数;每张露 `110-96=14px`,与旧注释精确一致。求解验证(count=28):`LEFT` 首尾 x = `16 → 504`,`RIGHT` 首尾 x = `791 → 1279`,`SELF` 首尾 x = `396 → 884`,均落在 0–1280 内且有余量(`RIGHT` 距边界 1px、`LEFT`/`SELF` 余量更大)。`maxWidth:480` 与 `spacingMin:-96` 仍是**估值,待设计稿复核**。 > **已知限制**:每张仅露 14px、视觉偏窄,待设计稿复核是否改为分两排显示,或改用 `CARD_SIZE.M`——这是估值阶段接受的限制,留给后续复核。 > 牌用精灵复制:三个容器精灵摆在原点、尺寸铺满屏,故 `fan` 求出的屏幕坐标可直接当容器内坐标用。 | 部件 | 节点名 | kind | 参数 | | --- | --- | --- | --- | | 出牌历史 · 遮罩 | `HIST_MASK` | `point` | `x:0 y:0 w:1280 h:720, opacity:0.72`(【亮牌】用途下**不注册点击**,见 §1.6) | | 出牌历史 · 三个牌容器 | `HIST_BOX` | `point` | `x:0 y:0 w:1280 h:720`(同摆原点、铺满屏) | | 出牌历史 · 每家一排牌 | `HIST_FAN` | `fan` | `size:L, anchor:center, maxWidth:480, spacingMax:-40, spacingMin:-96`;`bySeat{ LEFT:{260,120}, RIGHT:{1035,120}, SELF:{640,400} }`。求解验证(count=28,spacing 触底 -96、总宽 488):`LEFT` 首尾 x = `16 → 504`,`RIGHT` 首尾 x = `791 → 1279`,`SELF` 首尾 x = `396 → 884`,均落在 0–1280 内且有余量 | | 出牌历史 · 座位标签 | `HIST_LABEL` | `point` | `bySeat{ LEFT:{200,318}, RIGHT:{975,318}, SELF:{580,598} }`;`w/h` 运行时注入 | | 明牌 · 遮罩 | `MING_MASK` | `point` | 同 `HIST_MASK` | | 明牌 · 两个牌容器 | `MING_BOX` | `point` | 同 `HIST_BOX` | | 明牌 · 每家一排牌 | `MING_FAN` | `fan` | 同 `HIST_FAN`,但 **`bySeat` 只有 LEFT / RIGHT**(明牌只看他家,没有自己那排;精灵 `MING_BOX_A` 落 LEFT 位、`MING_BOX_B` 落 RIGHT 位) | | 明牌 · 座位标签 | `MING_LABEL` | `point` | `bySeat{ LEFT:{200,318}, RIGHT:{975,318} }`;`w/h` 运行时注入 | ### 6.10 配置文件组织 按 client 02 §2 的三件套拆分,布局部分建议再按界面拆文件,用保护性声明合并: ``` codes/config/ LayoutConstants.js // var EQW_Layout = EQW_Layout || {}; —— 总则:CARD_SIZE、textStyle 预设、座位键 Layout_Table.js // 常驻区(§6.5)+ 玩家位(§6.6) Layout_Cards.js // 牌区(§6.7) Layout_Action.js // 阶段操作区(§6.8) Layout_Result.js // 结算区(§6.9) Layout_CreateRoom.js // 建房规则选项(§6.9b) ImageResources.js // 图片资源 ID(§3) SoundResources.js // 声音资源 ID(§4) Sprites_*.js // 精灵结构(§5) ``` 每个文件顶部 `var EQW_Layout = EQW_Layout || {};`,加载后合并为一份(client 02 §2「多文件用保护性声明」)。 **布局文件是纯数据**:不得出现函数、不得引用其他模块、不得有副作用。`fan` 的间距算法、`bySeat` 的合并、`attach` 的换算都属于**布局求解器**的职责,放在 `codes/ui/LayoutSolver.js`(或直接调 `AlignmentUtils`),不写进配置文件。 ### 6.11 与平台布局配置的衔接 平台的玩家信息与气泡布局在 `01_SubGame_modify.js` 的配置区(该文件**只允许在配置区填值**): - `Game_Modify.PLAYER_INFO_LAYOUT.POSITIONS` —— 平台默认 ind0 南 `(6,592)`、ind1 东 `(1186,85)`、ind3 西 `(7,59)`,与 §6.6 实测值不一致,**须改为 §6.6 的值**。 - `Game_Modify.BUBBLE_LAYOUT.POSITIONS` —— 平台聊天/语音气泡锚点,同样按三人局位置调整。 > 这两处是**平台契约配置**,与子游戏自己的 `EQW_Layout` 是两份东西,但**坐标必须一致**(否则平台画的头像和子游戏贴的庄标会错位)。建议:`EQW_Layout` 里的头像框坐标为权威,`01_SubGame_modify.js` 配置区手工抄写并在注释里注明「与 EQW_Layout.PLAYER.AVATAR 保持一致」【T-28:转发壳配置区不能引用 codes,只能手工同步,需在验收清单里加一条一致性检查】。 --- ## 7. 待补充清单 > **状态汇总**(2026-08-26):待确认规格 34 项**只剩 T-20 音效**(已明确后补);设计稿只剩 **D-8**(判定结果**动画**)。 > **§7.5 服务端待补项 8 条全部处理完毕**。**服务端侧无遗留**——D-8 只差动画设计稿与触发细节。 ### 7.1 待出设计稿的界面 **只剩 D-8 一项**(已定稿的保留在表内备查)。 | 编号 | 界面 | 状态 | | --- | --- | --- | | **D-8** | **判定结果动画**(大光 / 小光 / 过庄 / 升N级 / 投降) | ⬜ 已定形态:**不做结算面板上的静态文字,改为动画**,在**对局过程中**与**结算前**播放(§1.8)。缺动画设计稿与触发细节 | | ~~D-1~~ | 建房规则选项区 | ✅ 定稿:版式照 `创建房间选项…png`,内容按 §1.1 的 3 类别 / 4 选项组。**子游戏只做「类别标签 + 选项」内容区**,弹窗背景 / 标题 / 关闭 / 确认按钮均由平台提供 | | ~~D-2~~ | 底牌 3 秒展示 | ✅ 定稿:**复用庄家翻开底牌那套 UI**(`叫分确定庄.png`),**不要倒计时**。非 70 分只有庄家能看、70 分全场能看——同一套界面,只差可见范围 | | ~~D-3~~ | 甩错提示 | ✅ 定稿:桌面中央深色半透条 **`强甩失败`**(`强甩失败提示.png`),**不做收回动画**,直接落下那张被强制打出的最小主牌 | | ~~D-4~~ | 报无主 | ✅ 定稿:**不做全场提示**。只让他家 `主N`/`对N` 角标出现;自己的主牌统计条本来就常显(§2.5) | | ~~D-5~~ | 踩 / 没分 / 有分 | ✅ 定稿:底栏三按钮(占庄标区)+ 3×2 六帧气泡 | | ~~D-6~~ | 明牌面板 | ✅ 定稿:版式照 `冲关牌型显示.png`(遮罩 + 每家一排牌)。按钮条件**按 design 手册**:可查牌 + 已有任一玩家报无主 → 三家都显示、可随时查看 | | ~~D-7~~ | 出牌历史面板 | ✅ 定稿:版式同上。**不按轮次**,按玩家聚合、用主牌序排序。前端摊平 `pushlist` 即可,无需改服务端 | | ~~D-9~~ | 大局总结算 | ✅ 定稿:`大局结算.png`,玩家栏用精灵复制(§5.7a)。**四项分数协议缺** → 见 **S-6** | | ~~D-10~~ | 解散结算 | ✅ 复用 D-9 面板;投票界面平台全包(Layer 420) | | ~~D-11~~ | 准备标识 | ✅ 用平台的 | | ~~D-12~~ | 左上家已出牌区 | ✅ 与右上家左右镜像 | | ~~D-13~~ | `扣底` 查看面板 | ✅ 与 `底牌` 按钮复用同一个「8 张牌」展示面板,仅数据源不同 | ### 7.2 待确认的规格问题 **仍未决(1 项)** | 编号 | 问题 | 影响 | | --- | --- | --- | | **T-20** | **整套音效**:清单、是否需要语音包、是否分男女声、倒计时提示音从第几秒起 | §4 全节;已明确「等待后续补充」 | **已定论(33 项)** | 编号 | 结论 | | --- | --- | | ~~T-9~~ | ~~主牌统计条文案(原称"亮牌条")~~ | **已定**:自己的主牌统计条显示 `x对 x主`、有手牌时常显(§2.5)。注意这条**不是** design §8.2 的亮牌——2026-08-27 已把该条改名为 `ZHU_STAT_*` 以杜绝混用 | | ~~T-1~~ | `class.paiju.js:264` `get_nextseat = (seat+1)%3`,下家显示在右上(§0.1) | | ~~T-2~~ | 解散投票**平台全包**(Layer 420 + `GameUI.openCheck` + `Desk.*_free_room`),子游戏只处理结算包 | | ~~T-3~~ | 建房界面 = **Layer 27 `CreateRoom_Layer`**(Layer 28 是代开房/抽水,无关) | | ~~T-4~~ | **需要发牌动画**:从右往左铺开(§1.2) | | ~~T-5~~ | 见下方「T-5 手牌排序」小节 | | ~~T-6~~ | 叫分按钮**按档位位置固定三档配色**(浅黄/金黄/橙)+ 灰=已叫过;按钮底与角标底各 4 帧(§1.3) | | ~~T-7~~ | 选主按钮中央大数字 = 该花色**张数**,右上角标 = 该花色**对数**(§1.5) | | ~~T-8~~ | 手牌标记 3 帧:`拖`=拖拉机 / 橙星=**正2正7** / 蓝星=**其他主牌**;前端据 `flower` 本地标注(§1.6) | | ~~T-10~~ | 出牌阶段**必有** `提示` 按钮;服务端已下发 `mustcard` 供自动选中(S-2 ✅) | | ~~T-11~~ | 牌型标签**只做「毙」一种**;数据层保留 毙/垫/混合出牌 三种标识备扩展,当前不出图 | | ~~T-12~~ | `底牌 20x0` 的 `x` 后面是**扣底倍数**(`bottom.multiple`);未扣底显示 0(§1.8) | | ~~T-13~~ | 结算两个小字 = `牌局分`(`grade_jf`) / `冲关分`(`grade_aw`) | | ~~T-14~~ | 冲关明细走独立的**冲关牌型叠加层** | | ~~T-15~~ | 准备标识**用平台的** | | ~~T-16~~ | `抓分` 角标 = **当前捡分对应判定倍率的实时预览**;服务端已下发 `curmultiple`(S-1 ✅) | | ~~T-17~~ | 倒计时归零**停在 `0`** 不隐藏 | | ~~T-18~~ | 底栏 5 按钮:`上一轮`/`出牌历史`/`明牌`/`扣底`(看**埋牌底牌**)/`底牌`(看**底牌**);术语已统一见 §2.6;可见性按手册 → **S-3** | | ~~T-19~~ | 数字**一律用图片多帧精灵**(参考图数字带描边渐变,文字精灵做不出),资源 631–634 全部要出 | | ~~T-21~~ | 手牌单排 `spacingMin:-78`、双排 `-65`(§6.7) | | ~~T-22~~ | 冲关牌型叠加层:**全屏半透明遮罩本身即关闭热区,点击遮罩关闭**,不另设关闭钮 | | ~~T-23~~ | 叠加层**需要并列显示奖数**(以 `naward` 为主) | | ~~T-24~~ | 冲关牌用**精灵复制**,张数不设上限 | | ~~T-25~~ | `line` 支持可选 `items` 逐项不等宽 | | ~~T-26~~ | 自己发的提示**需要本地回显**(受控例外,理由见 §1.7) | | ~~T-27~~ | 埋牌双排 `floor(n/2)` / `ceil(n/2)` | | ~~T-28~~ | `EQW_Layout` 为权威,配置区手工同步 + 验收检查 | | ~~T-29~~ | 建房整体位置尺寸随 Layer 27 定,编辑器建好后回填 | | ~~T-30~~ | ~~亮牌没有界面位置~~ | **已定**:亮牌下发庄家全部固定主牌的**具体牌面**,用与明牌相同的遮罩版式展示;时机见 T-34(§1.6) | | ~~T-31~~ | ~~大局结算「基础分」与「总得分」两行的对应~~ | **已定**:参考图数值是填充假数据、不作依据(§0.5)。取 `grade_jf_total`(基础分)与 `score`(总分),见 S-6 | | ~~T-32~~ | ~~`等待庄家选主.png` 里底牌仍摊着与 design §4 顺序不符~~ | **已定**:叠画示意(§0.5)。实际按 design §4 的先后:开底 3 秒 → 摸底入手 → 选主 | | ~~T-34~~ | ~~亮牌的展示时机~~ | **已定**:**埋牌后、出牌前**一次性弹出(收 `maipai` 包时),点遮罩关闭 + 自动关闭时长兜底。见 §1.6 | | ~~T-33~~ | ~~等待提示条文案~~ | **已定**:全部 3 条(`等待庄家选主` / `等待庄家埋牌` / `强甩失败`)。叫分·出牌·准备·掉线·开底五处不做——倒计时、平台标识、翻牌画面本身已表达清楚。见 §2.8 | #### T-5 手牌排序(已定) 前端排序**对齐服务端主牌序**(design §3),不另造一套——服务端 `class.arith.js` 的 `order_cards(mainflower, cards)` 即权威顺序(从大到小),下发的 `pushlist` 等字段也已按此排好。 - **选主前**(叫分阶段):`flower` 未定,只有固定主牌(双王 + 全部 2、7)是主牌,其余按花色分组。 - **选主后**:`flower` 确定,正 2 / 正 7 升格、主花色普通牌并入主牌段,**必须整体重排**。重排动画暂不做(先直接重画),后续可加。 - 主牌段落在**左侧**——排序从大到小、主牌最大,自然靠左。 ### 7.3 验收检查清单 编码完成后必须逐条核对(每条都对应一处已知易错点): | # | 检查项 | 依据 | | --- | --- | --- | | 1 | `cardIdToFrame` 通过 §0.4 的 10 条校验样例 | 花色段与 id 段序相反,最易静默画错牌 | | 2 | 三家显示位映射正确:下家 `(mySeat+1)%3` 在右上 | §0.1 | | 3 | 提示按钮顺序(踩/有分/没分)与 `tip` 编号(1踩/2没分/3有分)的映射未按下标硬推 | §1.7 | | 4 | 气泡帧号 = `(箭头向右?3:0)+tip`,朝向按头像相对位置取 | §1.7 / §6.6 | | 5 | 不可查牌模式下:`明牌`/`已出牌`/`上一轮` 按钮不出现、**亮牌面板不弹出**(design §8.2 那个遮罩面板,群组 242)、`主N`/`对N` 角标不出现。⚠ §2.5 **自己的主牌统计条(群组 206)不在此列——它任何模式下都常显** | design §9 / §2.5 | | 6 | 投降结算(无 `bottom`)与解散结算(无 `deskfree` 的情形)都不崩、不显示空框 | §1.8 / §1.10 | | 7 | 解散包从 `data.deskfree.data.aset` 取值,不是 `data.aset` | §1.10 | | 8 | 倒计时归零不做任何界面推进 | 协议 §0.3 | | 9 | 冲关牌型叠加层关闭时 `SpriteCopyUtils.removeRange` 已清理 | §5.6a | | 10 | `EQW_Layout` 与 `Game_Modify.PLAYER_INFO_LAYOUT` 的头像框坐标一致 | §6.11 | | 11 | 丢弃 `this.data`、仅凭最近一次服务端快照重画,界面完全一致 | 前端 05 §6.2 | | 12 | 所有布局参数都在配置文件里,业务代码无裸值 | §6 | | 13 | **底牌 / 埋牌底牌未写反**:`底牌` 按钮读 `bottomcards`、`扣底` 按钮读 `burycards`;内部变量 `bottomCards`/`buryCards` 一一对应 | §2.6 | | 14 | 叫分按钮配色按**档位位置**取(非按子数),已叫过的档位置灰 | §1.3 | | 15 | 自己发的提示本地回显后,收到失败回包时能撤掉 | §1.7 | | 16 | 自己的主牌统计条**有手牌时常显**、不受查牌模式与报无主影响;他家 `主N`/`对N` 角标只在「可查牌 + 已报无主」时出现 | §2.5 | | 17 | 出牌历史**按玩家聚合 + 主牌序排序**,不是按轮次分组 | §1.7 D-7 | | 18 | `明牌` 按钮在「场上**有人**报无主」时**三家都显示**(含报无主那家自己),不是只给自己无主的那家 | §1.7 D-6 | ### 7.4 开发前置条件 在本清单的资源补齐前,前端**不具备开发条件**的部分: 1. **美术出图** → §3 全部资源(尤其 3 套牌面 × 60 帧) 2. **编辑器建资源与精灵** → §5 全部 ID 回填本文 3. **D-1 建房界面** → 影响 `roomtype` 拼串与进入牌局的第一步 不受阻、可先行的部分: - `client/js/01_SubGame/codes/` 目录结构与 `SubGameHooks.js` 骨架 - `cardIdToFrame` 转换与其单元测试(§0.4 的 10 条校验样例可直接作为用例) - 收包分发骨架(一 rpc 一处理器)与 `data.success` 判定 - **布局配置骨架与布局求解器**(§6)——`kind` 五型的参数结构与求解逻辑是设计决定、不依赖具体数值,可先写好并用占位坐标跑通,等设计稿到位只改配置值 - `shared/` 同步机制 ### 7.5 服务端待补项(需改服务端才能实现的前端需求) 以下 3 条来自本轮确认的需求,**当前协议/服务端不支持**,需在服务端补齐后前端才能实现。三条都要同步更新 `packet_protocol.md`。 #### S-1 · 「当前抓分的倍数」字段 `curmultiple` —— ✅ 已完成(2026-08-25) **语义**:当前捡分对应判定倍率的**实时预览**——「若此刻结束是几倍」,取绝对值。 | 当前捡分 `grade` 相对叫分 `call` | 判定 | `curmultiple` | | --- | --- | --- | | `grade == 0` | 庄家:大光 | **3** | | `0 < grade < Q` | 庄家:小光 | **2** | | `Q ≤ grade < call` | 庄家:过庄 | **1** | | `grade ≥ call` | 闲家:升 N 级 | **N** | (`Q` = 小光/过庄分界兼升级级距,常规算子固定 40、爬坡按叫分分段,design §7.2.0 / §7.3.2。) **下发位置**(随现有包,**不另开推送**):`shangzhuang`、`chupai1`、`chupai2`、`chupai3`、`deskinfo.PushCards`。 **要点**: - 三家**同值**,整表下发不涉及泄露(`grade` 本就公开)。 - 与结算包 `aset.upgrade` **同源同算法**(`get_qvalue` + `get_upgrade`),差别只在这里**不含扣底**——扣底要到末轮打完才产生。 - 叫分未定时为 **0**,前端不显示角标。 - 前端**不得自算**(服务端权威)。 **实现**:`class.paiju.js` 新增 `get_curmultiple()`;`mod.js` / `class.export.js` 组包处下发。 #### S-2 · 下发「应当选中的牌」`mustcard` —— ✅ 已完成(2026-08-25) **语义**:下一个出牌者本轮跟牌的**必出牌**(design §5.2),前端收到后自动选中(上浮),玩家确认后点 `出牌`。 **下发位置**:`chupai1` / `chupai2`(给 `nextseat`)、`deskinfo.PushCards`(给轮到的那一家)。 **可见性(关键)**:**只发给 `nextseat` 一家**。它是该玩家自己手牌的子集,发给他本人不泄露;**整表下发会把另两家的手牌结构透露出去**(server 红线:发全 ≠ 发多,按可见性下发)。已纳入 `test_leak.js` 的 `CARD_FIELDS` 审计白名单。 **不下发的情形**: 1. 非出牌阶段; 2. 该座位是本轮**首家**(自由出牌,无必出); 3. 首家是**甩牌**——甩牌跟牌走 `flush_follow_ok` 的逐分量匹配(design §5.4.4),与 `get_followcard` 不是同一条路径,给建议会给错,宁可不发; 4. 算出的必出牌为空(玩家可自由选)。 **实现**:`class.paiju.js` 新增 `get_mustcard(seat)`,内部复用**与出牌校验完全相同**的 `get_followcard`,故建议与校验结果天然一致。 **不违反「请求包只带意图」**:方向是服务端 → 前端的建议,前端仍只回传 `cards`,合法性最终由服务端校验。 #### S-3 · 底牌的可见性:按手册办(对应 T-18)—— 已定 **结论:按 design 手册**,即前述方案 (a)。底栏 `底牌` 按钮只在**能合法看到底牌**时可用: | 谁 | 何时可用 | | --- | --- | | 庄家 | **全程可用**(他本来就摸过这 8 张) | | 闲家 | **仅 70 分坐庄局**可用(这 8 张已向全场公示过 3 秒);其余局**按钮不显示** | 依据 design §4:非 70 分坐庄时这 8 张**只有庄家可见**;协议也一致(`shangzhuang.bottomcards` 闲家仅 70 分时才有,重连包的同名字段标注「只有庄家有此属性」)。 **服务端无需改动**——现有下发面正好支持这条规则:闲家在非 70 分局本来就收不到数据,按钮不显示即可,不存在「拿到了但不渲染」的泄露风险(服务端红线:下发即泄露)。 #### S-4 · 拆分 `bottomcards` 字段名 —— ✅ 已完成(2026-08-25) 两批牌原先共用 `bottomcards`、只靠所在包区分语义,现已拆分: | 字段名 | 含义 | 出现在 | | --- | --- | --- | | **`bottomcards`** | **底牌**(发牌留桌 8 张) | `shangzhuang`、`deskinfo.ChooseMain`、`deskinfo.BuryCards` | | **`burycards`** | **埋牌底牌**(庄家埋下 8 张) | `maipai`、`deskinfo.PushCards` | | `bottom.cards` | **埋牌底牌** | 结算包 `bottom` 分组(分组名已表明是抠底相关,未改名) | **服务端改动**:`mod.js`(`maipai` 组包)、`class.export.js`(`deskinfo.PushCards`),另同步了两处的注释术语。 **测试**:`test/test_leak.js` 的 `CARD_FIELDS` 白名单加入 `burycards`(否则新字段会静默脱离下发面审计);`test/test_rpc.js` 新增 11 条正反用例锁死拆分结果。 **协议文档**:`packet_protocol.md` §0.0 与相关字段表已更新。 **前端取值**:直接按字段名区分即可,不再需要「按所在包判断语义」: ```js // 底牌(发牌留桌 8 张)——底栏「底牌」按钮 data.bottomcards // shangzhuang / ChooseMain / BuryCards // 埋牌底牌(庄家埋下 8 张)——底栏「扣底」按钮 data.burycards // maipai / PushCards data.bottom.cards // 结算包 ``` #### S-5 · 同步 design.md 与 packet_protocol.md 的术语 —— ✅ 已完成(2026-08-25) 术语变更(暗牌→**底牌**、底牌→**埋牌底牌**)已在各处生效: | 文档 / 代码 | 状态 | | --- | --- | | `docs_dev/二七王-UI资源与精灵清单.md`(本文) | ✅ 全文新术语 | | `server/games/erqiwang/docs/design/design.md` | ✅ 全文 38 处替换;术语表后补「术语变更记录」说明改名缘由 | | `server/games/erqiwang/docs/protocol/packet_protocol.md` | ✅ 新增 §0.0 术语小节;字段说明同步;字段名随 S-4 拆分 | | `mod.js` / `class.export.js` / `test/test_leak.js` 注释 | ✅ 随 S-4 一并更新 | | `docs/compliance/*.md`(3 份,24 处) | ⬜ **有意不改**——这三份是历次核对的**历史记录**,反映当时的 design 表述;下轮核对按新 design 重做即可,不追溯修改 | | `test/*.js` 其余注释(约 14 处) | ⬜ 仅注释、不影响行为,后续顺手清理 | #### S-6 · 大局结算的三项分解 —— ✅ 已完成(2026-08-26) **问题**:大局结算面板要显示 `基础分` / `冲关分` / `傍王分`,而原 `account` 只有「累积得分 + 每局得分数组」。其中**傍王分尤其拆不出来**——`N = 冲关奖数 + 傍王王数` 两个来源在**求和之后**才代入 `grade_aw = X×(2Ni−Nj−Nk)`,事后无法反推各自占比。 **解法**:在算钱时就拆。把 `N` 分成 `N_冲关`(只计庄家)与 `N_傍王`(勾选后庄闲都算),**分别代入同一公式**各算一次。公式对 `N` 线性,故恒有 `grade_cg + grade_bw == grade_aw`——这条恒等式正好当回归断言。 **小局包 `aset.seatlist[i]` 新增**: | 字段 | 说明 | | --- | --- | | `grade_cg` | 算奖得分里的**冲关**分量(不含傍王) | | `grade_bw` | 算奖得分里的**傍王**分量(未勾傍王时恒 0) | **大局包 `account` 结构改了**(原为二元数组,现为对象数组): ```json [{ "score": 0, "grades": [], "grade_jf_total": 0, "grade_cg_total": 0, "grade_bw_total": 0 }, …] ``` | 面板栏位 | 取值 | | --- | --- | | 基础分 | `grade_jf_total` | | 冲关分 | `grade_cg_total` | | 傍王分 | `grade_bw_total` | | 右侧总分大字 | `score` | 恒等式:`grade_jf_total + grade_cg_total + grade_bw_total == score`。 **顺带解决了小局结算「冲关分」标签的不严谨**(见 §0.0):该标签现在应改取 `grade_cg` 而非 `grade_aw`,开傍王时也名副其实了。 > 面板上「基础分」取 `grade_jf_total`、「总得分」取 `score`。参考图里这两行数值相同、且与右侧大字对不上,是填充假数据所致,不作依据(§0.5)。 #### S-7 · 明牌按钮的显示条件 —— ✅ 已定论:按手册,服务端无需改动(2026-08-26) **结论:按 design §9.3 手册办**——**任一玩家报无主后,三家都显示 `明牌` 按钮,且可随时查看。** | 项 | 取值 | | --- | --- | | 显示条件 | 可查牌模式 **+** 出牌阶段 **+** `baozhu == 1`(**任一**玩家主牌出空) | | 谁能看 | **全体三人**,包括刚刚报无主的那位自己 | | 次数 | **不限**,随时可点开、可关闭 | **服务端无需改动**——`mingpai` 现有的受理条件 `have_baofu()`(任一玩家报无主)**正好就是这条规则**,与 design §9.3 一致。此前我把前端条件收紧成「自己是无主玩家」,比手册严格,反而与服务端校验对不上;现已改回。 > 之前记录的「有主牌的玩家伪造请求也能拿到明牌数据」这一泄露担忧**不成立**:按手册他本来就有权查看,服务端受理是正确行为,不是校验缺失。 #### S-8 · `curmultiple` 改为带符号 —— ✅ 已完成(2026-08-26) **问题**:S-1 实现时取了绝对值,而判定的**正负号恰恰是「谁赢」的区分**——`3` 大光与 `-3` 升3级、`2` 小光与 `-2` 升2级、`1` 过庄与 `-1` 升1级,取绝对值后三对完全撞在一起,D-8 的过程中动画无从分辨该播哪个。 **改法**:`get_curmultiple` 直接返回 `get_upgrade` 的原值,**与结算包 `aset.upgrade` 完全同口径**: | 值 | 含义 | | --- | --- | | `3` / `2` / `1` | 庄家 **大光** / **小光** / **过庄** | | `-N` | 闲家**升 N 级** | | `0` | 叫分未定 | **前端用法**: - 顶部「抓分」角标只关心倍数大小 → `Math.abs(curmultiple)`(§2.1)。 - **判定动画**靠符号分辨该播哪个(§1.8 D-8),一套映射通吃「过程中」与「结算前」两条路径。 **测试**(hint 33 → 46 checks):新增两组——一组直接钉住三对「绝对值相同、判定相反」的组合可区分;另一组**直接调 `get_curmultiple` 本身**覆盖负数分支。 > 后一组是补上的**测试盲点**:原有断言里,算法层的 `cm()` 走的是 `A.get_upgrade`、**绕过了 `get_curmultiple`**,而端到端那局恰好是大光(正数),取不取绝对值结果一样。第一次反向验证(把实现退回取绝对值)竟然全绿,正是因为这个盲点。补齐后再验,4 条断言立刻失败。 --- ## 附:本文与其他文档的关系 | 文档 | 关系 | | --- | --- | | `server/games/erqiwang/docs/design/design.md` | **玩法规则权威源**。本文引用其条款,不重新定义规则;术语「底牌 / 埋牌底牌」已同步(见 §0.0) | | `server/games/erqiwang/docs/protocol/packet_protocol.md` | **协议字段权威源**。本文的「渲染什么」全部溯源到具体字段 | | `docs/client/development-guide/` | **前端规范权威源**。ID 范围、组件范式、红线均以其为准 | | `docs_dev/uiref/*.png` | 参考图,16 张(牌桌态 + 叫分按钮面板 + 大局结算 + 强甩失败 + 等待庄家选主 + 1 张建房**样式范式**,末者内容属他游戏、不可照抄) | | `client/js/gameabc-framework/ui/AlignmentUtils.js` | **布局求解的框架能力**。§6 的配置参数名与其入参一一对应,配置可原样喂入 | | `client/js/gameabc-framework/ui/SpriteCopyUtils.js` | 精灵复制。用于冲关牌型、大局结算玩家栏、出牌历史 / 明牌(§5.6a 判据表) | | `client/js/gameabc-framework/templates/*.template.js` | 常量文件写法蓝本。本清单落地为常量文件时按其格式 |