/**
* ============================================================================
* 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 }
}
};