/** * AlignmentUtils - 精灵对齐工具类 * 文件: client/js/01_SubGame/codes/game/utils/AlignmentUtils.js * * 🎯 设计目的: * 提供统一的精灵对齐计算方法,简化UI布局代码 * * 🔑 核心原则: * 1. 精灵锚点永远在左上角,无法更改 * 2. 所有对齐计算都基于锚点位置 * 3. 支持常见的9种对齐方式(左上/中上/右上/左中/中中/右中/左下/中下/右下) * 4. 支持偏移量微调 * * 📐 坐标系统: * - X轴:从左到右递增 * - Y轴:从上到下递增 * - 精灵锚点:左上角 (0, 0) * * @author JinXian Team * @version 1.0.0 * @since 2025-12-30 */ 'use strict'; var AlignmentUtils = { VERSION: '1.0.0', /** * 对齐常量 */ ALIGN: { // 水平对齐 LEFT: 'left', // 左对齐 CENTER: 'center', // 水平居中 RIGHT: 'right', // 右对齐 // 垂直对齐 TOP: 'top', // 顶部对齐 MIDDLE: 'middle', // 垂直居中 BOTTOM: 'bottom' // 底部对齐 }, /** * 将精灵对齐到目标矩形区域 * * 🔑 核心:精灵的锚点永远在左上角,此方法计算锚点应放置的位置 * * @param {Object} target - 目标矩形区域 * @param {number} target.x - 目标区域左上角X坐标 * @param {number} target.y - 目标区域左上角Y坐标 * @param {number} target.width - 目标区域宽度 * @param {number} target.height - 目标区域高度 * * @param {Object} sprite - 精灵信息(精灵锚点固定在左上角) * @param {number} sprite.width - 精灵宽度 * @param {number} sprite.height - 精灵高度 * * @param {string} horizontalAlign - 水平对齐方式: 'left'|'center'|'right' * @param {string} verticalAlign - 垂直对齐方式: 'top'|'middle'|'bottom' * * @param {Object} [offset] - 偏移量(可选) * @param {number} [offset.x=0] - X轴偏移 * @param {number} [offset.y=0] - Y轴偏移 * * @returns {Object} 精灵锚点位置(左上角) {x, y} * * @example * // 将文本标签对齐到矩形框左下角 * var labelPos = AlignmentUtils.alignTo( * { x: 10, y: 10, width: 60, height: 60 }, // 矩形框区域 * { width: 40, height: 20 }, // 标签大小 * 'left', // 左对齐 * 'bottom', // 底部对齐 * { x: 0, y: 5 } // 向下偏移5px * ); * // 返回: { x: 10, y: 75 } (矩形底部 + 偏移,注意精灵锚点在左上角) * * @example * // 将图标对齐到矩形框右下角 * var iconPos = AlignmentUtils.alignTo( * { x: 10, y: 10, width: 60, height: 60 }, // 矩形框区域 * { width: 30, height: 30 }, // 图标大小 * 'right', // 右对齐 * 'bottom', // 底部对齐 * { x: -5, y: 5 } // 向左偏移5px,向下偏移5px * ); * // 返回: { x: 35, y: 75 } (矩形右边界 - 图标宽度 - 5px,精灵锚点在左上角) */ alignTo: function(target, sprite, horizontalAlign, verticalAlign, offset) { offset = offset || {}; var offsetX = offset.x || 0; var offsetY = offset.y || 0; var x, y; // 计算水平位置 switch (horizontalAlign) { case this.ALIGN.LEFT: // 左对齐:精灵左边缘与目标左边缘对齐 x = target.x; break; case this.ALIGN.CENTER: // 水平居中:精灵中心与目标中心对齐 // 🔑 精灵的锚点永远在左上角,所以需要减去精灵宽度的一半 x = target.x + (target.width - sprite.width) / 2; break; case this.ALIGN.RIGHT: // 右对齐:精灵右边缘与目标右边缘对齐 // 🔑 精灵的锚点永远在左上角,所以需要减去精灵宽度 x = target.x + target.width - sprite.width; break; default: console.warn('[AlignmentUtils] 未知的水平对齐方式:', horizontalAlign); x = target.x; } // 计算垂直位置 switch (verticalAlign) { case this.ALIGN.TOP: // 顶部对齐:精灵顶边与目标顶边对齐 y = target.y; break; case this.ALIGN.MIDDLE: // 垂直居中:精灵中心与目标中心对齐 // 🔑 精灵的锚点永远在左上角,所以需要减去精灵高度的一半 y = target.y + (target.height - sprite.height) / 2; break; case this.ALIGN.BOTTOM: // 底部对齐:精灵底边与目标底边对齐 // 🔑 精灵的锚点永远在左上角,所以需要加上目标高度 y = target.y + target.height; break; default: console.warn('[AlignmentUtils] 未知的垂直对齐方式:', verticalAlign); y = target.y; } // 应用偏移 return { x: x + offsetX, y: y + offsetY }; }, /** * 将精灵对齐到目标矩形的左下角(快捷方法) * * 🔑 注意:精灵的锚点永远在左上角 * * @param {Object} target - 目标矩形区域 * @param {number} target.x - 目标区域左上角X坐标 * @param {number} target.y - 目标区域左上角Y坐标 * @param {number} target.width - 目标区域宽度 * @param {number} target.height - 目标区域高度 * * @param {Object} sprite - 精灵大小(锚点固定在左上角) * @param {number} sprite.width - 精灵宽度 * @param {number} sprite.height - 精灵高度 * * @param {Object} [offset] - 偏移量 * @param {number} [offset.x=0] - X偏移 * @param {number} [offset.y=0] - Y偏移 * * @returns {Object} 精灵锚点位置(左上角) {x, y} * * @example * // 将文本标签对齐到容器左下角 * var pos = AlignmentUtils.alignBottomLeft( * { x: 10, y: 10, width: 60, height: 60 }, // 容器区域 * { width: 40, height: 20 }, // 标签大小 * { x: 0, y: 5 } // 向下偏移5px * ); */ alignBottomLeft: function(target, sprite, offset) { return this.alignTo(target, sprite, this.ALIGN.LEFT, this.ALIGN.BOTTOM, offset); }, /** * 将精灵对齐到目标矩形的右下角(快捷方法) * * 🔑 注意:精灵的锚点永远在左上角,右对齐时会自动减去精灵宽度 * * @param {Object} target - 目标矩形区域 * @param {Object} sprite - 精灵大小(锚点固定在左上角) * @param {Object} [offset] - 偏移量 * @returns {Object} 精灵锚点位置(左上角) {x, y} * * @example * // 将图标对齐到容器右下角 * var pos = AlignmentUtils.alignBottomRight( * { x: 10, y: 10, width: 60, height: 60 }, // 容器区域 * { width: 30, height: 30 }, // 图标大小 * { x: -5, y: 5 } // 向左偏移5px,向下偏移5px * ); */ alignBottomRight: function(target, sprite, offset) { return this.alignTo(target, sprite, this.ALIGN.RIGHT, this.ALIGN.BOTTOM, offset); }, /** * 将精灵对齐到目标矩形的顶部居中(快捷方法) * * 🔑 注意:精灵的锚点永远在左上角,居中时会自动减去精灵宽度的一半 * * @param {Object} target - 目标矩形区域 * @param {Object} sprite - 精灵大小(锚点固定在左上角) * @param {Object} [offset] - 偏移量 * @returns {Object} 精灵锚点位置(左上角) {x, y} */ alignTopCenter: function(target, sprite, offset) { return this.alignTo(target, sprite, this.ALIGN.CENTER, this.ALIGN.TOP, offset); }, /** * 将精灵对齐到目标矩形的底部居中(快捷方法) * * 🔑 注意:精灵的锚点永远在左上角,居中时会自动减去精灵宽度的一半 * * @param {Object} target - 目标矩形区域 * @param {Object} sprite - 精灵大小(锚点固定在左上角) * @param {Object} [offset] - 偏移量 * @returns {Object} 精灵锚点位置(左上角) {x, y} */ alignBottomCenter: function(target, sprite, offset) { return this.alignTo(target, sprite, this.ALIGN.CENTER, this.ALIGN.BOTTOM, offset); }, /** * 将精灵对齐到目标矩形的中心(快捷方法) * * 🔑 注意:精灵的锚点永远在左上角,居中时会自动减去精灵宽度和高度的一半 * * @param {Object} target - 目标矩形区域 * @param {Object} sprite - 精灵大小(锚点固定在左上角) * @param {Object} [offset] - 偏移量 * @returns {Object} 精灵锚点位置(左上角) {x, y} */ alignCenter: function(target, sprite, offset) { return this.alignTo(target, sprite, this.ALIGN.CENTER, this.ALIGN.MIDDLE, offset); }, // ============================================================================ // 角落对齐方法 - 精灵的角与目标的角重合 // ============================================================================ /** * 将精灵的左上角对齐到目标的左上角 * * 🔑 注意:精灵的锚点永远在左上角 * 💡 角落对齐:精灵的角与目标的角重合(不是边缘对齐) * * @param {Object} target - 目标矩形区域 * @param {Object} sprite - 精灵大小(锚点固定在左上角) * @param {Object} [offset] - 偏移量 * @returns {Object} 精灵锚点位置(左上角) {x, y} * * @example * // 将子元素的左上角与容器的左上角重合 * var pos = AlignmentUtils.alignCornerTopLeft( * { x: 10, y: 10, width: 100, height: 100 }, * { width: 30, height: 30 } * ); * // 返回: { x: 10, y: 10 } */ alignCornerTopLeft: function(target, sprite, offset) { offset = offset || {}; return { x: target.x + (offset.x || 0), y: target.y + (offset.y || 0) }; }, /** * 将精灵的右上角对齐到目标的右上角 * * 🔑 注意:精灵的锚点永远在左上角,需要减去精灵宽度 * 💡 角落对齐:精灵的角与目标的角重合 * * @param {Object} target - 目标矩形区域 * @param {Object} sprite - 精灵大小(锚点固定在左上角) * @param {Object} [offset] - 偏移量 * @returns {Object} 精灵锚点位置(左上角) {x, y} * * @example * var pos = AlignmentUtils.alignCornerTopRight( * { x: 10, y: 10, width: 100, height: 100 }, * { width: 30, height: 30 } * ); * // 返回: { x: 80, y: 10 } (10 + 100 - 30) */ alignCornerTopRight: function(target, sprite, offset) { offset = offset || {}; return { x: target.x + target.width - sprite.width + (offset.x || 0), y: target.y + (offset.y || 0) }; }, /** * 将精灵的左下角对齐到目标的左下角 * * 🔑 注意:精灵的锚点永远在左上角,需要减去精灵高度 * 💡 角落对齐:精灵的角与目标的角重合 * * @param {Object} target - 目标矩形区域 * @param {Object} sprite - 精灵大小(锚点固定在左上角) * @param {Object} [offset] - 偏移量 * @returns {Object} 精灵锚点位置(左上角) {x, y} * * @example * var pos = AlignmentUtils.alignCornerBottomLeft( * { x: 10, y: 10, width: 100, height: 100 }, * { width: 30, height: 30 } * ); * // 返回: { x: 10, y: 80 } (10 + 100 - 30) */ alignCornerBottomLeft: function(target, sprite, offset) { offset = offset || {}; return { x: target.x + (offset.x || 0), y: target.y + target.height - sprite.height + (offset.y || 0) }; }, /** * 将精灵的右下角对齐到目标的右下角 * * 🔑 注意:精灵的锚点永远在左上角,需要减去精灵宽度和高度 * 💡 角落对齐:精灵的角与目标的角重合 * * @param {Object} target - 目标矩形区域 * @param {Object} sprite - 精灵大小(锚点固定在左上角) * @param {Object} [offset] - 偏移量 * @returns {Object} 精灵锚点位置(左上角) {x, y} * * @example * var pos = AlignmentUtils.alignCornerBottomRight( * { x: 10, y: 10, width: 100, height: 100 }, * { width: 30, height: 30 } * ); * // 返回: { x: 80, y: 80 } */ alignCornerBottomRight: function(target, sprite, offset) { offset = offset || {}; return { x: target.x + target.width - sprite.width + (offset.x || 0), y: target.y + target.height - sprite.height + (offset.y || 0) }; }, /** * 计算多个精灵水平居中的起始X坐标 * * @param {number} containerWidth - 容器宽度 * @param {number} totalItemsWidth - 所有项目的总宽度(包括间距) * @returns {number} 起始X坐标 * * @example * // 3张牌,每张42px,间距5px,容器宽度600px * var startX = AlignmentUtils.centerMultipleItems(600, 42 * 3 + 5 * 2); * // 返回: 258 ((600 - 136) / 2) */ centerMultipleItems: function(containerWidth, totalItemsWidth) { return (containerWidth - totalItemsWidth) / 2; }, /** * 计算矩形区域的中心点 * * @param {Object} rect - 矩形区域 * @param {number} rect.x - 左上角X * @param {number} rect.y - 左上角Y * @param {number} rect.width - 宽度 * @param {number} rect.height - 高度 * @returns {Object} 中心点 {x, y} */ getCenter: function(rect) { return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 }; }, // ============================================================================ // 精灵ID版本 - 自动获取精灵尺寸 // ============================================================================ /** * 将精灵对齐到目标精灵(使用精灵ID,自动获取尺寸) * * 🔑 核心:精灵的锚点永远在左上角,此方法计算锚点应放置的位置 * 💡 优点:无需手动获取精灵尺寸,代码更简洁 * * @param {number} targetSpriteId - 目标精灵ID(会自动获取位置和大小) * @param {number} spriteId - 要对齐的精灵ID(会自动获取大小) * @param {string} horizontalAlign - 水平对齐方式: 'left'|'center'|'right' * @param {string} verticalAlign - 垂直对齐方式: 'top'|'middle'|'bottom' * @param {Object} [offset] - 偏移量(可选) * @param {number} [offset.x=0] - X轴偏移 * @param {number} [offset.y=0] - Y轴偏移 * * @returns {Object|null} 精灵锚点位置(左上角) {x, y},失败返回null * * @example * // 将座位标签(精灵5678)对齐到头像(精灵1234)左下角 * var pos = AlignmentUtils.alignSpriteToSprite( * 1234, // 目标精灵ID(头像) * 5678, // 要对齐的精灵ID(座位标签) * 'left', // 左对齐 * 'bottom', // 底部对齐 * { x: 0, y: 5 } // 向下偏移5px * ); * if (pos) { * SpriteManager.setPosition(5678, pos.x, pos.y); * } */ alignSpriteToSprite: function(targetSpriteId, spriteId, horizontalAlign, verticalAlign, offset) { if (typeof SpriteManager === 'undefined') { console.error('[AlignmentUtils] SpriteManager 未加载,无法使用 alignSpriteToSprite'); return null; } // 获取目标精灵的位置和大小 var targetPos = SpriteManager.getPosition(targetSpriteId); var targetSize = SpriteManager.getSize(targetSpriteId); if (!targetPos || !targetSize) { console.error('[AlignmentUtils] 无法获取目标精灵 ' + targetSpriteId + ' 的位置或大小'); return null; } // 获取要对齐的精灵的大小 var spriteSize = SpriteManager.getSize(spriteId); if (!spriteSize) { console.error('[AlignmentUtils] 无法获取精灵 ' + spriteId + ' 的大小'); return null; } // 构建目标矩形 var target = { x: targetPos.x, y: targetPos.y, width: targetSize.width, height: targetSize.height }; // 调用核心对齐方法 return this.alignTo(target, spriteSize, horizontalAlign, verticalAlign, offset); }, /** * 将精灵对齐到目标精灵的左下角(快捷方法) * * 🔑 注意:精灵的锚点永远在左上角 * 💡 自动获取精灵尺寸,无需手动传入 * * @param {number} targetSpriteId - 目标精灵ID * @param {number} spriteId - 要对齐的精灵ID * @param {Object} [offset] - 偏移量 * @returns {Object|null} 精灵锚点位置(左上角) {x, y} * * @example * var pos = AlignmentUtils.alignSpriteBottomLeft(1234, 5678, { x: 0, y: 5 }); * if (pos) SpriteManager.setPosition(5678, pos.x, pos.y); */ alignSpriteBottomLeft: function(targetSpriteId, spriteId, offset) { return this.alignSpriteToSprite(targetSpriteId, spriteId, this.ALIGN.LEFT, this.ALIGN.BOTTOM, offset); }, /** * 将精灵对齐到目标精灵的右下角(快捷方法) * * 🔑 注意:精灵的锚点永远在左上角,右对齐时会自动减去精灵宽度 * 💡 自动获取精灵尺寸,无需手动传入 * * @param {number} targetSpriteId - 目标精灵ID * @param {number} spriteId - 要对齐的精灵ID * @param {Object} [offset] - 偏移量 * @returns {Object|null} 精灵锚点位置(左上角) {x, y} * * @example * var pos = AlignmentUtils.alignSpriteBottomRight(1234, 5678, { x: -5, y: 5 }); * if (pos) SpriteManager.setPosition(5678, pos.x, pos.y); */ alignSpriteBottomRight: function(targetSpriteId, spriteId, offset) { return this.alignSpriteToSprite(targetSpriteId, spriteId, this.ALIGN.RIGHT, this.ALIGN.BOTTOM, offset); }, /** * 将精灵对齐到目标精灵的顶部居中(快捷方法) * * 🔑 注意:精灵的锚点永远在左上角,居中时会自动减去精灵宽度的一半 * 💡 自动获取精灵尺寸,无需手动传入 * * @param {number} targetSpriteId - 目标精灵ID * @param {number} spriteId - 要对齐的精灵ID * @param {Object} [offset] - 偏移量 * @returns {Object|null} 精灵锚点位置(左上角) {x, y} */ alignSpriteTopCenter: function(targetSpriteId, spriteId, offset) { return this.alignSpriteToSprite(targetSpriteId, spriteId, this.ALIGN.CENTER, this.ALIGN.TOP, offset); }, /** * 将精灵对齐到目标精灵的底部居中(快捷方法) * * 🔑 注意:精灵的锚点永远在左上角,居中时会自动减去精灵宽度的一半 * 💡 自动获取精灵尺寸,无需手动传入 * * @param {number} targetSpriteId - 目标精灵ID * @param {number} spriteId - 要对齐的精灵ID * @param {Object} [offset] - 偏移量 * @returns {Object|null} 精灵锚点位置(左上角) {x, y} */ alignSpriteBottomCenter: function(targetSpriteId, spriteId, offset) { return this.alignSpriteToSprite(targetSpriteId, spriteId, this.ALIGN.CENTER, this.ALIGN.BOTTOM, offset); }, /** * 将精灵对齐到目标精灵的中心(快捷方法) * * 🔑 注意:精灵的锚点永远在左上角,居中时会自动减去精灵宽度和高度的一半 * 💡 自动获取精灵尺寸,无需手动传入 * * @param {number} targetSpriteId - 目标精灵ID * @param {number} spriteId - 要对齐的精灵ID * @param {Object} [offset] - 偏移量 * @returns {Object|null} 精灵锚点位置(左上角) {x, y} */ alignSpriteCenter: function(targetSpriteId, spriteId, offset) { return this.alignSpriteToSprite(targetSpriteId, spriteId, this.ALIGN.CENTER, this.ALIGN.MIDDLE, offset); }, // ============================================================================ // 精灵ID版本 - 角落对齐 // ============================================================================ /** * 将精灵的左上角对齐到目标精灵的左上角 * * 🔑 注意:精灵的锚点永远在左上角 * 💡 角落对齐:精灵的角与目标的角重合 * * @param {number} targetSpriteId - 目标精灵ID * @param {number} spriteId - 要对齐的精灵ID * @param {Object} [offset] - 偏移量 * @returns {Object|null} 精灵锚点位置(左上角) {x, y} */ alignSpriteCornerTopLeft: function(targetSpriteId, spriteId, offset) { if (typeof SpriteManager === 'undefined') { console.error('[AlignmentUtils] SpriteManager 未加载'); return null; } var targetPos = SpriteManager.getPosition(targetSpriteId); var spriteSize = SpriteManager.getSize(spriteId); if (!targetPos || !spriteSize) { console.error('[AlignmentUtils] 无法获取精灵位置或大小'); return null; } offset = offset || {}; return { x: targetPos.x + (offset.x || 0), y: targetPos.y + (offset.y || 0) }; }, /** * 将精灵的右上角对齐到目标精灵的右上角 * * 🔑 注意:精灵的锚点永远在左上角,需要减去精灵宽度 * 💡 角落对齐:精灵的角与目标的角重合 * * @param {number} targetSpriteId - 目标精灵ID * @param {number} spriteId - 要对齐的精灵ID * @param {Object} [offset] - 偏移量 * @returns {Object|null} 精灵锚点位置(左上角) {x, y} */ alignSpriteCornerTopRight: function(targetSpriteId, spriteId, offset) { if (typeof SpriteManager === 'undefined') { console.error('[AlignmentUtils] SpriteManager 未加载'); return null; } var targetPos = SpriteManager.getPosition(targetSpriteId); var targetSize = SpriteManager.getSize(targetSpriteId); var spriteSize = SpriteManager.getSize(spriteId); if (!targetPos || !targetSize || !spriteSize) { console.error('[AlignmentUtils] 无法获取精灵位置或大小'); return null; } offset = offset || {}; return { x: targetPos.x + targetSize.width - spriteSize.width + (offset.x || 0), y: targetPos.y + (offset.y || 0) }; }, /** * 将精灵的左下角对齐到目标精灵的左下角 * * 🔑 注意:精灵的锚点永远在左上角,需要减去精灵高度 * 💡 角落对齐:精灵的角与目标的角重合 * * @param {number} targetSpriteId - 目标精灵ID * @param {number} spriteId - 要对齐的精灵ID * @param {Object} [offset] - 偏移量 * @returns {Object|null} 精灵锚点位置(左上角) {x, y} */ alignSpriteCornerBottomLeft: function(targetSpriteId, spriteId, offset) { if (typeof SpriteManager === 'undefined') { console.error('[AlignmentUtils] SpriteManager 未加载'); return null; } var targetPos = SpriteManager.getPosition(targetSpriteId); var targetSize = SpriteManager.getSize(targetSpriteId); var spriteSize = SpriteManager.getSize(spriteId); if (!targetPos || !targetSize || !spriteSize) { console.error('[AlignmentUtils] 无法获取精灵位置或大小'); return null; } offset = offset || {}; return { x: targetPos.x + (offset.x || 0), y: targetPos.y + targetSize.height - spriteSize.height + (offset.y || 0) }; }, /** * 将精灵的右下角对齐到目标精灵的右下角 * * 🔑 注意:精灵的锚点永远在左上角,需要减去精灵宽度和高度 * 💡 角落对齐:精灵的角与目标的角重合 * * @param {number} targetSpriteId - 目标精灵ID * @param {number} spriteId - 要对齐的精灵ID * @param {Object} [offset] - 偏移量 * @returns {Object|null} 精灵锚点位置(左上角) {x, y} */ alignSpriteCornerBottomRight: function(targetSpriteId, spriteId, offset) { if (typeof SpriteManager === 'undefined') { console.error('[AlignmentUtils] SpriteManager 未加载'); return null; } var targetPos = SpriteManager.getPosition(targetSpriteId); var targetSize = SpriteManager.getSize(targetSpriteId); var spriteSize = SpriteManager.getSize(spriteId); if (!targetPos || !targetSize || !spriteSize) { console.error('[AlignmentUtils] 无法获取精灵位置或大小'); return null; } offset = offset || {}; return { x: targetPos.x + targetSize.width - spriteSize.width + (offset.x || 0), y: targetPos.y + targetSize.height - spriteSize.height + (offset.y || 0) }; }, // ============================================================================ // 批量精灵分布对齐 // ============================================================================ /** * 计算多个精灵水平分布的位置 * * 🔑 核心:精灵的锚点永远在左上角 * 💡 用途:将多个相同大小的精灵水平排列,支持居中/左对齐/右对齐 * * @param {Object} options - 分布选项 * @param {number} options.anchorX - 锚点X坐标(对齐基准点) * @param {number} options.anchorY - 锚点Y坐标(所有精灵的Y坐标) * @param {number} options.count - 精灵数量 * @param {number} options.itemWidth - 每个精灵的宽度 * @param {number} options.itemHeight - 每个精灵的高度 * @param {number} [options.spacing=0] - 精灵之间的间距 * @param {string} [options.anchor='center'] - 对齐方式: 'left'|'center'|'right' * * @returns {Array} 每个精灵的位置数组 [{x, y}, ...] * * @example * // 3张牌水平居中排列 * var positions = AlignmentUtils.distributeHorizontally({ * anchorX: 640, // 屏幕中心X * anchorY: 500, // Y位置 * count: 3, // 3张牌 * itemWidth: 62, // 牌宽 * itemHeight: 86, // 牌高 * spacing: 0, // 无间距 * anchor: 'center' // 居中对齐 * }); * // 返回: [{x: 547, y: 500}, {x: 609, y: 500}, {x: 671, y: 500}] */ distributeHorizontally: function(options) { var anchorX = options.anchorX || 0; var anchorY = options.anchorY || 0; var count = options.count || 0; var itemWidth = options.itemWidth || 0; var itemHeight = options.itemHeight || 0; var spacing = options.spacing || 0; var anchor = options.anchor || 'center'; if (count <= 0) return []; // 计算总宽度 var totalWidth = count * itemWidth + (count - 1) * spacing; // 计算起始X位置(精灵锚点在左上角) var startX; switch (anchor) { case 'left': // 左对齐:第一个精灵的左边缘与锚点对齐 startX = anchorX; break; case 'right': // 右对齐:最后一个精灵的右边缘与锚点对齐 startX = anchorX - totalWidth; break; case 'center': default: // 居中:精灵组的中心与锚点对齐 startX = anchorX - totalWidth / 2; break; } // 生成每个精灵的位置 var positions = []; for (var i = 0; i < count; i++) { positions.push({ x: startX + i * (itemWidth + spacing), y: anchorY }); } return positions; }, /** * 计算多个精灵垂直分布的位置 * * 🔑 核心:精灵的锚点永远在左上角 * 💡 用途:将多个相同大小的精灵垂直排列,支持居中/顶部对齐/底部对齐 * * @param {Object} options - 分布选项 * @param {number} options.anchorX - 锚点X坐标(所有精灵的X坐标) * @param {number} options.anchorY - 锚点Y坐标(对齐基准点) * @param {number} options.count - 精灵数量 * @param {number} options.itemWidth - 每个精灵的宽度 * @param {number} options.itemHeight - 每个精灵的高度 * @param {number} [options.spacing=0] - 精灵之间的间距 * @param {string} [options.anchor='center'] - 对齐方式: 'top'|'center'|'bottom' * * @returns {Array} 每个精灵的位置数组 [{x, y}, ...] * * @example * // 3张牌垂直居中排列 * var positions = AlignmentUtils.distributeVertically({ * anchorX: 100, // X位置 * anchorY: 360, // 屏幕中心Y * count: 3, // 3张牌 * itemWidth: 62, // 牌宽 * itemHeight: 86, // 牌高 * spacing: 2, // 2px间距 * anchor: 'center' // 居中对齐 * }); */ distributeVertically: function(options) { var anchorX = options.anchorX || 0; var anchorY = options.anchorY || 0; var count = options.count || 0; var itemWidth = options.itemWidth || 0; var itemHeight = options.itemHeight || 0; var spacing = options.spacing || 0; var anchor = options.anchor || 'center'; if (count <= 0) return []; // 计算总高度 var totalHeight = count * itemHeight + (count - 1) * spacing; // 计算起始Y位置(精灵锚点在左上角) var startY; switch (anchor) { case 'top': // 顶部对齐:第一个精灵的顶边与锚点对齐 startY = anchorY; break; case 'bottom': // 底部对齐:最后一个精灵的底边与锚点对齐 startY = anchorY - totalHeight; break; case 'center': default: // 居中:精灵组的中心与锚点对齐 startY = anchorY - totalHeight / 2; break; } // 生成每个精灵的位置 var positions = []; for (var i = 0; i < count; i++) { positions.push({ x: anchorX, y: startY + i * (itemHeight + spacing) }); } return positions; }, /** * 计算多个精灵分布的位置(自动选择水平或垂直) * * @param {Object} options - 分布选项 * @param {string} [options.direction='horizontal'] - 分布方向: 'horizontal'|'vertical' * @param {number} options.anchorX - 锚点X坐标 * @param {number} options.anchorY - 锚点Y坐标 * @param {number} options.count - 精灵数量 * @param {number} options.itemWidth - 每个精灵的宽度 * @param {number} options.itemHeight - 每个精灵的高度 * @param {number} [options.spacing=0] - 精灵之间的间距 * @param {string} [options.anchor='center'] - 对齐方式 * * @returns {Array} 每个精灵的位置数组 [{x, y}, ...] */ distribute: function(options) { var direction = options.direction || 'horizontal'; if (direction === 'vertical') { return this.distributeVertically(options); } else { return this.distributeHorizontally(options); } } }; // ============================================================================ // 导出 // ============================================================================ if (typeof module !== 'undefined' && module.exports) { module.exports = AlignmentUtils; } else if (typeof window !== 'undefined') { window.AlignmentUtils = AlignmentUtils; }