import { _decorator, Component, Mask, Node, tween, Tween, UITransform, v3, Vec3 } from "cc"; import { ICON_HEIGHT, ICON_MAP, ICON_WIDTH, ROLLER_EVENT } from "./Define"; import { IconFactory } from "./IconFactory"; import { EDITOR } from "cc/env"; import { Icon } from "./Icon"; let { ccclass, property, executeInEditMode } = _decorator; /** * 滚轮状态枚举 */ export enum ROLLER_STATE { ACCELERATE = 1, // 加速 UNIFORM = 2, // 匀速 DECELERATE = 3, // 减速 LAST_PAGE_CREATE = 4, // 最后一页的创建 BOUNCE = 5, // 回弹 STOP = 6, // 停止 } /** * 滚轮信息类 * 存储滚轮运行时的各种状态和数据 */ export class Info { icons: Node[] = []; // 图标节点数组 iconRule: number[] = []; // 滚轮图标创建规则 speed: number = 0; // 当前速度 speedNode: Node; // 速度控制节点 receiveStopData: boolean = false;// 是否收到停止数据 StopXnInfo: any = null;// 是否收到停止数据 speedDataComplete: boolean = false;// 速度数据是否完成 stopSpeedData: number[][] = null;// 停止时的速度数据 state: ROLLER_STATE = ROLLER_STATE.STOP; // 当前状态 isFastSpin: boolean = false; // 是否快速旋转 isManualStop: boolean = false; // 是否手动停止 constructor() { this.speedNode = new Node('SpeedNode'); } /** * 重置滚轮信息 * 保留hitBlocks数据,重置其他状态 */ resetLxInfo() { this.receiveStopData = false; this.StopXnInfo = null this.speed = 0; this.stopSpeedData = null; this.isManualStop = false; this.speedDataComplete = false; } } /** * 滚轮组件类 */ @ccclass('BaseRoller') @executeInEditMode export abstract class BaseRoller extends Component { // 编辑器属性 @property({ tooltip: '图标宽度' }) iconWidth: number = ICON_WIDTH; @property({ tooltip: '图标高度' }) iconHeight: number = ICON_HEIGHT; @property({ type: IconFactory, tooltip: '图标工厂' }) iconFactory: IconFactory = null; // 保护属性,子类可访问 protected _rollerId: number = 0; protected _view: Node = null; protected _content: Node = null; protected _info: Info = new Info(); protected _stopData: number[] = []; protected _crossSymbols: any = null; // 存储当前滚轮的n*1 Icon信息 // 统一的图标管理结构 protected _allIcons: Map = new Map(); // 存储所有图标节点,通过唯一key访问 protected _posToIconKey: Map = new Map(); // 位置到图标key的映射 // 性能优化相关缓存 protected _cachedUITransform: UITransform = null; protected _positionCache: Map = new Map(); /** * 获取内容节点 * @returns 滚轮的内容节点 */ getContentNode(): Node { return this._content; } resizeContentSize() { // 初始化view节点 this._view = this.node.getChildByName('view'); if (!this._view) { this._view = this.createViewNode(); } // 初始化content节点 this._content = this._view.getChildByName('content'); if (!this._content) { this._content = this.createContentNode(); } // 设置尺寸和位置 this.setupNodesSizeAndPosition(); // 初始化缓存 this.initCache(); } /** * 创建view节点 */ createViewNode(): Node { let node = new Node('view'); // node.addComponent(Mask); node.addComponent(UITransform); this.node.addChild(node); return node; } /** * 创建content节点 */ createContentNode(): Node { let node = new Node('content'); node.addComponent(UITransform); this._view.addChild(node); return node; } /** * 通过位置获取图标节点 * @param pos 位置索引 * @returns 对应位置的图标节点 */ getIconNode(pos: number): Node { let iconId = this._posToIconKey.get(pos); if (iconId) { return this._allIcons.get(iconId); } return null; } /** * 为图标生成唯一ID * @param pos 位置索引 * @param size 图标大小 * @returns 唯一key字符串 */ generateIconKey(startPos: number, size: number, endPos: number): string { if (size > 1) { return `large_${startPos}_${endPos}_${size}`; } else { return `normal_${startPos}_${endPos}_${size}`; } } /** * 初始化滚轮 * @param rollerId 滚轮ID */ initRoller(rollerId: number) { this._rollerId = rollerId; this._view = this.node.getChildByName('view'); this._content = this._view.getChildByName('content'); this._content.removeAllChildren(); // 清空图标映射 this._allIcons.clear(); this._posToIconKey.clear(); this._crossSymbols = null; if (!EDITOR) { // 运行时初始化 this._info.speedNode.parent = null; this.node.addChild(this._info.speedNode); } } /** * 初始化滚轮并创建初始图 * @param id 滚轮ID * @param data 初始图标数据数组 * @param crossSymbols n*1 Icon数据 */ initRollerWithIcon(id: number, data: number[], crossSymbols?: any, XnInfo?: any) { this.iconFactory.init(); this.initRoller(id); // 创建图标 this._crossSymbols = crossSymbols; this.createInitIcons(data, XnInfo, id == 0); } /** * 创建初始图标,包括处理n*1 Icon * @param data 图标数据数组 */ createInitIcons(data: number[], XnInfo: any, isHroll: boolean) { // 清空现有的图标和映射 this._allIcons.clear(); this._posToIconKey.clear(); // 先处理n*1 Icon if (this._crossSymbols && Object.keys(this._crossSymbols).length > 0) { // 创建n*1 Icon this.createSpecialIcons(XnInfo); } // 然后创建普通图标(跳过已被n*1 Icon占用的位置) for (let i = 0; i < data.length; i++) { // 如果位置已被占用,跳过 if (this._posToIconKey.has(i)) continue; let iconIndex = data[i]; this.createNormalIcon(i, iconIndex, XnInfo, isHroll); } } createSpecialIcons(XnInfo: any) { // 直接使用已有的符号数据,避免重复处理 let processedPoses = new Set(); // 用于跟踪已处理的符号ID // 遍历所有位置的符号数据 for (let pos in this._crossSymbols) { let iconSpecialMsg = this._crossSymbols[pos]; // 如果这个符号ID已经处理过,跳过 if (processedPoses.has(iconSpecialMsg.id)) continue; // 标记这个符号ID为已处理 processedPoses.add(iconSpecialMsg.id); // 创建特殊图标 this.createOneSpecialIcon(iconSpecialMsg, XnInfo); } } /** * 创建n*1 Icon * @param pos 位置索引 * @param iconIndex 图标类型索引 * @param height 图标高度 * @param frameType 框架类型 * @returns 创建的图标节点 */ createOneSpecialIcon(iconSpecialMsg: any, XnInfo: any): Node { let startPos = iconSpecialMsg.startPos; let endPos = iconSpecialMsg.endPos; let height = iconSpecialMsg.lHeight; let iconIndex = iconSpecialMsg.iconIndex; let frameType = iconSpecialMsg.frameType; // 生成图标ID let iconKey = this.generateIconKey(startPos, height, endPos); // 创建图标节点 let icon = this.iconFactory.icfactoryCreateIcon(iconIndex); // 初始化图标 let multi = 0 if (XnInfo.Bottom && iconIndex == 2) { for (let i = 0; i < XnInfo.Bottom.length; i++) { if (XnInfo.Bottom[i].StartIndex == startPos + (this._rollerId - 1) * 5) { multi = XnInfo.Bottom[i].N } } } icon.getComponent(Icon).initIcon(iconIndex, height, iconKey, frameType, this._rollerId, multi); // 设置位置 let position = this.getIconPosition(startPos, height); this._content.addChild(icon); icon.setPosition(position); // 存储图标节点 this._allIcons.set(iconKey, icon); // 设置位置映射 for (let i = 0; i < height; i++) { this._posToIconKey.set(startPos + i, iconKey); } return icon; } /** * 创建普通图标 * @param pos 位置索引 * @param iconIndex 图标类型索引 * @returns 创建的图标节点 */ createNormalIcon(pos: number, iconIndex: number, XnInfo: any, isHroll: boolean): Node { // 生成图标ID let iconKey = this.generateIconKey(pos, 1, pos); // 创建图标节点 let icon = this.iconFactory.icfactoryCreateIcon(iconIndex); // 初始化图标 let multi = 1 if (XnInfo.Top || XnInfo.Bottom) { let XnInfoData = isHroll ? XnInfo.Top : XnInfo.Bottom if (XnInfoData) { for (let i = 0; i < XnInfoData.length; i++) { if (XnInfoData[i].StartIndex == pos + (isHroll ? 0 : (this._rollerId - 1) * 5)) { multi = XnInfoData[i].N } } } } icon.getComponent(Icon).initIcon(iconIndex, 1, iconKey, 0, this._rollerId, multi); // 设置位置 let position = this.getIconPosition(pos, 1); this._content.addChild(icon); icon.setPosition(position); // 存储图标节点 this._allIcons.set(iconKey, icon); this._posToIconKey.set(pos, iconKey); return icon; } /** * 设置n*1 Icon数据 * @param crossSymbols n*1 Icon数据 */ setCrossSymbols(crossSymbols: any) { this._crossSymbols = crossSymbols; } /** * 设置滚轮图标规则 * @param rollerIconRule 图标规则数组 */ setRollerIconRule(rollerIconRule: number[]) { this._info.iconRule = rollerIconRule; } /** * 设置快速旋转模式 * @param isFastSpin 是否快速旋转 */ setFastSpin(isFastSpin: boolean) { if (this._info.isFastSpin === isFastSpin) return; this._info.isFastSpin = isFastSpin; } /** * 重置滚轮信息 */ resetInfo() { this._stopData = null; this._info.resetLxInfo(); } /** * 获取初始速度数据 * @returns 速度数据数组 */ getInitialSpeedData(): number[][] { return this._info.isFastSpin ? [ // [0.1, -1000], [0.1, 0], [0.1, 4000] ] : [ [0.05 * this._rollerId, 0], // [0.1, -1000], [0.1, 0], [0.1, 4000] ]; } /** * 开始滚动 */ startScroll() { if (this._info.state != ROLLER_STATE.STOP) { return; } // 收集现有图标 this.collectExistingIcons(); // 设置速度数据 let speedData = this.getInitialSpeedData(); // 开始加速 this.changeState(ROLLER_STATE.ACCELERATE); this.tweenSpeed(speedData, () => { // 进入匀速阶段 let uniformSpeedData = this._info.isFastSpin ? [[0]] : [[0.3]]; this.changeState(ROLLER_STATE.UNIFORM); this.tweenSpeed(uniformSpeedData, () => { this._info.speedDataComplete = true; let stopSpeedData = this._info.isFastSpin ? [[0, 6000]] : [[0.1, 3500]]; if (this._info.receiveStopData) { this.stopScrollWork(stopSpeedData, this._info.StopXnInfo); } }); }); } /** * 停止滚动 * @param data 停止时的图标数据 */ stopScroll(data: number[], stopSpeedData: number[][], XnInfo: any) { this._stopData = data; this._info.receiveStopData = true; this._info.StopXnInfo = XnInfo // 如果速度数据已完成,执行停止逻辑 if (this._info.speedDataComplete) { this.stopScrollWork(stopSpeedData, this._info.StopXnInfo); } } /** * 执行停止滚动的具体工作 */ stopScrollWork(stopSpeedData: number[][], XnInfo) { // 如果不是手动停止,执行正常的停止逻辑 if (!this._info.isManualStop) { this.changeState(ROLLER_STATE.DECELERATE); // 设置停止时的速度数据 this._info.stopSpeedData = stopSpeedData; // 执行减速动画 this.tweenSpeed(this._info.stopSpeedData, () => { this.changeState(ROLLER_STATE.LAST_PAGE_CREATE, XnInfo); }); } } /** * 执行速度变化动画 * @param speedData 速度数据数组 * @param callback 完成回调 * @param obsv 回调的this指向 */ tweenSpeed(speedData: number[][], callback: Function = null, obsv: any = null) { let speedNode = this._info.speedNode; let speed = this._info.speed; // 停止现有动画 Tween.stopAllByTarget(speedNode); speedNode.setPosition(0, speed); // 创建动画序列 let timeline = 0; speedData.forEach((cur, i) => { if (cur.length === 2) { // 速度变化动画 this.createSpeedChangeTween(speedNode, cur, timeline, i === speedData.length - 1, callback, obsv); timeline += cur[0]; } else if (cur.length === 1) { // 延时 this.createDelayTween(speedNode, cur[0], timeline, i === speedData.length - 1, callback, obsv); timeline += cur[0]; } }); } /** * 创建速度变化动画 */ createSpeedChangeTween( speedNode: Node, data: number[], startTime: number, isLast: boolean, callback: Function, obsv: any ) { tween(speedNode) .delay(startTime) .to(data[0], { position: v3(0, data[1], 0) }, { onUpdate: () => { this._info.speed = Math.floor(speedNode.position.y); } }) .call(() => { if (isLast && callback) { callback.call(obsv); } }) .start(); } /** * 创建延时动画 */ createDelayTween( speedNode: Node, delay: number, startTime: number, isLast: boolean, callback: Function, obsv: any ) { tween(speedNode) .delay(startTime) .delay(delay) .call(() => { if (isLast && callback) { callback.call(obsv); } }) .start(); } /** * 改变滚轮状态 * @param state 目标状态 */ changeState(state: ROLLER_STATE, XnInfo?: any) { if (this._info.state === state) return; this._info.state = state; // 状态变化处理 switch (state) { case ROLLER_STATE.LAST_PAGE_CREATE: this.createLastPage(XnInfo); break; case ROLLER_STATE.BOUNCE: this.emitRollerEvent(ROLLER_EVENT.ROLLER_BOUNCE); break; case ROLLER_STATE.STOP: this.emitRollerEvent(ROLLER_EVENT.ROLLER_STOP); break; } } /** * 发送滚轮事件 * @param eventName 事件名称 */ emitRollerEvent(eventName: string) { this.node.emit(eventName, this._rollerId); } /** * 获取随机图标索引 */ getRandomIconIndex(): number { let rule = this._info.iconRule; if (!rule?.length) { let res = Math.floor(Math.random() * this.iconFactory.getIconNum()); if (res == 2) { res = 3 } return res } let res = rule[Math.floor(Math.random() * rule.length)]; if (res == 2) { res = 3 } return res } /** * 判断是否显示快速图标效果 */ shouldShowFastIcon(): boolean { return this._info.state === ROLLER_STATE.ACCELERATE || this._info.state === ROLLER_STATE.UNIFORM; } /** * 检查是否正在滚动 */ isScroll(): boolean { return this._info.state !== ROLLER_STATE.STOP; } /** * 更新方法 */ localUpdate(dt: number) { if (EDITOR) return; let state = this._info.state; if (this.shouldUpdateRoller(state)) { this.updateRollerMovement(dt, state); } } /** * 判断是否需要更新滚轮 */ shouldUpdateRoller(state: ROLLER_STATE): boolean { return state !== ROLLER_STATE.STOP && state !== ROLLER_STATE.BOUNCE; } /** * 更新滚轮移动 */ updateRollerMovement(dt: number, state: ROLLER_STATE) { this.rollerMove(dt); this.recycleRollerIcon(); switch (state) { case ROLLER_STATE.LAST_PAGE_CREATE: this.stopProcess(); break; case ROLLER_STATE.ACCELERATE: case ROLLER_STATE.UNIFORM: case ROLLER_STATE.DECELERATE: this.suppleIcon(); break; } } /** * 获取图标的世界坐标 */ getIconWorldPosition(pos: number): Vec3 { let iconKey = this._posToIconKey.get(pos); let icon = this._allIcons.get(iconKey); if (!icon) { return null; // 返回安全的默认值 } return this._content.getComponent(UITransform).convertToWorldSpaceAR(icon.position); } /** *改变速度 */ changeSpeed() { this._info.speed = 7000; } abstract setupNodesSizeAndPosition(): void; abstract getIconPosition(pos: number, size: number): Vec3; abstract initCache(): void; abstract collectExistingIcons(): void; abstract manualStopScroll(data: number[], XnInfo: any): void; abstract createLastPage(XnInfo: any): void; abstract checkDeadLine(icon: Node): boolean; abstract suppleIcon(): void; abstract findHighestIconXorY(icons: Node[]): number; abstract computeNewIconXorY(): number; abstract createRandomIcon(): void; abstract rollerMove(dt: number): void; abstract recycleRollerIcon(): void; abstract stopProcess(): void; abstract playBounceAnimation(): void; abstract deleteIconNode(deleteMsg: any): void; abstract playFrameTypeChangeAni(positions: number[]): void; abstract showMultiMove(multiNum: number): void; abstract chanegeIconAndFrameType(data: any[]): void; abstract createNewIconTop(createDatas: number[], XnInfo: any): void; abstract iconFallDown(data: number[], crossSymbols: any): void; }