370 lines
12 KiB
TypeScript
370 lines
12 KiB
TypeScript
import { _decorator, Component, Node } from 'cc';
|
|
import * as cc from 'cc';
|
|
import { BezierCurveAnimation } from './BezierCurveAnimation';
|
|
const { ccclass, property, requireComponent } = _decorator;
|
|
|
|
/** 旋转抽奖方向 */
|
|
export enum RotatingLotteryDirection {
|
|
顺时针,
|
|
逆时针
|
|
}
|
|
|
|
/** 旋转抽奖 */
|
|
@ccclass('RotatingLottery')
|
|
@requireComponent(BezierCurveAnimation)
|
|
export class RotatingLottery extends Component {
|
|
/* --------------- 属性 --------------- */
|
|
/** 旋转方向 */
|
|
@property({ displayName: '旋转方向', type: cc.Enum(RotatingLotteryDirection) })
|
|
dire: RotatingLotteryDirection = null;
|
|
|
|
/** 旋转指针 */
|
|
@property({ displayName: '旋转指针' })
|
|
rotateArrowB = false;
|
|
|
|
/** 旋转对象 */
|
|
@property({ displayName: '旋转对象', type: cc.Node })
|
|
rotateNode: cc.Node = null;
|
|
|
|
/** 内容容器 */
|
|
@property({ displayName: '内容容器', type: cc.Node })
|
|
contentNode: cc.Node = null;
|
|
|
|
/** 当前下标变更事件 */
|
|
@property({ displayName: '当前下标变更事件', tooltip: '(下标_indexN, 跳过状态_jumpB)', type: cc.EventHandler })
|
|
indexChangeEvent = new cc.EventHandler();
|
|
|
|
/** 旋转结束事件 */
|
|
@property({ displayName: '旋转结束事件', type: cc.EventHandler })
|
|
rotateEndEvent = new cc.EventHandler();
|
|
/* --------------- private --------------- */
|
|
/** 滚动状态 */
|
|
private _scrollB = false;
|
|
/** 循环滚动状态 */
|
|
private _loopScrollB = false;
|
|
/** 循环缓动 */
|
|
private _loopTween: cc.Tween<any>;
|
|
/** 当前滚动配置 */
|
|
private _scrollConfig: RotatingLotteryScrollConfig;
|
|
/** 跳过状态 */
|
|
private _jumpB = false;
|
|
/** 目标角度 */
|
|
private _targetAngleN: number;
|
|
/** 上次曲线 Y */
|
|
private _lastCurveYN = 0;
|
|
/** 内容角度区间 */
|
|
private _contentAngleNs: number[] = [];
|
|
/** 特殊角度下标 */
|
|
private _specialAngleIndexN: number;
|
|
/** 当前下标 */
|
|
private _currIndexN = 0;
|
|
/* --------------- public --------------- */
|
|
/** 曲线组件 */
|
|
curveComp: BezierCurveAnimation;
|
|
/** 当前中心下标 */
|
|
get currIndexN() {
|
|
return this._currIndexN;
|
|
}
|
|
set currIndexN(valueN_) {
|
|
this._setCurrIndexN(valueN_);
|
|
}
|
|
/* ------------------------------- get/set ------------------------------- */
|
|
private _setCurrIndexN(valueN_: number) {
|
|
if (valueN_ === this._currIndexN) {
|
|
return;
|
|
}
|
|
this._currIndexN = valueN_;
|
|
this.indexChangeEvent.emit([this._currIndexN, this._jumpB]);
|
|
// logger.log('当前选中', this._currIndexN);
|
|
}
|
|
/* ------------------------------- 生命周期 ------------------------------- */
|
|
onLoad() {
|
|
this._initData();
|
|
this._initEvent();
|
|
}
|
|
/* ------------------------------- 功能 ------------------------------- */
|
|
/** 初始化数据 */
|
|
private _initData(): void {
|
|
this.curveComp = this.getComponent(BezierCurveAnimation);
|
|
|
|
// 设置更新事件
|
|
let updateEvent = new cc.EventHandler();
|
|
updateEvent.component = cc.js.getClassName(this);
|
|
updateEvent.handler = '_eventUpdate';
|
|
updateEvent.target = this.node;
|
|
this.curveComp.updateEventAS.push(updateEvent);
|
|
|
|
// 设置结束事件
|
|
let endEvent = new cc.EventHandler();
|
|
endEvent.component = cc.js.getClassName(this);
|
|
endEvent.handler = '_eventEnd';
|
|
endEvent.target = this.node;
|
|
this.curveComp.endEventAS.push(endEvent);
|
|
|
|
this._updateAngleRange();
|
|
}
|
|
|
|
/** 初始化事件 */
|
|
private _initEvent(): void {
|
|
this.node.on(cc.Node.EventType.CHILD_ADDED, this._nodeChildAdded, this);
|
|
this.node.on(cc.Node.EventType.CHILD_REMOVED, this._nodeChildRemoved, this);
|
|
}
|
|
|
|
/** 重置 */
|
|
private _reset() {
|
|
this._updateAngleRange();
|
|
}
|
|
|
|
/** 更新角度区间 */
|
|
private _updateAngleRange(): void {
|
|
let leftNode: cc.Node;
|
|
let rightNode: cc.Node;
|
|
this.contentNode.children.forEach((v, kN) => {
|
|
// 获取左右节点
|
|
leftNode = this.contentNode.children[kN + 1 === this.contentNode.children.length ? 0 : kN + 1];
|
|
rightNode = this.contentNode.children[kN - 1 < 0 ? this.contentNode.children.length - 1 : kN - 1];
|
|
|
|
// 获取当前节点最大角度
|
|
if (leftNode.angle < v.angle) {
|
|
this._contentAngleNs[kN] =
|
|
v.angle + Math.min((360 + leftNode.angle - v.angle) * 0.5, (v.angle - rightNode.angle) * 0.5);
|
|
} else if (v.angle > rightNode.angle) {
|
|
this._contentAngleNs[kN] =
|
|
v.angle + Math.min((leftNode.angle - v.angle) * 0.5, (v.angle - rightNode.angle) * 0.5);
|
|
} else {
|
|
this._specialAngleIndexN = kN;
|
|
this._contentAngleNs[kN] =
|
|
v.angle + Math.min((leftNode.angle - v.angle) * 0.5, (v.angle + (360 - rightNode.angle)) * 0.5);
|
|
}
|
|
});
|
|
}
|
|
|
|
/** 获取当前下标 */
|
|
private _getCurrIndex(): number {
|
|
let angleN = this.rotateNode.angle % 360;
|
|
if (angleN < 0) {
|
|
angleN += 360;
|
|
}
|
|
|
|
let resultN: number;
|
|
for (let kN = 0, lenN = this._contentAngleNs.length; kN < lenN; ++kN) {
|
|
if (angleN < this._contentAngleNs[kN]) {
|
|
resultN = kN;
|
|
break;
|
|
}
|
|
}
|
|
if (resultN === undefined) {
|
|
resultN = this._specialAngleIndexN;
|
|
}
|
|
if (!this.rotateArrowB) {
|
|
resultN = this._contentAngleNs.length - 1 - resultN;
|
|
}
|
|
return resultN;
|
|
}
|
|
|
|
/**
|
|
* 更新运动角度
|
|
* @param indexN_ 目标下标
|
|
*/
|
|
private _updateMoveAngle(indexN_: number): void {
|
|
/** 目标节点角度 */
|
|
let targetNodeAngleN = this.contentNode.children[indexN_].angle;
|
|
/** 旋转节点角度 */
|
|
let rotateNodeAngleN = (this.rotateNode.angle %= 360);
|
|
|
|
// 计算最终角度
|
|
if (this.dire === RotatingLotteryDirection.顺时针) {
|
|
// 旋转指针
|
|
if (this.rotateArrowB) {
|
|
this._targetAngleN = -(360 - targetNodeAngleN) - rotateNodeAngleN;
|
|
if (this._targetAngleN > rotateNodeAngleN) {
|
|
this._targetAngleN -= 360;
|
|
}
|
|
}
|
|
// 旋转转盘
|
|
else {
|
|
this._targetAngleN = -targetNodeAngleN - rotateNodeAngleN;
|
|
if (this._targetAngleN > rotateNodeAngleN) {
|
|
this._targetAngleN -= 360;
|
|
}
|
|
}
|
|
this._targetAngleN %= 360;
|
|
|
|
// 添加圈数
|
|
if (!this._jumpB && this._scrollConfig) {
|
|
this._targetAngleN -= this._scrollConfig.turnN * 360;
|
|
this._targetAngleN += this._scrollConfig.offsetAngleN;
|
|
}
|
|
} else {
|
|
// 旋转指针
|
|
if (this.rotateArrowB) {
|
|
this._targetAngleN = targetNodeAngleN - rotateNodeAngleN;
|
|
if (this._targetAngleN < rotateNodeAngleN) {
|
|
this._targetAngleN += 360;
|
|
}
|
|
}
|
|
// 旋转转盘
|
|
else {
|
|
this._targetAngleN = 360 - targetNodeAngleN - rotateNodeAngleN;
|
|
if (this._targetAngleN < rotateNodeAngleN) {
|
|
this._targetAngleN += 360;
|
|
}
|
|
}
|
|
this._targetAngleN %= 360;
|
|
|
|
// 添加圈数
|
|
if (!this._jumpB && this._scrollConfig) {
|
|
this._targetAngleN += this._scrollConfig.turnN * 360;
|
|
this._targetAngleN += this._scrollConfig.offsetAngleN;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** 停止循环 */
|
|
private _stopLoop(): void {
|
|
this._loopTween.stop();
|
|
this._loopTween = null;
|
|
this._loopScrollB = false;
|
|
}
|
|
|
|
/**
|
|
* 循环滚动
|
|
* @param speedN_ 速度/秒
|
|
* @param timeSN_ 时间(秒),不填则一直滚动
|
|
*/
|
|
loop(speedN_: number, timeSN_?: number): void {
|
|
if (this._scrollB) {
|
|
return;
|
|
}
|
|
this._scrollB = true;
|
|
this._loopScrollB = true;
|
|
let angleN: number;
|
|
this._loopTween = cc
|
|
.tween({ valueN: 0, lastValueN: 0 })
|
|
.repeatForever(
|
|
cc.tween().by(
|
|
1,
|
|
{
|
|
valueN: 1
|
|
},
|
|
{
|
|
onUpdate: (target?: any, ratioN?: number) => {
|
|
if (!this.isValid) {
|
|
return;
|
|
}
|
|
angleN = (target.valueN - target.lastValueN) * speedN_;
|
|
if (this.rotateArrowB) {
|
|
if (this.dire === RotatingLotteryDirection.顺时针) {
|
|
angleN = -angleN;
|
|
}
|
|
} else {
|
|
angleN = -angleN;
|
|
if (this.dire === RotatingLotteryDirection.逆时针) {
|
|
angleN = -angleN;
|
|
}
|
|
}
|
|
this.rotateNode.angle += angleN;
|
|
target.lastValueN = target.valueN;
|
|
this.currIndexN = this._getCurrIndex();
|
|
}
|
|
}
|
|
)
|
|
)
|
|
.start();
|
|
if (timeSN_ !== undefined) {
|
|
this.scheduleOnce(() => {
|
|
this._stopLoop();
|
|
}, timeSN_);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 跳转到指定下标
|
|
* @param indexN_ 目标下标
|
|
* @returns
|
|
*/
|
|
jump(indexN_: number): void {
|
|
if (this._scrollB && !this._loopScrollB) {
|
|
return;
|
|
}
|
|
this._scrollB = true;
|
|
this._jumpB = true;
|
|
|
|
// 停止循环滚动
|
|
if (this._loopScrollB) {
|
|
this._stopLoop();
|
|
}
|
|
|
|
// 更新角度
|
|
this._updateMoveAngle(indexN_);
|
|
|
|
// 直接跳转
|
|
this.rotateNode.angle += this._targetAngleN;
|
|
this.currIndexN = this._getCurrIndex();
|
|
this._scrollB = false;
|
|
this._jumpB = false;
|
|
}
|
|
|
|
/**
|
|
* 滚动到指定下标
|
|
* @param indexN_ 目标下标
|
|
* @param scrollConfig_ 滚动配置
|
|
* @returns
|
|
*/
|
|
scroll(indexN_: number, scrollConfig_?: RotatingLotteryScrollConfig): void {
|
|
if (this._scrollB && !this._loopScrollB) {
|
|
return;
|
|
}
|
|
this._scrollB = true;
|
|
this._scrollConfig = new RotatingLotteryScrollConfig(scrollConfig_);
|
|
|
|
// 停止循环滚动
|
|
if (this._loopScrollB) {
|
|
this._stopLoop();
|
|
}
|
|
|
|
// 更新角度
|
|
this._lastCurveYN = 0;
|
|
this._updateMoveAngle(indexN_);
|
|
|
|
// 开始缓动
|
|
this.curveComp.startTween(this._scrollConfig.tweenIndexNS);
|
|
}
|
|
/* ------------------------------- 自定义事件 ------------------------------- */
|
|
private _eventUpdate(yN_: number, indexN_: number): void {
|
|
this.rotateNode.angle += this._targetAngleN * (yN_ - this._lastCurveYN);
|
|
this._lastCurveYN = yN_;
|
|
this.currIndexN = this._getCurrIndex();
|
|
// cc.log('缓动更新', yN_, indexN_, y2N_, yN_ - this._lastCurveYN);
|
|
}
|
|
|
|
private _eventEnd(): void {
|
|
this._scrollB = false;
|
|
this.rotateEndEvent.emit([]);
|
|
this._scrollConfig.endCBF?.();
|
|
// cc.log('缓动结束');
|
|
}
|
|
/* ------------------------------- 节点事件 ------------------------------- */
|
|
private _nodeChildAdded(): void {
|
|
this._reset();
|
|
}
|
|
private _nodeChildRemoved(): void {
|
|
this._reset();
|
|
}
|
|
}
|
|
|
|
/** 滚动配置 */
|
|
class RotatingLotteryScrollConfig {
|
|
constructor(init_?: RotatingLotteryScrollConfig) {
|
|
Object.assign(this, init_);
|
|
}
|
|
/** 缓动队列 */
|
|
tweenIndexNS?: number[];
|
|
/** 圈数 */
|
|
turnN? = 1;
|
|
/** 偏移角度 */
|
|
offsetAngleN? = 0;
|
|
/** 结束回调 */
|
|
endCBF?: () => void;
|
|
}
|