Files

850 lines
33 KiB
JavaScript

/**
* 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<Object>} 每个精灵的位置数组 [{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<Object>} 每个精灵的位置数组 [{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<Object>} 每个精灵的位置数组 [{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;
}