/** * ============================================================================ * LayoutConstants.template.js - 游戏布局常量规范模板 * ============================================================================ * * 使用方式: * 将此文件复制到游戏目录,重命名为 LayoutConstants.js, * 然后按游戏实际分辨率和精灵编排修改各项数值。 * * 如布局配置过多,可拆分为多个文件(如 PopupLayoutConstants.js), * 每个文件都向同一个全局对象 LayoutConstants 挂载属性。 * * 加载顺序(index.html 中只需保证在 CardLayoutManager.js 之前加载即可): * * * * * 文件不依赖框架,可单独加载。 * * ============================================================================ * @guide 核心设计理念 * ============================================================================ * * LayoutConstants 是一个 **纯数据文件**(无函数、无副作用), * 所有坐标/尺寸/偏移量都在这里集中定义,供各 Manager / View 读取。 * * 原则: * 1. 只包含常量对象,不包含业务逻辑 * 2. 每个布局区域单独一个顶层属性(HAND_CARD_LAYOUT, DISCARD_LAYOUT …) * 3. 4人麻将用 PLAYER_1..4 子对象;其他游戏可用 SELF/OPPONENT 等命名 * 4. 透视偏移量(PERSPECTIVE_OFFSET_X/Y)用于 2D 模拟 3D 效果,正对视角均为 0 * 5. DIRECTION 枚举:'horizontal' | 'horizontal-reverse' | 'vertical' | 'vertical-reverse' * 6. ALIGN_MODE 枚举:'start' | 'center' | 'end' * * ============================================================================ * @guide 透视偏移量说明 * ============================================================================ * * gameabc 以 2D 图片模拟 3D 透视效果。每张牌在排列时叠加一个累积偏移: * * 最终X = 基准X + 牌索引 × PERSPECTIVE_OFFSET_X * 最终Y = 基准Y + 牌索引 × PERSPECTIVE_OFFSET_Y * * 正值向右/向下,负值向左/向上。 * 玩家1(正对屏幕)通常 PERSPECTIVE_OFFSET = 0。 * 玩家2/4(侧视角)需要配置非零值形成倾斜效果。 * * ============================================================================ * @guide 如何拆分为多文件 * ============================================================================ * * // LayoutConstants.js(主游戏界面) * var LayoutConstants = {}; * LayoutConstants.HAND_CARD_LAYOUT = { ... }; * LayoutConstants.DISCARD_LAYOUT = { ... }; * * // PopupLayoutConstants.js(弹窗界面,另一个文件) * var LayoutConstants = LayoutConstants || {}; // 保护性声明 * LayoutConstants.ROUND_RESULT_LAYOUT = { ... }; * LayoutConstants.GAME_RESULT_LAYOUT = { ... }; * * 两个文件加载后,LayoutConstants 包含所有属性。 * * @version 1.0.0 * @see LayoutConstants.js 金仙麻将实际实现 * @see CardLayoutManager.js 使用 LayoutConstants 计算精灵坐标 */ 'use strict'; /** * LayoutConstants - 布局常量对象 * 所有布局配置挂载在此全局对象下 */ var LayoutConstants = {}; // ============================================================================ // 设计分辨率 // ============================================================================ /** 设计稿宽度(px) */ LayoutConstants.DESIGN_WIDTH = 1280; /** 设计稿高度(px) */ LayoutConstants.DESIGN_HEIGHT = 720; // ============================================================================ // 手牌区布局(示例:4人麻将) // ============================================================================ /** * 手牌区布局配置 * * DIRECTION 值: * 'horizontal' - 从左到右(PLAYER_1 常用) * 'horizontal-reverse' - 从右到左(PLAYER_3 常用) * 'vertical' - 从上到下(PLAYER_4 常用) * 'vertical-reverse' - 从下到上(PLAYER_2 常用) * * ALIGN_MODE 值: * 'start' - 从 START_X/Y 开始,按索引递增方向铺开 * 'center' - 手牌整体居中于 START_X/Y * 'end' - 最后一张牌落在 START_X/Y,向前排列 */ LayoutConstants.HAND_CARD_LAYOUT = { // 玩家1 (下方 - 自己) - 正视角,无透视 PLAYER_1: { START_X: 164, // 第1张牌的左边界X START_Y: 613, // 手牌区Y坐标 CARD_WIDTH: 74, // 单张牌宽度(含视觉厚度) CARD_HEIGHT: 102, // 单张牌高度 CARD_SPACING: -2, // 牌间距(负值=重叠) DRAW_CARD_GAP: 15, // 摸牌与手牌的额外间隔 SELECT_OFFSET_Y: -20, // 选中牌向上偏移量 MAX_CARDS: 13, // 最大手牌数 DIRECTION: 'horizontal', ALIGN_MODE: 'end', PERSPECTIVE_OFFSET_X: 0, PERSPECTIVE_OFFSET_Y: 0 }, // 玩家2 (右方) - 侧视角,竖排,透视向左上 PLAYER_2: { START_X: 1205, START_Y: 537, CARD_WIDTH: 52, CARD_HEIGHT: 72, CARD_SPACING: 0, DRAW_CARD_GAP: 0, MAX_CARDS: 13, DIRECTION: 'vertical-reverse', ALIGN_MODE: 'center', PERSPECTIVE_OFFSET_X: -8, PERSPECTIVE_OFFSET_Y: -37, DRAW_CARD_PERSPECTIVE_X: -12, DRAW_CARD_PERSPECTIVE_Y: 18 }, // 玩家3 (上方) - 对面视角,横排,无透视 PLAYER_3: { START_X: 820, START_Y: 44, CARD_WIDTH: 43, CARD_HEIGHT: 58, CARD_SPACING: -3, DRAW_CARD_GAP: 6, MAX_CARDS: 13, DIRECTION: 'horizontal-reverse', ALIGN_MODE: 'end', PERSPECTIVE_OFFSET_X: 0, PERSPECTIVE_OFFSET_Y: 0 }, // 玩家4 (左方) - 侧视角,竖排,透视向左上 PLAYER_4: { START_X: 139, START_Y: 74, CARD_WIDTH: 52, CARD_HEIGHT: 72, CARD_SPACING: 0, DRAW_CARD_GAP: 12, MAX_CARDS: 13, DIRECTION: 'vertical', ALIGN_MODE: 'center', PERSPECTIVE_OFFSET_X: -8, PERSPECTIVE_OFFSET_Y: -37, DRAW_CARD_PERSPECTIVE_X: -9, DRAW_CARD_PERSPECTIVE_Y: -35 } }; // ============================================================================ // 弃牌区布局(示例) // ============================================================================ /** * 弃牌区布局配置 * * CARDS_PER_ROW / MAX_ROWS:弃牌按行/列分组排列(DIRECTION 为 vertical 时按列)。 * ROW_PERSPECTIVE_X/Y:每换一行的累积透视偏移,用于模拟牌桌中央的深度感。 */ LayoutConstants.DISCARD_LAYOUT = { PLAYER_1: { START_X: 500, START_Y: 345, CARD_WIDTH: 45, CARD_HEIGHT: 55, CARDS_PER_ROW: 9, ROW_SPACING: 2, CARD_SPACING: 1, MAX_ROWS: 3, CARD_PERSPECTIVE_X: 0, CARD_PERSPECTIVE_Y: 0, ROW_PERSPECTIVE_X: 2, ROW_PERSPECTIVE_Y: -3 }, PLAYER_2: { START_X: 755, START_Y: 175, CARD_WIDTH: 28, CARD_HEIGHT: 36, CARDS_PER_ROW: 3, ROW_SPACING: 1, CARD_SPACING: 1, MAX_ROWS: 9, DIRECTION: 'vertical', CARD_PERSPECTIVE_X: -1, CARD_PERSPECTIVE_Y: 0, ROW_PERSPECTIVE_X: -2, ROW_PERSPECTIVE_Y: 1 }, PLAYER_3: { START_X: 720, START_Y: 95, CARD_WIDTH: 36, CARD_HEIGHT: 28, CARDS_PER_ROW: 9, ROW_SPACING: 2, CARD_SPACING: 1, MAX_ROWS: 3, DIRECTION: 'horizontal-reverse', CARD_PERSPECTIVE_X: 0, CARD_PERSPECTIVE_Y: 0, ROW_PERSPECTIVE_X: -2, ROW_PERSPECTIVE_Y: 2 }, PLAYER_4: { START_X: 200, START_Y: 280, CARD_WIDTH: 58, CARD_HEIGHT: 41, CARDS_PER_ROW: 3, ROW_SPACING: 1, CARD_SPACING: 1, MAX_ROWS: 9, DIRECTION: 'vertical-reverse', CARD_PERSPECTIVE_X: 1, CARD_PERSPECTIVE_Y: 0, ROW_PERSPECTIVE_X: -3, ROW_PERSPECTIVE_Y: -12 } }; // ============================================================================ // 副露区布局(吃/碰/杠) // ============================================================================ /** * 副露区布局配置 * * 透视偏移量(组内 + 组间): * CARD_PERSPECTIVE_X/Y - 同一副露组内,每张牌相对前一张的额外偏移(累加) * GROUP_PERSPECTIVE_X/Y - 不同副露组之间,每组相对前一组的额外偏移(累加) * * 坐标计算公式(CardLayoutManager.js 负责实现): * 透视X = cardIndex × CARD_PERSPECTIVE_X + meldIndex × GROUP_PERSPECTIVE_X * 最终X = 基准X + 透视X */ LayoutConstants.MELD_LAYOUT = { PLAYER_1: { START_X: 105, START_Y: 639, CARD_WIDTH: 64, CARD_HEIGHT: 84, CARD_SPACING: 0, GROUP_SPACING: 8, DIRECTION: 'horizontal', MAX_GROUPS: 4, KONG_STACK_OFFSET_X: 0, KONG_STACK_OFFSET_Y: -20, CARD_PERSPECTIVE_X: -5, CARD_PERSPECTIVE_Y: 0, GROUP_PERSPECTIVE_X: -3, GROUP_PERSPECTIVE_Y: 0 }, PLAYER_2: { START_X: 1115, START_Y: 509, CARD_WIDTH: 65, CARD_HEIGHT: 48, CARD_SPACING: 0, GROUP_SPACING: 0, DIRECTION: 'vertical-reverse', MAX_GROUPS: 4, KONG_STACK_OFFSET_X: 6, KONG_STACK_OFFSET_Y: -20, CARD_PERSPECTIVE_X: -7, CARD_PERSPECTIVE_Y: 35, GROUP_PERSPECTIVE_X: -25, GROUP_PERSPECTIVE_Y: 35 }, PLAYER_3: { START_X: 930, START_Y: 48, CARD_WIDTH: 43, CARD_HEIGHT: 58, CARD_SPACING: 0, GROUP_SPACING: 4, DIRECTION: 'horizontal-reverse', MAX_GROUPS: 4, KONG_STACK_OFFSET_X: 0, KONG_STACK_OFFSET_Y: -9, CARD_PERSPECTIVE_X: 3, CARD_PERSPECTIVE_Y: 0, GROUP_PERSPECTIVE_X: -3, GROUP_PERSPECTIVE_Y: 0 }, PLAYER_4: { START_X: 198, START_Y: 91, CARD_WIDTH: 63, CARD_HEIGHT: 49, CARD_SPACING: 0, GROUP_SPACING: 0, DIRECTION: 'vertical', MAX_GROUPS: 4, KONG_STACK_OFFSET_X: -7, KONG_STACK_OFFSET_Y: -17, CARD_PERSPECTIVE_X: -6, CARD_PERSPECTIVE_Y: -30, GROUP_PERSPECTIVE_X: -25, GROUP_PERSPECTIVE_Y: -30 } }; // ============================================================================ // 玩家信息 UI 布局(支持 2/3/4 人) // ============================================================================ /** * 玩家信息 UI 布局配置 * * 座位映射(seatToInd): * 不同人数局中,座位号(0-3)映射到屏幕显示位置(ind 0-3)。 * ind = -1 表示该座位在此人数局中不显示。 * * 约定:ind0=下方(自己) ind1=右方 ind2=上方 ind3=左方 * * 位置配置(POSITIONS[ind]): * FRAME_* 底框精灵的绝对坐标和尺寸 * AVATAR.* 头像相对于底框左上角的偏移量(OFFSET_X/Y + WIDTH/HEIGHT) * NAME_OFFSET_X/Y 昵称相对于头像左上角的偏移 * SCORE_OFFSET_X/Y 分数相对于头像左上角的偏移 */ LayoutConstants.PLAYER_INFO_LAYOUT = { DESIGN_WIDTH: 1280, DESIGN_HEIGHT: 720, // 座位号 → 显示位置 ind 的映射 SEAT_MAPPING: { 4: [0, 1, 2, 3], 3: [0, 1, 3, -1], // 3人局:北位(ind2)不显示 2: [0, 2, -1, -1] // 2人局:只有南北两位 }, VISIBLE_POSITIONS: { 4: [0, 1, 2, 3], 3: [0, 1, 3], 2: [0, 2] }, POSITIONS: { // ind=0: 南位(下方) - 玩家自己 0: { FRAME_X: 6, FRAME_Y: 592, FRAME_WIDTH: 90, FRAME_HEIGHT: 115, AVATAR: { OFFSET_X: 5, OFFSET_Y: 5, WIDTH: 80, HEIGHT: 80, NAME_OFFSET_X: -400, NAME_OFFSET_Y: 888, SCORE_OFFSET_X: 0, SCORE_OFFSET_Y: 88 }, NAME_CENTER_ALIGN: true }, // ind=1: 东位(右方) 1: { FRAME_X: 1186, FRAME_Y: 85, FRAME_WIDTH: 90, FRAME_HEIGHT: 115, AVATAR: { OFFSET_X: 5, OFFSET_Y: 5, WIDTH: 80, HEIGHT: 80, NAME_OFFSET_X: 905, NAME_OFFSET_Y: -888, SCORE_OFFSET_X: 0, SCORE_OFFSET_Y: 88 }, NAME_CENTER_ALIGN: true }, // ind=2: 北位(上方) 2: { FRAME_X: 1006, FRAME_Y: 5, FRAME_WIDTH: 90, FRAME_HEIGHT: 115, AVATAR: { OFFSET_X: 5, OFFSET_Y: 5, WIDTH: 80, HEIGHT: 80, NAME_OFFSET_X: 905, NAME_OFFSET_Y: -888, SCORE_OFFSET_X: 0, SCORE_OFFSET_Y: 88 }, NAME_CENTER_ALIGN: true }, // ind=3: 西位(左方) 3: { FRAME_X: 7, FRAME_Y: 59, FRAME_WIDTH: 90, FRAME_HEIGHT: 115, AVATAR: { OFFSET_X: 5, OFFSET_Y: 5, WIDTH: 80, HEIGHT: 80, NAME_OFFSET_X: -605, NAME_OFFSET_Y: -888, SCORE_OFFSET_X: 0, SCORE_OFFSET_Y: 88 }, NAME_CENTER_ALIGN: true } }, TEXT_STYLE: { NAME_FONT_SIZE: 14, NAME_MAX_LENGTH: 6, SCORE_FONT_SIZE: 12, CHAR_WIDTH: 9 } }; // ============================================================================ // 庄家标识布局 // ============================================================================ /** * 庄家图标布局 * * POSITION_TYPE 决定图标与头像底框的对齐方式: * 'top-left' - 图标左上角 = 底框右上角(图标显示在底框右侧) * 'top-right' - 图标右上角 = 底框左上角(图标显示在底框左侧) */ LayoutConstants.DEALER_ICON_LAYOUT = { ICON_WIDTH: 32, ICON_HEIGHT: 32, SEAT_MAPPING: { 4: [0, 1, 2, 3], 3: [0, 1, 3, -1], 2: [0, 2, -1, -1] }, POSITIONS: { 0: { POSITION_TYPE: 'top-left' }, 1: { POSITION_TYPE: 'top-right' }, 2: { POSITION_TYPE: 'top-right' }, 3: { POSITION_TYPE: 'top-left' } } }; // ============================================================================ // 操作倒计时布局 // ============================================================================ /** * 操作倒计时布局 * * 数字图片居中显示在 CENTER_X: * actualX = CENTER_X - (digitCount × CHAR_WIDTH / 2) */ LayoutConstants.COUNTDOWN_LAYOUT = { CENTER_X: 640, // 水平居中锚点 Y: 296, CHAR_WIDTH: 31, CHAR_HEIGHT: 33 }; // ============================================================================ // 操作按钮布局(吃/碰/杠/胡/听/过) // ============================================================================ /** * 操作按钮动态水平布局 * * 从右锚点 ANCHOR_X 向左排列,按钮数量变化时整体右对齐: * totalW = count × BUTTON_WIDTH + (count-1) × BUTTON_SPACING * firstX = ANCHOR_X - totalW * nthX = firstX + (n-1) × (BUTTON_WIDTH + BUTTON_SPACING) */ LayoutConstants.ACTION_BUTTON_LAYOUT = { ANCHOR_X: 1180, // 右对齐锚点 Y: 560, // 按钮Y坐标(统一) BUTTON_WIDTH: 100, BUTTON_HEIGHT: 80, BUTTON_SPACING: 10 }; // ============================================================================ // 最后出牌指示器布局 // ============================================================================ /** * 最后出牌指示器偏移量(相对于该牌的坐标) */ LayoutConstants.LAST_DISCARD_INDICATOR_LAYOUT = { OFFSET: { 0: { X: 8, Y: -20 }, 1: { X: 15, Y: -25 }, 2: { X: 6, Y: -20 }, 3: { X: 15, Y: -23 } } };