first commit

This commit is contained in:
yuanfengxin
2022-07-13 18:35:31 +08:00
commit 8bcf8cf385
19 changed files with 3394 additions and 0 deletions

248
assets/BezierCurve.ts Normal file
View File

@@ -0,0 +1,248 @@
import * as cc from 'cc';
/** 贝塞尔曲线 */
class BezierCurve {
constructor(pointAs_?: cc.Vec3[]) {
this.pointV3S = pointAs_;
this._resetData();
}
/* --------------- private --------------- */
private _distanceNS: number[] = [];
private _funcFSS: Function[][] = [];
/** 控制点 */
private _pointV3S!: cc.Vec3[];
/* --------------- public --------------- */
/** 控制点 */
get pointV3S() {
return this._pointV3S;
}
set pointV3S(valueV3S) {
this._pointV3S = valueV3S;
this._resetData();
}
/* ------------------------------- 功能函数 ------------------------------- */
/** 重置数据 */
private _resetData(): void {
if (this._pointV3S.length < 2) {
return;
}
/** 首尾相等 */
let equalsB = this._pointV3S[0].strictEquals(this._pointV3S[this._pointV3S.length - 1]);
/** 总距离 */
let sumDistanceN = 0;
/** 临时变量 */
let tempV3: cc.Vec3;
let temp2V3: cc.Vec3;
let temp3V3: cc.Vec3;
let temp4V3: cc.Vec3;
for (let kN = 0, lenN = this._pointV3S.length - 1; kN < lenN; kN++) {
if (kN === 0) {
tempV3 = equalsB ? this._pointV3S[this._pointV3S.length - 2] : this._pointV3S[0];
} else {
tempV3 = this._pointV3S[kN - 1];
}
temp2V3 = this._pointV3S[kN];
temp3V3 = this._pointV3S[kN + 1];
if (kN + 1 === this._pointV3S.length - 1) {
temp4V3 = equalsB ? this._pointV3S[1] : this._pointV3S[this._pointV3S.length - 1];
} else {
temp4V3 = this._pointV3S[kN + 2];
}
this._funcFSS[kN] = [];
[this._funcFSS[kN][0], this._funcFSS[kN][1]] = this._curve(tempV3, temp2V3, temp3V3, temp4V3);
sumDistanceN += this._gaussLegendre(this._funcFSS[kN][1] as any, 0, 1);
this._distanceNS[kN] = sumDistanceN;
}
}
/**
* 递归阶乘
* @param valueN_
* @returns
*/
private _factorial(valueN_: number): number {
let resultN = 1;
for (let kN = 2; kN <= valueN_; ++kN) {
resultN *= kN;
}
return resultN;
}
/**
* 高斯—勒让德积分公式可以用较少节点数得到高精度的计算结果
* @param valueF_ 曲线长度变化率,用于匀速曲线运动
* @param valueN_ 左区间
* @param value2N_ 右区间
* @returns
*/
private _gaussLegendre(valueF_: (vN: number) => number, valueN_: number, value2N_: number): number {
// 3次系数
let gauFactor = {
0.7745966692: 0.555555556,
0: 0.8888888889
};
// 5次系数
// let GauFactor = {0.9061798459:0.2369268851,0.5384693101:0.4786286705,0:0.5688888889}
// 积分
let gauSumN = 0;
let keyN: number;
for (let key in gauFactor) {
if (Object.prototype.hasOwnProperty.call(gauFactor, key)) {
keyN = Number(key);
let v = gauFactor[key];
let t = ((value2N_ - valueN_) * keyN + valueN_ + value2N_) / 2;
let der = valueF_(t);
gauSumN = gauSumN + der * v;
if (keyN > 0) {
t = ((value2N_ - valueN_) * -key + valueN_ + value2N_) / 2;
der = valueF_(t);
gauSumN = gauSumN + der * v;
}
}
}
return (gauSumN * (value2N_ - valueN_)) / 2;
}
private _curve(pointV3_: cc.Vec3, point2V3_: cc.Vec3, point3V3_: cc.Vec3, point4V3_: cc.Vec3) {
// 基本样条线插值算法
// 弹性
let sN = 0.5;
// 计算三次样条线函数系数
let bV3 = pointV3_
.clone()
.multiplyScalar(-sN)
.add(point2V3_.clone().multiplyScalar(2 - sN))
.add(point3V3_.clone().multiplyScalar(sN - 2))
.add(point4V3_.clone().multiplyScalar(sN));
let b2V3 = pointV3_
.clone()
.multiplyScalar(2 * sN)
.add(point2V3_.clone().multiplyScalar(sN - 3))
.add(point3V3_.clone().multiplyScalar(3 - 2 * sN))
.add(point4V3_.clone().multiplyScalar(-sN));
let b3V3 = pointV3_.clone().multiplyScalar(-sN).add(point3V3_.clone().multiplyScalar(sN));
let b4V3 = point2V3_;
// 函数曲线
function fx(xN: number) {
return bV3
.clone()
.multiplyScalar(Math.pow(xN, 3))
.add(b2V3.clone().multiplyScalar(Math.pow(xN, 2)))
.add(b3V3.clone().multiplyScalar(xN))
.add(b4V3.clone());
}
// 曲线长度变化率,用于匀速曲线运动
function ds(xN: number) {
let derV3 = bV3
.clone()
.multiplyScalar(3 * Math.pow(xN, 2))
.add(b2V3.clone().multiplyScalar(2 * xN))
.add(b3V3.clone());
return Math.sqrt(Math.pow(derV3.x, 2) + Math.pow(derV3.y, 2) + Math.pow(derV3.z, 2));
}
return [fx, ds];
}
/**
* 获取曲线上某点的位置
* @param posN_ min: 0, max: 1
*/
point(posN_: number): cc.Vec3 | null {
let posN = posN_;
if (this._pointV3S.length < 2) {
return null;
}
if (posN < 0 || posN > 1) {
posN = posN < 0 ? 0 : 1;
}
// 首个和最后点直接返回
if (posN === 0) {
return this._pointV3S[0];
} else if (posN === 1) {
return this._pointV3S[this._pointV3S.length - 1];
}
let resultV3 = cc.v3();
let indexN = this._pointV3S.length - 1;
this._pointV3S.forEach((v, kS) => {
if (!kS) {
resultV3.x += v.x * Math.pow(1 - posN, indexN - kS) * Math.pow(posN, kS);
resultV3.y += v.y * Math.pow(1 - posN, indexN - kS) * Math.pow(posN, kS);
resultV3.z += v.z * Math.pow(1 - posN, indexN - kS) * Math.pow(posN, kS);
} else {
resultV3.x +=
(this._factorial(indexN) / this._factorial(kS) / this._factorial(indexN - kS)) *
v.x *
Math.pow(1 - posN, indexN - kS) *
Math.pow(posN, kS);
resultV3.y +=
(this._factorial(indexN) / this._factorial(kS) / this._factorial(indexN - kS)) *
v.y *
Math.pow(1 - posN, indexN - kS) *
Math.pow(posN, kS);
resultV3.z +=
(this._factorial(indexN) / this._factorial(kS) / this._factorial(indexN - kS)) *
v.z *
Math.pow(1 - posN, indexN - kS) *
Math.pow(posN, kS);
}
});
return resultV3;
}
/** 匀速点 */
uniformPoint(posN_: number): cc.Vec3 | null {
let posN = posN_;
if (this._pointV3S.length < 2) {
return null;
}
if (posN < 0 || posN > 1) {
posN = posN < 0 ? 0 : 1;
}
// 首个和最后点直接返回
if (posN === 0) {
return this._pointV3S[0];
} else if (posN === 1) {
return this._pointV3S[this._pointV3S.length - 1];
}
// 平均距离
let averDistN = posN * this._distanceNS[this._pointV3S.length - 2];
let indexN = 0;
let beyondN = 0;
let percentN = 0;
for (let kN = 0; kN < this._pointV3S.length - 1; kN++) {
if (averDistN < this._distanceNS[kN]) {
let preDis = kN === 0 ? 0 : this._distanceNS[kN - 1];
indexN = kN;
beyondN = averDistN - preDis;
percentN = beyondN / (this._distanceNS[kN] - preDis);
break;
}
}
// 牛顿切线法求根
let aN = percentN;
let bN: number;
// 最多迭代6次
for (let i = 0; i < 6; i++) {
let actualLen = this._gaussLegendre(this._funcFSS[indexN][1] as any, 0, aN);
bN = aN - (actualLen - beyondN) / this._funcFSS[indexN][1](aN);
if (Math.abs(aN - bN) < 0.0001) {
break;
}
aN = bN;
}
percentN = bN;
return this._funcFSS[indexN][0](percentN);
}
}
export default BezierCurve;

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.22",
"importer": "typescript",
"imported": true,
"uuid": "c34af995-9c5c-4522-a007-1bc0d11fec95",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,140 @@
import { _decorator, Component, Node } from 'cc';
import * as cc from 'cc';
import { EDITOR } from 'cc/env';
import BezierCurve from './BezierCurve';
const { ccclass, property } = _decorator;
/** 缓动枚举 */
let easingEnum = {};
{
let tempN = 0;
for (let kS in cc.easing) {
easingEnum[kS] = tempN;
easingEnum[tempN] = kS;
tempN++;
}
}
/** 缓动单元 */
@ccclass('BezierCurveAnimationTweenUnit')
class BezierCurveAnimationTweenUnit {
/* --------------- 属性 --------------- */
/** 自定义缓动曲线 */
@property({ displayName: '自定义缓动曲线' })
customCurveB = false;
/** 缓动曲线 */
@property({
displayName: '缓动曲线',
type: cc.Enum(easingEnum),
visible: function (this: BezierCurveAnimationTweenUnit) {
return !this.customCurveB;
}
})
easing = 0;
/** 缓动控制点 */
@property({
displayName: '控制点',
type: [cc.Vec3],
visible: function (this: BezierCurveAnimationTweenUnit) {
return this.customCurveB;
}
})
controlPointV3S: cc.Vec3[] = [];
/** 时间(秒) */
@property({ displayName: '时间(秒)' })
timeSN = 0;
}
/** 贝塞尔曲线通用动画组件 */
@ccclass('BezierCurveAnimation')
export class BezierCurveAnimation extends Component {
/* --------------- 属性 --------------- */
/** 缓动单元 */
@property({ displayName: '缓动单元', type: [BezierCurveAnimationTweenUnit] })
tweenUnitAs: BezierCurveAnimationTweenUnit[] = [];
/** 缓动切换事件 */
@property({ displayName: '缓动切换事件', tooltip: '(当前缓动下标_indexN)', type: cc.EventHandler })
tweenSwitchEvent = new cc.EventHandler();
/** 更新事件 */
@property({ displayName: '更新事件', tooltip: '(曲线Y_yN)', type: cc.EventHandler })
updateEvent = new cc.EventHandler();
/** 结束事件 */
@property({ displayName: '结束事件', type: cc.EventHandler })
endEvent = new cc.EventHandler();
/* --------------- private --------------- */
/* ------------------------------- segmentation ------------------------------- */
/** 开始缓动 */
startTween(): cc.Tween<any> {
/** 总时间(秒) */
let totalTimeSN = this.tweenUnitAs.reduce((preValue, currValue) => preValue + currValue.timeSN, 0);
/** 时间占比 */
let timeRatioNs: number[] = [];
{
let currN = 0;
this.tweenUnitAs.forEach((v, kN) => {
let ratioN = v.timeSN / totalTimeSN;
currN += ratioN;
timeRatioNs.push(currN);
});
}
/** 曲线函数 */
let curveFS = this.tweenUnitAs.map((v) => {
if (v.customCurveB) {
let curve = new BezierCurve(v.controlPointV3S);
return curve.point.bind(curve) as (kN: number) => number;
} else {
return cc.easing[easingEnum[v.easing]].bind(cc.easing) as (kN: number) => number;
}
});
/** 上次缓动下标 */
let lastTweenIndexN = 0;
/** 缓动对象 */
let tweenTarget = { valueN: 0 };
/** 缓动 */
let tween = cc
.tween(tweenTarget)
.to(
totalTimeSN,
{
valueN: 1
},
{
onUpdate: (target: typeof tweenTarget, ratioN: number) => {
/** 当前缓动下标 */
let tweenIndexN = timeRatioNs.findIndex((vN) => ratioN <= vN);
if (tweenIndexN === -1) {
return;
}
/** 上个时间占比 */
let lastTimeRatioN = tweenIndexN ? timeRatioNs[tweenIndexN - 1] : 0;
/** 当前时间范围 */
let timeRangeN = timeRatioNs[tweenIndexN] - lastTimeRatioN;
/** 曲线位置 */
let posN = (ratioN - lastTimeRatioN) / timeRangeN;
/** 曲线位置 */
let yN = curveFS[tweenIndexN](posN) * timeRangeN + lastTimeRatioN;
// 缓动切换事件触发
if (lastTweenIndexN !== tweenIndexN) {
this.tweenSwitchEvent?.emit([lastTweenIndexN]);
}
// 更新事件触发
this.updateEvent?.emit([yN]);
// 更新缓动下标
lastTweenIndexN = tweenIndexN;
}
}
)
.call(() => {
// 结束事件触发
this.endEvent?.emit([]);
})
.start();
return tween;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.22",
"importer": "typescript",
"imported": true,
"uuid": "5f284aba-f716-4ed7-90d6-b6bca57731e5",
"files": [],
"subMetas": {},
"userData": {}
}

193
assets/RollingLottery.ts Normal file
View File

@@ -0,0 +1,193 @@
import { _decorator, Component, Node } from 'cc';
import * as cc from 'cc';
import { BezierCurveAnimation } from './BezierCurveAnimation';
const { ccclass, property, requireComponent } = _decorator;
/** 旋转抽奖方向 */
export enum RollingLotteryDirection {
/** 竖 */
VERTICAL,
/** 横 */
HORIZONTAL
}
/** 循环滚动抽奖 */
@ccclass('RollingLottery')
@requireComponent(BezierCurveAnimation)
@requireComponent(cc.Layout)
export class RollingLottery extends Component {
/* --------------- 属性 --------------- */
/** 滚动方向 */
@property({ displayName: '滚动方向', type: cc.Enum(RollingLotteryDirection) })
dire = RollingLotteryDirection.VERTICAL;
/** 子节点刷新事件 */
@property({ displayName: '子节点刷新事件', tooltip: '(子节点_node, 下标_indexN)', type: cc.EventHandler })
itemUpdateEvent = new cc.EventHandler();
/* --------------- private --------------- */
/** 曲线组件 */
private _curveComp: BezierCurveAnimation;
/** transform 组件 */
private _uiTransform: cc.UITransform;
/** 周长 */
private _perimeterN: number;
/** 当前距离 */
private _currDistN = 0;
/** 总距离 */
private _totalDistN: number;
/** 当前下标 */
private _currIndexN: number;
/** 子节点大小 */
private _ItemSize: cc.Size;
/** 运动状态 */
private _scrollB = false;
/* --------------- 临时变量 --------------- */
private _tempM4 = cc.mat4();
private _temp2M4 = cc.mat4();
/* ------------------------------- 生命周期 ------------------------------- */
onLoad() {
this._initData();
this._initView();
this._initEvent();
}
/* ------------------------------- 功能 ------------------------------- */
/** 获取格子移动后距离 */
private _getItemMovePos(currIndexN_: number, targetIndexN_: number): void {
/** 格子距离 */
let boxDistN = this.dire === RollingLotteryDirection.HORIZONTAL ? this._ItemSize.width : this._ItemSize.height;
/** 移动距离 */
let moveDistN = (targetIndexN_ - currIndexN_) * boxDistN;
/** 圈数 */
let circleN = Math.floor(moveDistN / this._perimeterN);
/** 额外移动距离 */
let extraMoveDistN = moveDistN - circleN * this._perimeterN;
}
/** 获取在世界坐标系下的节点包围盒(不包含自身激活的子节点范围) */
private _getBoundingBoxToWorld(node_: cc.Node): cc.Rect {
node_.getWorldMatrix(this._temp2M4);
cc.Mat4.fromRTS(this._tempM4, this.node.getRotation(), this.node.getPosition(), this.node.getScale());
let width = this._uiTransform.contentSize.width;
let height = this._uiTransform.contentSize.height;
let rect = new cc.Rect(
-this._uiTransform.anchorPoint.x * width,
-this._uiTransform.anchorPoint.y * height,
width,
height
);
cc.Mat4.multiply(this._temp2M4, this._temp2M4, this._tempM4);
rect.transformMat4(this._temp2M4);
return rect;
}
/** 检测参数节点是否与当前节点碰撞 */
private _checkCollision(node_: cc.Node): boolean {
let rect = this._getBoundingBoxToWorld(this.node);
let rect2 = this._getBoundingBoxToWorld(node_);
// 增加保险范围
rect.width += rect.width * 0.5;
rect.height += rect.height * 0.5;
rect.x -= rect.width * 0.25;
rect.y -= rect.height * 0.25;
return rect.intersects(rect2);
}
/** 更新运动距离 */
private _updateMoveDist(indexN_: number): void {
/** 间隔格子 */
let intervalN = indexN_ - this._currIndexN;
/** 格子距离 */
let boxDistN = this.dire === RollingLotteryDirection.HORIZONTAL ? this._ItemSize.width : this._ItemSize.height;
/** 超出当前格子距离 */
let overDistN = this._currDistN - boxDistN * this._currIndexN;
// 设置总距离
this._totalDistN = intervalN * boxDistN - overDistN;
}
/** 更新数据 */
private _updateData(): void {
// 单圈长度
this._perimeterN = 0;
this.node.children.forEach((v1) => {
this._perimeterN += v1.getComponent(cc.UITransform).height;
});
// 重置距离
this._currDistN = 0;
}
/** 初始化数据 */
private _initData(): void {
this._curveComp = this.node.getComponent(BezierCurveAnimation);
this._uiTransform = this.node.getComponent(cc.UITransform);
this._ItemSize = this.node.children[0].getComponent(cc.UITransform).contentSize.clone();
// 设置更新事件
this._curveComp.updateEvent.component = cc.js.getClassName(this);
this._curveComp.updateEvent.handler = 'updateEvent';
// 设置结束事件
this._curveComp.endEvent.component = cc.js.getClassName(this);
this._curveComp.endEvent.handler = 'updateEvent';
// 更新当前距离
{
let distV3 = this.node.worldPosition.clone().subtract(this.node.children[0].worldPosition);
this._currDistN = this.dire === RollingLotteryDirection.HORIZONTAL ? distV3.x : distV3.y;
}
this._updateData();
}
/** 初始化视图 */
private _initView(): void {
this.scroll(0);
}
/** 初始化事件 */
private _initEvent(): void {
this.node.on(cc.Node.EventType.SIBLING_ORDER_CHANGED, this._nodeSiblingOrderChanged, this);
}
/**
* 循环滚动
* @param speedN_ 速度
* @param timeSN_ 时间(秒),不填则一直滚动
*/
loop(speedN_: number, timeSN_?: number): void {}
/** 滚动到指定下标 */
scroll(indexN_: number, timeSN_?: number): void {
this._scrollB = true;
this._updateMoveDist(indexN_);
/** 移动距离 */
let moveDistN = this._totalDistN - this._currDistN;
// 直接跳转
if (!timeSN_) {
/** 圈数 */
let circleN = Math.floor(moveDistN / this._perimeterN);
/** 额外移动距离 */
let extraMoveDistN = moveDistN - circleN * this._perimeterN;
}
}
/* ------------------------------- 自定义事件 ------------------------------- */
tweenSwitchEvent(indexN_: number): void {
// cc.log('缓动切换', indexN_);
}
updateEvent(yN_: number): void {
// cc.log('缓动更新', yN_);
}
endEvent(): void {
// cc.log('缓动结束');
}
/** 子节点更新 */
childUpdate(node_: cc.Node, indexN_: number): void {
node_.getComponentInChildren(cc.Label).string = indexN_ + '';
}
/* ------------------------------- 节点事件 ------------------------------- */
private _nodeSiblingOrderChanged(): void {
this._updateData();
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.22",
"importer": "typescript",
"imported": true,
"uuid": "b1f6263d-6e67-4833-9b0d-bd379c66031d",
"files": [],
"subMetas": {},
"userData": {}
}

2421
assets/main.scene Normal file

File diff suppressed because it is too large Load Diff

11
assets/main.scene.meta Normal file
View File

@@ -0,0 +1,11 @@
{
"ver": "1.1.32",
"importer": "scene",
"imported": true,
"uuid": "4c7c011d-1dee-494d-b761-aa723a3a84c7",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}