rp_10012/assets/Game/scripts/game/BaseRoller.ts
TJH af88dc5992
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 2s
快速模式节奏
2025-10-14 18:11:35 +08:00

687 lines
19 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, 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<string, Node> = new Map(); // 存储所有图标节点通过唯一key访问
protected _posToIconKey: Map<number, string> = new Map(); // 位置到图标key的映射
// 性能优化相关缓存
protected _cachedUITransform: UITransform = null;
protected _positionCache: Map<number, Vec3> = 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<string>(); // 用于跟踪已处理的符号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, 0],
[0, 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(XnInfo): void;
abstract chanegeIconAndFrameType(data: any[]): void;
abstract createNewIconTop(createDatas: number[], XnInfo: any): void;
abstract iconFallDown(data: number[], crossSymbols: any): void;
}