All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 1m13s
660 lines
18 KiB
TypeScript
660 lines
18 KiB
TypeScript
import { _decorator, Component, Mask, Node, tween, Tween, UITransform, v3, Vec3 } from "cc";
|
||
import { ICON_HEIGHT, ICON_STATE, 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;// 是否收到停止数据
|
||
speedDataComplete: boolean = false;// 速度数据是否完成
|
||
stopSpeedData: number[][] = null;// 停止时的速度数据
|
||
hasStopScrollWork: boolean = false;
|
||
state: ROLLER_STATE = ROLLER_STATE.STOP; // 当前状态
|
||
isFastSpin: boolean = false; // 是否快速旋转
|
||
isManualStop: boolean = false; // 是否手动停止
|
||
|
||
constructor() {
|
||
this.speedNode = new Node('SpeedNode');
|
||
}
|
||
|
||
/**
|
||
* 重置滚轮信息
|
||
* 保留hitBlocks数据,重置其他状态
|
||
*/
|
||
resetLxInfo() {
|
||
this.receiveStopData = false;
|
||
this.speed = 0;
|
||
this.stopSpeedData = null;
|
||
this.isManualStop = false;
|
||
this.hasStopScrollWork = 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 _CroSymbols: 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._CroSymbols = null;
|
||
|
||
if (!EDITOR) {
|
||
// 运行时初始化
|
||
this._info.speedNode.parent = null;
|
||
this.node.addChild(this._info.speedNode);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 初始化滚轮并创建初始图
|
||
* @param id 滚轮ID
|
||
* @param data 初始图标数据数组
|
||
* @param CroSymbols n*1 Icon数据
|
||
*/
|
||
initRollerWithIcon(id: number, data: number[], CroSymbols?: any) {
|
||
this.iconFactory.init();
|
||
this.initRoller(id);
|
||
// 创建图标
|
||
this._CroSymbols = CroSymbols;
|
||
this.createInitIcons(data);
|
||
}
|
||
|
||
/**
|
||
* 创建初始图标,包括处理n*1 Icon
|
||
* @param data 图标数据数组
|
||
*/
|
||
createInitIcons(data: number[]) {
|
||
// 清空现有的图标和映射
|
||
this._allIcons.clear();
|
||
this._posToIconKey.clear();
|
||
|
||
// 先处理n*1 Icon
|
||
if (this._CroSymbols && Object.keys(this._CroSymbols).length > 0) {
|
||
// 创建n*1 Icon
|
||
this.createSpecialIcons();
|
||
}
|
||
|
||
// 然后创建普通图标(跳过已被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);
|
||
}
|
||
}
|
||
|
||
createSpecialIcons() {
|
||
// 直接使用已有的符号数据,避免重复处理
|
||
let processedPoses = new Set<string>(); // 用于跟踪已处理的符号ID
|
||
// 遍历所有位置的符号数据
|
||
|
||
|
||
for (let pos in this._CroSymbols) {
|
||
let iconSpecialMsg = this._CroSymbols[pos];
|
||
// 如果这个符号ID已经处理过,跳过
|
||
if (processedPoses.has(iconSpecialMsg.id)) continue;
|
||
// 标记这个符号ID为已处理
|
||
processedPoses.add(iconSpecialMsg.id);
|
||
// 创建特殊图标
|
||
this.createOneSpecialIcon(iconSpecialMsg);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建n*1 Icon
|
||
* @param pos 位置索引
|
||
* @param iconIndex 图标类型索引
|
||
* @param height 图标高度
|
||
* @param frameType 框架类型
|
||
* @returns 创建的图标节点
|
||
*/
|
||
createOneSpecialIcon(iconSpecialMsg: any): Node {
|
||
let startPos = iconSpecialMsg.startPos;
|
||
let endPos = iconSpecialMsg.endPos;
|
||
let height = iconSpecialMsg.lHeight;
|
||
let iconIndex = iconSpecialMsg.iconIndex;
|
||
let frameType = iconSpecialMsg.Type;
|
||
// 生成图标ID
|
||
let iconKey = this.generateIconKey(startPos, height, endPos);
|
||
// 创建图标节点
|
||
let icon = this.iconFactory.icfactoryCreateIcon(iconIndex);
|
||
// 初始化图标
|
||
icon.getComponent(Icon).initIcon(iconIndex, height, iconKey, frameType, this._rollerId);
|
||
// 设置位置
|
||
let position = this.getIconPosition(startPos, height);
|
||
this._content.addChild(icon);
|
||
|
||
icon.setPosition(position);
|
||
icon.getComponent(Icon).changeIconState(ICON_STATE.IDLE);
|
||
|
||
// 存储图标节点
|
||
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): Node {
|
||
// 生成图标ID
|
||
let iconKey = this.generateIconKey(pos, 1, pos);
|
||
// 创建图标节点
|
||
let icon = this.iconFactory.icfactoryCreateIcon(iconIndex);
|
||
// 初始化图标
|
||
icon.getComponent(Icon).initIcon(iconIndex, 1, iconKey, 0, this._rollerId);
|
||
// 设置位置
|
||
let position = this.getIconPosition(pos, 1);
|
||
this._content.addChild(icon);
|
||
icon.setPosition(position);
|
||
icon.getComponent(Icon).changeIconState(ICON_STATE.IDLE);
|
||
|
||
// 存储图标节点
|
||
this._allIcons.set(iconKey, icon);
|
||
this._posToIconKey.set(pos, iconKey);
|
||
return icon;
|
||
}
|
||
|
||
/**
|
||
* 设置n*1 Icon数据
|
||
* @param CroSymbols n*1 Icon数据
|
||
*/
|
||
setCroSymbols(CroSymbols: any) {
|
||
this._CroSymbols = CroSymbols;
|
||
}
|
||
|
||
/**
|
||
* 设置滚轮图标规则
|
||
* @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.05, -1000],
|
||
[0.1, 0],
|
||
[0.05, 6000]
|
||
] :
|
||
[
|
||
[0.05 * this._rollerId, 0],
|
||
// [0.1, -1000],
|
||
[0.1, 0],
|
||
[0.1, 5000]
|
||
];
|
||
}
|
||
|
||
|
||
/**
|
||
* 开始滚动
|
||
*/
|
||
// 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.6]];
|
||
// this.changeState(ROLLER_STATE.UNIFORM);
|
||
|
||
// this.tweenSpeed(uniformSpeedData, () => {
|
||
// this._info.speedDataComplete = true;
|
||
// let rollerSpeed = this.row * this.iconHeight / 0.1 * 175;
|
||
// let stopSpeedData = this._info.isFastSpin ? [[0, rollerSpeed]] : [[0.4, 5500]];
|
||
// if (this._info.receiveStopData) {
|
||
// this.stopScrollWork(stopSpeedData);
|
||
// }
|
||
// });
|
||
// });
|
||
// }
|
||
|
||
/**
|
||
* 停止滚动
|
||
* @param data 停止时的图标数据
|
||
*/
|
||
// stopScroll(data: number[], stopSpeedData: number[][]) {
|
||
// this._stopData = data;
|
||
// this._info.receiveStopData = true;
|
||
|
||
// // 如果速度数据已完成,执行停止逻辑
|
||
// if (this._info.speedDataComplete) {
|
||
// this.stopScrollWork(stopSpeedData);
|
||
// }
|
||
// }
|
||
|
||
|
||
/**
|
||
* 执行停止滚动的具体工作
|
||
*/
|
||
stopScrollWork(stopSpeedData: number[][]) {
|
||
// 如果不是手动停止,执行正常的停止逻辑
|
||
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);
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 执行速度变化动画
|
||
* @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) {
|
||
if (this._info.state === state) return;
|
||
|
||
this._info.state = state;
|
||
|
||
// 状态变化处理
|
||
switch (state) {
|
||
case ROLLER_STATE.LAST_PAGE_CREATE:
|
||
this.createLastPage();
|
||
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) {
|
||
return Math.floor(Math.random() * this.iconFactory.getIconNum());
|
||
}
|
||
return rule[Math.floor(Math.random() * rule.length)];
|
||
}
|
||
|
||
/**
|
||
* 判断是否显示快速图标效果
|
||
*/
|
||
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[]): void;
|
||
|
||
abstract createLastPage(): 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 chanegeIconAndFrameType(data: any[]): void;
|
||
|
||
abstract createNewIconTop(createDatas: number[][], CroSymbols: any): void;
|
||
|
||
abstract iconFallDown(data: number[], CroSymbols: any, inPan: boolean): void;
|
||
} |