rp_10012/assets/Game/scripts/game/HRoller.ts
2025-08-26 15:48:20 +08:00

817 lines
25 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 负责单个滚轮的创建、滚动、停止等逻辑
*/
import {
_decorator,
Node,
UITransform,
Vec2,
v2,
Vec3,
v3,
tween,
Tween,
Mask,
} from 'cc';
import { IconFactory } from './IconFactory';
import { Icon } from './Icon';
import { BaseRoller, ROLLER_STATE } from './BaseRoller';
import { ROLLER_EVENT } from './Define';
import { callGameApi } from 'db://assets/Loading/scripts/comm';
let { ccclass, property, executeInEditMode } = _decorator;
/**
* 滚轮组件类
*/
@ccclass('HRoller')
@executeInEditMode
export class HRoller extends BaseRoller {
// 编辑器属性
@property({ tooltip: '列数' })
col: number = 3;
// 本地格式化相关
// @property
// _format = false;
// @property({ tooltip: '本地格式化' })
// get format(): boolean {
// return this._format;
// }
// set format(b: boolean) {
// this._format = b;
// this.resizeContentSize();
// if (!this.iconFactory) {
// console.error('IconFactory没有设置');
// return;
// }
// // 重新创建图标
// this._content.removeAllChildren();
// this._info.icons = [];
// this._allIcons.clear();
// this._posToIconKey.clear();
// // 清除位置缓存 - 添加这一行
// this._positionCache.clear();
// for (let i = 0; i < this.col; i++) {
// let randomIndex = Math.floor(Math.random() * this.iconFactory.getIconNum());
// this.createNormalIcon(i, randomIndex);
// }
// }
_cachedContentWidth: number = 0;
_cachedAnchorX: number = 0;
/**
* 创建滚轮实例
* @param id 滚轮ID
* @param row 行数
* @param iconWidth 图标宽度
* @param iconHeight 图标高度
* @param iconFactory 图标工厂实例
* @param anchor 锚点位置
* @returns 新创建的滚轮实例
*/
static create(
id: number,
col: number,
iconWidth: number,
iconHeight: number,
iconFactory: IconFactory,
anchor: Vec2 = v2(0.5, 0.5)
): HRoller {
let rollerNode = new Node(`Roller${id}`);
rollerNode.addComponent(UITransform);
let roller = rollerNode.addComponent(HRoller);
roller._rollerId = id;
roller.col = col;
roller.iconWidth = iconWidth;
roller.iconHeight = iconHeight;
roller.iconFactory = iconFactory;
rollerNode.getComponent(UITransform).setAnchorPoint(anchor);
roller.resizeContentSize();
roller.initRoller(id);
return roller;
}
/**
* 设置节点尺寸和位置
*/
setupNodesSizeAndPosition() {
let totalWidth = this.iconWidth * this.col;
let anchorPoint = this.node.getComponent(UITransform).anchorPoint;
// 设置主节点尺寸
this.node.getComponent(UITransform).setContentSize(totalWidth, this.iconHeight);
// 设置view节点
this._view.setPosition(0, 0);
this._view.getComponent(UITransform).setContentSize(totalWidth, this.iconHeight);
this._view.getComponent(UITransform).setAnchorPoint(anchorPoint);
this._view.getComponent(UITransform).height *= 2;
// 设置content节点
this._content.setPosition(0, 0);
this._content.getComponent(UITransform).setContentSize(totalWidth, this.iconHeight);
this._content.getComponent(UITransform).setAnchorPoint(anchorPoint);
this._content.removeAllChildren();
}
/**
* 获取图标的实际坐标
* @param pos 图标位置索引
* @param height 图标高度
* @returns 图标的世界坐标
*/
getIconPosition(pos: number, size: number = 1): Vec3 {
// 创建缓存键
let cacheKey = size > 1 ? (pos + 1) * 1000 + size : pos;
if (this._positionCache.has(cacheKey)) {
return this._positionCache.get(cacheKey).clone();
}
// 确保缓存数据已初始化
if (!this._cachedContentWidth) {
this._cachedContentWidth = this._content.getComponent(UITransform).width;
this._cachedAnchorX = this._content.getComponent(UITransform).anchorX;
}
// 计算基准位置
let contentWidth = this._cachedContentWidth;
let anchorX = this._cachedAnchorX;
// 计算最左边位置
let leftX = -contentWidth * (1 - anchorX);
// 计算第一个位置的中心x坐标
let firstCenterX = leftX + this.iconWidth / 2;
// 计算当前位置的中心X坐标
let centerX = firstCenterX + pos * this.iconWidth;
// 对于大图标,需要调整位置
let finalX = centerX;
if (size > 1) {
// 大图标的中心点应该下移,使其顶部对齐格子
// 对于高度为3的图标中心点应该下移1个格子高度
finalX = centerX + (size - 1) * this.iconWidth / 2;
}
// 创建最终位置
let position = v3(finalX, 0, 0);
// 缓存结果
this._positionCache.set(cacheKey, position.clone());
return position;
}
/**
* 初始化性能优化相关的缓存
*/
initCache() {
this._cachedUITransform = this._content.getComponent(UITransform);
this._cachedContentWidth = this._cachedUITransform.width;
this._cachedAnchorX = this._cachedUITransform.anchorX;
}
/**
* 收集现有图标
*/
collectExistingIcons() {
for (let i = 0; i < this.col; i++) {
let icon = this._allIcons.get(this._posToIconKey.get(i));
if (icon) {
this._allIcons.delete(this._posToIconKey.get(i));
this._posToIconKey.delete(i);
this._info.icons.push(icon);
}
}
}
/**
* 手动停止滚动
* @param data 停止时的图标数据
*/
async manualStopScroll(data: number[], XnInfo: any) {
if (this._info.isManualStop ||
this._info.state === ROLLER_STATE.STOP) {
return;
}
this.changeState(ROLLER_STATE.STOP);
this._info.resetLxInfo();
this._stopData = data;
this._info.isManualStop = true;
Tween.stopAllByTarget(this._info.speedNode);
// 直接回收所有动态图标
while (this._info.icons.length > 0) {
let icon = this._info.icons.pop();
this.iconFactory.recycleIcon(icon);
}
// 回收固定位置图标
for (let i = 0; i < this.col; i++) {
let icon = this._allIcons.get(this._posToIconKey.get(i));
if (icon) {
this._allIcons.delete(this._posToIconKey.get(i));
this._posToIconKey.delete(i);
this.iconFactory.recycleIcon(icon);
}
}
this._allIcons.clear();
this._posToIconKey.clear();
this.createInitIcons(data, XnInfo, true);
// 为所有创建的图标播放动画
for (let icon of this._allIcons.values()) {
icon.getComponent(Icon).playSpawnAni();
}
}
/**
* 创建最后一页图标
*/
createLastPage(XnInfo) {
let data = this._stopData;
if (!data) return;
// 计算最右边icon基准位置
let rightX = this.getIconPosition(this.col - 1).x;
let icons = this._info.icons;
if (icons.length > 0) {
rightX = Math.max(rightX, this.findHighestIconXorY(icons));
}
// 清除已有的位置映射,准备重新创建
this._allIcons.clear();
this._posToIconKey.clear();
// 从左到右依次创建图标从位置0开始
for (let i = 0; i < data.length; i++) {
// 如果当前位置已被特殊图标占用,跳过
if (this._posToIconKey.has(i)) continue;
// 生成图标ID
let pos = i;
let iconIndex = data[i];
let iconKey = this.generateIconKey(pos, 1, pos);
let multi = 1
if (XnInfo.Top) {
for (let i = 0; i < XnInfo.Top.length; i++) {
if (XnInfo.Top[i].StartIndex == pos) {
multi = XnInfo.Top[i].N
}
}
}
// 创建图标节点
let icon = this.iconFactory.icfactoryCreateIcon(iconIndex);
icon.getComponent(Icon).initIcon(iconIndex, 1, iconKey, 0, this._rollerId, multi);
// 计算位置
let x = rightX + (pos + 1) * this.iconWidth;
icon.setPosition(x, 0, 0);
this._content.addChild(icon);
// 存储图标节点
this._allIcons.set(iconKey, icon);
this._posToIconKey.set(pos, iconKey);
}
this.node.emit(ROLLER_EVENT.LAST_PAGE_CREATE, this._rollerId);
}
/**
* 检查图标是否超出显示范围
*/
checkDeadLine(icon: Node): boolean {
if (!this._cachedContentWidth) {
this._cachedContentWidth = this._content.getComponent(UITransform).width;
this._cachedAnchorX = this._content.getComponent(UITransform).anchorX;
}
let iconComponent = icon.getComponent(Icon);
let lheight = iconComponent.lHeight;
let iconBody = (lheight - 1) * this.iconWidth + this.iconWidth / 2;
let deadLine = -(this._cachedContentWidth * this._cachedAnchorX) - iconBody;
return icon.position.x <= deadLine;
}
/**
* 补充新的图标
*/
suppleIcon() {
let bornLine = this.getIconPosition(this.col - 1).x;
let icons = this._info.icons;
// 找到最右边的的图标X坐标
let rightX = this.findHighestIconXorY(icons);
// 如果最右边的图标低于出生线,创建新图标
if (rightX < bornLine) {
this.createRandomIcon();
}
}
/**
* 创建随机图标
*/
createRandomIcon() {
let iconIndex = this.getRandomIconIndex();
let newX = this.computeNewIconXorY();
let icon = this.iconFactory.icfactoryCreateIcon(iconIndex);
// 设置快速图标效果
if (this.shouldShowFastIcon()) {
icon.getComponent(Icon).showFastIcon(true);
}
icon.setPosition(newX, 0, 0);
this._content.addChild(icon);
this._info.icons.push(icon);
this.node.emit(ROLLER_EVENT.ON_R_ICON_CREATE, this._rollerId, icon);
}
/**
* 找到最右边的图标X坐标
* @param icons 图标数组
* @returns 最右边的X坐标
*/
findHighestIconXorY(icons: Node[]): number {
return icons.reduce((maxX, icon) => {
if (!icon || !icon.active) return maxX;
let iconComponent = icon.getComponent(Icon);
if (!iconComponent) return maxX;
let lHeight = iconComponent.lHeight || 1;
let iconX = icon.position.x;
// 对于高度大于1的图标考虑其顶部位置
if (lHeight > 1) {
// 计算图标顶部位置:当前位置 + 高度差 * 图标高度 / 2
return Math.max(maxX, iconX + (lHeight - 1) * this.iconWidth / 2);
} else {
return Math.max(maxX, iconX);
}
}, -999);
}
/**
* 计算新图标的X坐标
*/
computeNewIconXorY(): number {
let icons = this._info.icons;
// 如果没有图标,使用初始位置
if (!icons.length) {
return this.getIconPosition(this.col - 1).x + this.iconWidth;
}
// 找到最右边的图标X坐标
let rightX = this.findHighestIconXorY(icons);
// 新图标位置 = 最高图标位置 + 图标高度
return rightX + this.iconWidth;
}
/**
* 清理资源
*/
onDestroy() {
this._cachedUITransform = null;
this._cachedContentWidth = 0;
this._cachedAnchorX = 0;
this._positionCache.clear();
}
/**
* 滚轮移动
* @param dt 时间增量
*/
rollerMove(dt: number) {
// 计算移动向量
let speed = this._info.speed;
let move = speed * dt;
let moveVec = v3(move, 0, 0);
// 更新动态图标位置
this._info.icons.forEach(icon => {
if (!icon || !icon.isValid) return;
if (icon.active) {
let newPosition = icon.position.clone().subtract(moveVec);
icon.setPosition(newPosition);
}
});
// 获取所有图标
let allIcons = Array.from(this._allIcons.values());
allIcons.forEach(icon => {
if (!icon || !icon.isValid) return;
let newPosition = icon.position.clone().subtract(moveVec);
icon.setPosition(newPosition);
});
}
/**
* 回收滚轮图标
*/
recycleRollerIcon() {
// 回收动态图标
let icons = this._info.icons;
for (let i = icons.length - 1; i >= 0; i--) {
let icon = icons[i];
if (!icon || !icon.isValid) {
icons.splice(i, 1);
continue;
}
if (this.checkDeadLine(icon)) {
// 从数组中移除
icons.splice(i, 1);
// 回收图标
this.iconFactory.recycleIcon(icon);
}
}
// 回收固定位置图标(非最后一页创建状态)
if (this._info.state !== ROLLER_STATE.LAST_PAGE_CREATE) {
for (let i = 0; i < this.col; i++) {
let iconKey = this._posToIconKey.get(i);
if (!iconKey) continue;
let icon = this._allIcons.get(iconKey);
if (!icon || !icon.isValid) {
this._posToIconKey.delete(i);
this._allIcons.delete(iconKey);
continue;
}
if (this.checkDeadLine(icon)) {
// 从映射中移除
this._allIcons.delete(iconKey);
this._posToIconKey.delete(i);
// 回收图标
this.iconFactory.recycleIcon(icon);
}
}
}
// 处理特殊图标n*1图标
if (this._info.state !== ROLLER_STATE.LAST_PAGE_CREATE) {
// 获取所有特殊图标的key
let specialIconKeys = Array.from(this._allIcons.keys())
.filter(key => key.startsWith('large_'));
for (let iconKey of specialIconKeys) {
let icon = this._allIcons.get(iconKey);
if (!icon || !icon.isValid) {
this._allIcons.delete(iconKey);
continue;
}
if (this.checkDeadLine(icon)) {
// 从映射中移除
this._allIcons.delete(iconKey);
// 移除所有关联的位置映射
for (let [pos, key] of this._posToIconKey.entries()) {
if (key === iconKey) {
this._posToIconKey.delete(pos);
}
}
// 回收图标
this.iconFactory.recycleIcon(icon);
}
}
}
}
/**
* 停止处理
*/
stopProcess() {
let rightIcon = this.getIconPosition(this.col - 1);
let stopline = rightIcon.x - 100; // 偏移量100
let topX = this.findHighestIconXorY(Array.from(this._allIcons.values()));
if (topX <= stopline) {
this.changeState(ROLLER_STATE.BOUNCE);
this.playBounceAnimation();
}
}
/**
* 播放回弹动画
*/
playBounceAnimation() {
let time = 0.1;
let offset = 50;
// 获取所有图标
let allIcons = Array.from(this._allIcons.values());
allIcons.forEach(icon => {
if (!icon || !icon.isValid) return;
let iconComponent = icon.getComponent(Icon);
if (!iconComponent) return;
// 获取图标的起始位置和高度
let startPos = iconComponent.startPos;
let lHeight = iconComponent.lHeight || 1;
// 获取图标应该在的位置
let position = this.getIconPosition(startPos, lHeight);
// 设置初始位置(向下偏移)
// icon.setPosition(position.add(v3(-offset, 0, 0)));
icon.setPosition(position);
iconComponent.playSpawnAni();
// 创建回弹动画
// tween(icon)
// .by(time, { position: v3(offset, 0, 0) })
// .start();
});
// 延迟切换到停止状态
this.scheduleOnce(() => {
this.changeState(ROLLER_STATE.STOP);
}, time);
}
/**
* 消除逻辑
* 一定是在静止状态消除的
* @param deleteMsg 删除信息
* */
deleteIconNode(positions: number[]) {
// 记录被处理过的图标键
let processedPos = new Set<number>();
// 处理每个位置
for (let pos of positions) {
// 如果此图标已处理过,跳过
if (processedPos.has(pos)) {
continue;
}
let iconKey = this._posToIconKey.get(pos);
if (!iconKey) {
console.error('deleteIconNode iconKey is null', pos);
continue;
}
let iconNode = this._allIcons.get(iconKey);
if (!iconNode || !iconNode.isValid) {
console.error('deleteIconNode iconNode is null', pos);
continue;
}
let iconComponent = iconNode.getComponent(Icon);
if (!iconComponent) {
console.error('deleteIconNode iconComponent is null', pos);
continue;
}
let startPos = iconComponent.startPos;
// 从allIcons中删除图标
this._allIcons.delete(iconKey);
let height = iconComponent.lHeight || 1;
// 移除所有关联的位置映射
if (height > 1) {
// 对于n*1图标需要清除所有占用的位置
for (let i = 0; i < height; i++) {
this._posToIconKey.delete(startPos + i);
// 标记此图标已处理
processedPos.add(startPos + i);
}
} else {
this._posToIconKey.delete(pos);
// 标记此图标已处理
processedPos.add(pos);
}
iconComponent.playWinAni(true);
iconComponent.playDeleteAni();
this.scheduleOnce(() => {
this.iconFactory.recycleIcon(iconNode);
}, 1.2)
}
this.node.emit(ROLLER_EVENT.ICON_DELETED, this._rollerId);
}
playFrameTypeChangeAni(positions: number[]): void {
for (let pos of positions) {
let iconKey = this._posToIconKey.get(pos);
if (!iconKey) continue;
let iconNode = this._allIcons.get(iconKey);
if (!iconNode || !iconNode.isValid) continue;
let iconComponent = iconNode.getComponent(Icon);
if (!iconComponent) continue;
iconComponent.playWinAni(true);
}
}
chanegeIconAndFrameType(data: any[]): void {
return;
}
/**
* 创建新icon
* @param createMsg 信息
*/
createNewIconTop(createDatas: number[], XnInfo: any) {
// 获取所有图标
let rightX = this.getIconPosition(this.col - 1, 1).x;
for (let i = 0; i < createDatas.length; i++) {
let pos = this.col + i + 1;
let iconIndex = createDatas[i];
let iconKey = this.generateIconKey(pos, 1, pos);
let icon = this.iconFactory.icfactoryCreateIcon(iconIndex);
let multi = 1
if (XnInfo.Top && iconIndex == 2) {
for (let i = 0; i < XnInfo.Top.length; i++) {
if (XnInfo.Top[i].StartIndex == pos - createDatas.length - 1) {
multi = XnInfo.Top[i].N
}
}
}
icon.getComponent(Icon).initIcon(iconIndex, 1, iconKey, 0, this._rollerId, multi);
let x = rightX + (i + 1) * this.iconWidth;
icon.setPosition(x, 0, 0);
this._content.addChild(icon);
this._allIcons.set(iconKey, icon);
this._posToIconKey.set(pos, iconKey);
}
this.node.emit(ROLLER_EVENT.ICON_CREATE, this._rollerId);
}
/** icon进行掉落移动 */
iconFallDown(data: number[], crossSymbols: any) {
// 更新所有icon的iconKey和posToIconKey
let updates = [];
let sortNewIconStartPos = this.getNewIconsStartPos(data, crossSymbols);
let sortIcons = this.getSortIcons();
for (let i = 0; i < sortNewIconStartPos.length; i++) {
let newStartPos = sortNewIconStartPos[i];
let oldIcon = sortIcons[i];
let oldIconStartPos = oldIcon.startPos;
if (oldIconStartPos === newStartPos) {
continue;
}
let oldIconNode = oldIcon.icon;
let oldIconComponent = oldIcon.component;
let oldIconkey = oldIconComponent.iconKey;
let lHeight = oldIconComponent.lHeight || 1;
let newIconkey = this.generateIconKey(newStartPos, 1, newStartPos);
let newX = this.getIconPosition(newStartPos, lHeight).x;
let oldX = oldIconNode.position.x;
updates.push({
node: oldIconNode,
component: oldIconComponent,
oldKey: oldIconkey,
oldStartPos: oldIconStartPos,
newStartPos: newStartPos,
newKey: newIconkey,
height: lHeight
})
let time = 0.3;
tween(oldIconNode)
.to(time, { position: v3(newX, 0, 0) })
.start();
}
for (let update of updates) {
this._allIcons.delete(update.oldKey);
for (let i = 0; i < update.height; i++) {
this._posToIconKey.delete(update.oldStartPos + i);
}
}
// 再添加所有新映射
for (let update of updates) {
// 添加新的映射
update.component.iconKey = update.newKey;
this._allIcons.set(update.newKey, update.node);
for (let i = 0; i < update.height; i++) {
this._posToIconKey.set(update.newStartPos + i, update.newKey);
}
}
this.scheduleOnce(() => {
this.node.emit(ROLLER_EVENT.ICON_FALLEN, this._rollerId);
}, 0.5)
}
getSortIcons() {
let iconInfos: { icon: Node, component: Icon, startPos: number }[] = [];
// 收集所有图标信息
for (let [iconKey, iconNode] of this._allIcons.entries()) {
if (!iconNode || !iconNode.isValid) continue;
let iconComponent = iconNode.getComponent(Icon);
if (!iconComponent) continue;
// 记录图标信息
iconInfos.push({
icon: iconNode,
component: iconComponent,
startPos: iconComponent.startPos
});
}
// 按startPos从小到大排序
iconInfos.sort((a, b) => a.startPos - b.startPos);
return iconInfos;
}
getNewIconsStartPos(data: number[], crossSymbols: any) {
// 存储所有图标的startPos
let startPositions: number[] = [];
// 记录已处理的位置
let processedPositions = new Set<number>();
// 记录已经处理的pos
let processedPos = new Set<number>();
// 首先处理不规则图标n*1图标
if (crossSymbols) {
for (let pos in crossSymbols) {
let id = crossSymbols[pos].id;
if (processedPos.has(id)) continue;
processedPos.add(id);
let iconSpecialMsg = crossSymbols[pos];
let startPos = iconSpecialMsg.startPos;
let endPos = iconSpecialMsg.endPos;
// 添加不规则图标的startPos
startPositions.push(startPos);
// 标记所有被占用的位置
for (let i = startPos; i <= endPos; i++) {
processedPositions.add(i);
}
}
}
// 然后处理常规图标
for (let i = 0; i < data.length; i++) {
// 如果该位置未被处理(不是不规则图标的一部分)
if (!processedPositions.has(i)) {
startPositions.push(i);
}
}
// 从小到大排序
startPositions.sort((a, b) => a - b);
return startPositions;
}
}