501 lines
16 KiB
JavaScript
501 lines
16 KiB
JavaScript
/**
|
||
* ============================================================================
|
||
* LayoutConstants.template.js - 游戏布局常量规范模板
|
||
* ============================================================================
|
||
*
|
||
* 使用方式:
|
||
* 将此文件复制到游戏目录,重命名为 LayoutConstants.js,
|
||
* 然后按游戏实际分辨率和精灵编排修改各项数值。
|
||
*
|
||
* 如布局配置过多,可拆分为多个文件(如 PopupLayoutConstants.js),
|
||
* 每个文件都向同一个全局对象 LayoutConstants 挂载属性。
|
||
*
|
||
* 加载顺序(index.html 中只需保证在 CardLayoutManager.js 之前加载即可):
|
||
* <script src="js/01_SubGame/codes/game/constants/sprites/LayoutConstants.js"></script>
|
||
* <script src="js/01_SubGame/codes/game/constants/sprites/PopupLayoutConstants.js"></script>
|
||
* <script src="js/01_SubGame/codes/game/managers/CardLayoutManager.js"></script>
|
||
*
|
||
* 文件不依赖框架,可单独加载。
|
||
*
|
||
* ============================================================================
|
||
* @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 }
|
||
}
|
||
};
|