/** * 负责单个滚轮的创建、滚动、停止等逻辑 */ 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 = 0 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(); // 处理每个位置 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); } showMultiMove(multiMove: Node) { let allIcons = Array.from(this._allIcons.values()); allIcons.forEach(icon => { if (!icon || !icon.isValid) return; icon.getComponent(Icon).playNormalMultiMove(multiMove) }); } 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 = 0 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(); // 记录已经处理的pos let processedPos = new Set(); // 首先处理不规则图标(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; } }