first commit
This commit is contained in:
commit
8bcf8cf385
5
.creator/default-meta.json
Normal file
5
.creator/default-meta.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"image": {
|
||||||
|
"type": "sprite-frame"
|
||||||
|
}
|
||||||
|
}
|
23
.eslintignore
Normal file
23
.eslintignore
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# This file will tell ESLint which files and folders it should never lint.
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
*.d.ts
|
||||||
|
|
||||||
|
*.js
|
||||||
|
|
||||||
|
build
|
||||||
|
|
||||||
|
temp
|
||||||
|
|
||||||
|
library
|
||||||
|
|
||||||
|
local
|
||||||
|
|
||||||
|
native
|
||||||
|
|
||||||
|
profiles
|
||||||
|
|
||||||
|
settings
|
||||||
|
|
||||||
|
build-related
|
85
.eslintrc.js
Normal file
85
.eslintrc.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
/**
|
||||||
|
* 扩展->采用 AlloyTeam 的 ESLintConfig
|
||||||
|
* @see https://github.com/AlloyTeam/eslint-config-alloy
|
||||||
|
*/
|
||||||
|
extends: ['alloy', 'alloy/typescript', 'plugin:eqeqeq-fix/recommended'],
|
||||||
|
/**
|
||||||
|
* 适用环境
|
||||||
|
*/
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
browser: true
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 解析器:使用 ESLint 解析 TypeScript 语法
|
||||||
|
*/
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
/**
|
||||||
|
* ESLint 插件
|
||||||
|
*/
|
||||||
|
plugins: ['@typescript-eslint', 'unused-imports', 'autofix'],
|
||||||
|
globals: {
|
||||||
|
/**
|
||||||
|
* 指定Cocos Creator相关全局变量,这样子就不会触发 no-undef 的规则
|
||||||
|
*/
|
||||||
|
Decimal: 'readonly',
|
||||||
|
CryptoJS: 'readonly',
|
||||||
|
jsb: 'readonly',
|
||||||
|
Editor: 'readonly',
|
||||||
|
globalThis: 'readonly',
|
||||||
|
protobuf: 'readonly',
|
||||||
|
gg: 'readonly',
|
||||||
|
logger: 'readonly'
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 默认规则
|
||||||
|
*
|
||||||
|
* @see https://eslint.org/docs/rules/
|
||||||
|
* @see https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules
|
||||||
|
*/
|
||||||
|
rules: {
|
||||||
|
/** 在每条语句的末尾添加一个分号 */
|
||||||
|
semi: ['error', 'always'],
|
||||||
|
/** 禁止修改原生对象,排除的类型可以扩展 */
|
||||||
|
'no-extend-native': ['error', { exceptions: ['String', 'Number', 'Date', 'Array'] }],
|
||||||
|
/** 字符串指定使用单引号 */
|
||||||
|
'autofix/quotes': ['error', 'single'],
|
||||||
|
/** 禁止使用console */
|
||||||
|
'autofix/no-console': ['error'],
|
||||||
|
/** 禁止使用debugger */
|
||||||
|
'autofix/no-debugger': ['error'],
|
||||||
|
/** 必须使用===,禁止使用== */
|
||||||
|
'eqeqeq-fix/eqeqeq': ['error'],
|
||||||
|
/** 当导入的东西未被使用时报错提示 */
|
||||||
|
'unused-imports/no-unused-imports': 'error',
|
||||||
|
/** 禁止将自己赋值给自己,属性可以 */
|
||||||
|
'no-self-assign': ['error', { props: false }],
|
||||||
|
/** 控制环复杂度最大值为25(默认20),超过后警告(默认是报错) */
|
||||||
|
complexity: ['error', { max: 25 }],
|
||||||
|
/** 代码块嵌套的深度禁止超过 6 层 */
|
||||||
|
'max-depth': ['error', { max: 6 }],
|
||||||
|
/** 函数最多参数数量为 6个,超过之后警告,默认值是 3 */
|
||||||
|
'max-params': ['warn', 6],
|
||||||
|
/** 限制数组类型必须使用 Array<T> 或 T[] */
|
||||||
|
'@typescript-eslint/array-type': ['error'],
|
||||||
|
/** 指定类成员的排序规则 */
|
||||||
|
'@typescript-eslint/member-ordering': 'off',
|
||||||
|
/** method 和 property 不需要显式声明 public 访问修饰符 */
|
||||||
|
'@typescript-eslint/explicit-member-accessibility': ['error', { accessibility: 'no-public' }],
|
||||||
|
/** 禁止将 this 赋值给其他变量,除非是解构赋值 */
|
||||||
|
'@typescript-eslint/no-this-alias': ['error', { allowDestructuring: true, allowedNames: ['self'] }],
|
||||||
|
/** 不允许对初始化为数字、字符串或布尔值的变量或参数进行显式类型声明 */
|
||||||
|
'@typescript-eslint/no-inferrable-types': ['error', { ignoreParameters: true, ignoreProperties: true }],
|
||||||
|
/** 类型断言必须使用 as */
|
||||||
|
'@typescript-eslint/consistent-type-assertions': [
|
||||||
|
'error',
|
||||||
|
{ assertionStyle: 'as', objectLiteralTypeAssertions: 'allow' }
|
||||||
|
],
|
||||||
|
camelcase: [
|
||||||
|
'error',
|
||||||
|
{ properties: 'always', ignoreDestructuring: true, ignoreImports: true, ignoreGlobals: true, allow: [] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
#///////////////////////////
|
||||||
|
# Cocos Creator 3D Project
|
||||||
|
#///////////////////////////
|
||||||
|
library/
|
||||||
|
temp/
|
||||||
|
local/
|
||||||
|
build/
|
||||||
|
profiles/
|
||||||
|
native
|
||||||
|
#//////////////////////////
|
||||||
|
# NPM
|
||||||
|
#//////////////////////////
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
#//////////////////////////
|
||||||
|
# VSCode
|
||||||
|
#//////////////////////////
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
#//////////////////////////
|
||||||
|
# WebStorm
|
||||||
|
#//////////////////////////
|
||||||
|
.idea/
|
57
.prettierrc.js
Normal file
57
.prettierrc.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// .prettierrc.js
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* 一行最多 120 字符
|
||||||
|
*/
|
||||||
|
printWidth: 120,
|
||||||
|
/**
|
||||||
|
* 使用 4 个空格缩进
|
||||||
|
*/
|
||||||
|
tabWidth: 4,
|
||||||
|
// 不使用缩进符,而使用空格
|
||||||
|
useTabs: false,
|
||||||
|
// 行尾需要有分号
|
||||||
|
semi: true,
|
||||||
|
// 使用单引号
|
||||||
|
singleQuote: true,
|
||||||
|
/**
|
||||||
|
* 对象属性的引号使用
|
||||||
|
* as-needed 仅在需要的时候使用
|
||||||
|
* consistent 有一个属性需要引号,就都需要引号
|
||||||
|
* preserve 保留用户输入的情况
|
||||||
|
*/
|
||||||
|
quoteProps: 'as-needed',
|
||||||
|
/**
|
||||||
|
* 对象属性的尾随逗号,最后一个属性后是否需要加逗号
|
||||||
|
* none 末尾没有逗号
|
||||||
|
* es5 es5有效的地方保留
|
||||||
|
* all 在可能的地方都加上逗号
|
||||||
|
*/
|
||||||
|
trailingComma: 'none',
|
||||||
|
/**
|
||||||
|
* 字面量对象括号中的空格,默认true
|
||||||
|
* true - Example: { foo: bar }
|
||||||
|
* false - Example: {foo: bar}
|
||||||
|
*/
|
||||||
|
bracketSpacing: true,
|
||||||
|
/**
|
||||||
|
* 箭头函数中的括号
|
||||||
|
* “avoid” - 在有需要的时候使用. Example: x => x
|
||||||
|
* “always” - 一直使用. Example: (x) => x
|
||||||
|
*/
|
||||||
|
arrowParens: 'always',
|
||||||
|
// 每个文件格式化的范围是文件的全部内容
|
||||||
|
rangeStart: 0,
|
||||||
|
rangeEnd: Infinity,
|
||||||
|
/**
|
||||||
|
* 折行标准 preserve,always
|
||||||
|
*/
|
||||||
|
proseWrap: 'always',
|
||||||
|
/**
|
||||||
|
* 行末尾标识
|
||||||
|
* “auto”,“lf”,“crlf”,“cr”
|
||||||
|
*/
|
||||||
|
endOfLine: 'lf',
|
||||||
|
// 格式化内嵌代码
|
||||||
|
embeddedLanguageFormatting: 'auto'
|
||||||
|
};
|
248
assets/BezierCurve.ts
Normal file
248
assets/BezierCurve.ts
Normal 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;
|
9
assets/BezierCurve.ts.meta
Normal file
9
assets/BezierCurve.ts.meta
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.22",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "c34af995-9c5c-4522-a007-1bc0d11fec95",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
140
assets/BezierCurveAnimation.ts
Normal file
140
assets/BezierCurveAnimation.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
9
assets/BezierCurveAnimation.ts.meta
Normal file
9
assets/BezierCurveAnimation.ts.meta
Normal 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
193
assets/RollingLottery.ts
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
9
assets/RollingLottery.ts.meta
Normal file
9
assets/RollingLottery.ts.meta
Normal 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
2421
assets/main.scene
Normal file
File diff suppressed because it is too large
Load Diff
11
assets/main.scene.meta
Normal file
11
assets/main.scene.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"ver": "1.1.32",
|
||||||
|
"importer": "scene",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "4c7c011d-1dee-494d-b761-aa723a3a84c7",
|
||||||
|
"files": [
|
||||||
|
".json"
|
||||||
|
],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
9
package.json
Normal file
9
package.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "mk_bezier_animation",
|
||||||
|
"type": "3d",
|
||||||
|
"uuid": "a40d3209-184c-4665-ab62-fdc2a899ca69",
|
||||||
|
"version": "3.3.2",
|
||||||
|
"creator": {
|
||||||
|
"version": "3.3.2"
|
||||||
|
}
|
||||||
|
}
|
22
settings/v2/packages/cocos-service.json
Normal file
22
settings/v2/packages/cocos-service.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"game": {
|
||||||
|
"name": "未知游戏",
|
||||||
|
"app_id": "UNKNOW",
|
||||||
|
"c_id": "0"
|
||||||
|
},
|
||||||
|
"appConfigMaps": [
|
||||||
|
{
|
||||||
|
"app_id": "UNKNOW",
|
||||||
|
"config_id": "82a98c"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configs": [
|
||||||
|
{
|
||||||
|
"app_id": "UNKNOW",
|
||||||
|
"config_id": "82a98c",
|
||||||
|
"config_name": "Default",
|
||||||
|
"config_remarks": "",
|
||||||
|
"services": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
3
settings/v2/packages/device.json
Normal file
3
settings/v2/packages/device.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"__version__": "1.0.1"
|
||||||
|
}
|
114
settings/v2/packages/engine.json
Normal file
114
settings/v2/packages/engine.json
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
{
|
||||||
|
"__version__": "1.0.5",
|
||||||
|
"modules": {
|
||||||
|
"cache": {
|
||||||
|
"base": {
|
||||||
|
"_value": true
|
||||||
|
},
|
||||||
|
"graphcis": {
|
||||||
|
"_value": true
|
||||||
|
},
|
||||||
|
"gfx-webgl": {
|
||||||
|
"_value": true
|
||||||
|
},
|
||||||
|
"gfx-webgl2": {
|
||||||
|
"_value": false
|
||||||
|
},
|
||||||
|
"3d": {
|
||||||
|
"_value": false
|
||||||
|
},
|
||||||
|
"2d": {
|
||||||
|
"_value": true
|
||||||
|
},
|
||||||
|
"ui": {
|
||||||
|
"_value": true
|
||||||
|
},
|
||||||
|
"particle": {
|
||||||
|
"_value": false
|
||||||
|
},
|
||||||
|
"physics": {
|
||||||
|
"_value": false,
|
||||||
|
"_option": "physics-ammo"
|
||||||
|
},
|
||||||
|
"physics-ammo": {
|
||||||
|
"_value": false
|
||||||
|
},
|
||||||
|
"physics-cannon": {
|
||||||
|
"_value": false
|
||||||
|
},
|
||||||
|
"physics-physx": {
|
||||||
|
"_value": false
|
||||||
|
},
|
||||||
|
"physics-builtin": {
|
||||||
|
"_value": false
|
||||||
|
},
|
||||||
|
"physics-2d": {
|
||||||
|
"_value": true,
|
||||||
|
"_option": "physics-2d-box2d"
|
||||||
|
},
|
||||||
|
"physics-2d-box2d": {
|
||||||
|
"_value": false
|
||||||
|
},
|
||||||
|
"physics-2d-builtin": {
|
||||||
|
"_value": false
|
||||||
|
},
|
||||||
|
"intersection-2d": {
|
||||||
|
"_value": true
|
||||||
|
},
|
||||||
|
"primitive": {
|
||||||
|
"_value": false
|
||||||
|
},
|
||||||
|
"profiler": {
|
||||||
|
"_value": true
|
||||||
|
},
|
||||||
|
"particle-2d": {
|
||||||
|
"_value": true
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"_value": true
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"_value": true
|
||||||
|
},
|
||||||
|
"webview": {
|
||||||
|
"_value": true
|
||||||
|
},
|
||||||
|
"tween": {
|
||||||
|
"_value": true
|
||||||
|
},
|
||||||
|
"terrain": {
|
||||||
|
"_value": false
|
||||||
|
},
|
||||||
|
"tiled-map": {
|
||||||
|
"_value": true
|
||||||
|
},
|
||||||
|
"spine": {
|
||||||
|
"_value": true
|
||||||
|
},
|
||||||
|
"dragon-bones": {
|
||||||
|
"_value": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"includeModules": [
|
||||||
|
"base",
|
||||||
|
"gfx-webgl",
|
||||||
|
"2d",
|
||||||
|
"ui",
|
||||||
|
"physics-2d-box2d",
|
||||||
|
"intersection-2d",
|
||||||
|
"profiler",
|
||||||
|
"particle-2d",
|
||||||
|
"audio",
|
||||||
|
"video",
|
||||||
|
"webview",
|
||||||
|
"tween",
|
||||||
|
"tiled-map",
|
||||||
|
"spine",
|
||||||
|
"dragon-bones"
|
||||||
|
],
|
||||||
|
"noDeprecatedFeatures": {
|
||||||
|
"value": false,
|
||||||
|
"version": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
settings/v2/packages/program.json
Normal file
3
settings/v2/packages/program.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"__version__": "1.0.0"
|
||||||
|
}
|
9
tsconfig.json
Normal file
9
tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
/* Base configuration. Do not edit this field. */
|
||||||
|
"extends": "./temp/tsconfig.cocos.json",
|
||||||
|
|
||||||
|
/* Add your custom configuration here. */
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": false
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user