import { Component, Node, Vec3, tween, Quat, Sprite, Color, math, easing, Camera, ITweenOption, IPunchTweenOption, IShakeTweenOption } from 'cc'; import { calcPunchData, calcShakeData } from './Util'; ////////////////////// // Transform ////////////////////// Node.prototype.qtPosition = function (to: Vec3, duration: number, opts?: ITweenOption) { return tween(this).to(duration, { position: to }, opts); } Node.prototype.qtPositionX = function (to: number, duration: number, opts?: ITweenOption) { const startPos = this.position; return tween(this).to(duration, { position: new Vec3(to, startPos.y, startPos.z) }, opts); } Node.prototype.qtPositionY = function (to: number, duration: number, opts?: ITweenOption) { const startPos = this.position; return tween(this).to(duration, { position: new Vec3(startPos.x, to, startPos.z) }, opts); } Node.prototype.qtPositionZ = function (to: number, duration: number, opts?: ITweenOption) { const startPos = this.position; return tween(this).to(duration, { position: new Vec3(startPos.x, startPos.y, to) }, opts); } Node.prototype.qtWorldPosition = function (to: Vec3, duration: number, opts?: ITweenOption) { return tween(this).to(duration, { worldPosition: to }, opts); } Node.prototype.qtWorldPositionX = function (to: number, duration: number, opts?: ITweenOption) { const startPos = this.worldPosition; return tween(this).to(duration, { worldPosition: new Vec3(to, startPos.y, startPos.z) }, opts); } Node.prototype.qtWorldPositionY = function (to: number, duration: number, opts?: ITweenOption) { const startPos = this.worldPosition; return tween(this).to(duration, { worldPosition: new Vec3(startPos.x, to, startPos.z) }, opts); } Node.prototype.qtWorldPositionZ = function (to: number, duration: number, opts?: ITweenOption) { const startPos = this.worldPosition; return tween(this).to(duration, { worldPosition: new Vec3(startPos.x, startPos.y, to) }, opts); } Node.prototype.qtRotation = function (to: Vec3, duration: number, opts?: ITweenOption) { return tween(this).to(duration, { eulerAngles: to }, opts); } Node.prototype.qtRotationQuat = function (to: Quat, duration: number, opts?: ITweenOption) { return tween(this).to(duration, { rotation: to }, opts); } Node.prototype.qtScale = function (to: Vec3 | number, duration: number, opts?: ITweenOption) { let toScale = to; if (!(to instanceof Vec3)) { toScale = new Vec3(to, to, to); } return tween(this).to(duration, { scale: toScale }, opts); } Node.prototype.qtScaleX = function (to: number, duration: number, opts?: ITweenOption) { const startScale = this.scale; return tween(this).to(duration, { scale: new Vec3(to, startScale.y, startScale.z) }, opts); } Node.prototype.qtScaleY = function (to: number, duration: number, opts?: ITweenOption) { const startScale = this.scale; return tween(this).to(duration, { scale: new Vec3(startScale.x, to, startScale.z) }, opts); } Node.prototype.qtScaleZ = function (to: number, duration: number, opts?: ITweenOption) { const startScale = this.scale; return tween(this).to(duration, { scale: new Vec3(startScale.x, startScale.y, to) }, opts); } Node.prototype.qtPunchPosition = function (punch: Vec3, duration: number, opts?: IPunchTweenOption) { const vibrato = opts?.vibrato ?? 3; const elasticity = opts?.elasticity ?? 0.5; const { tos, durations } = calcPunchData(this.position.clone(), punch, duration, vibrato, elasticity); const punchTween = tween(this); tos.forEach((to, index) => { const d = durations[index]; let tweenOpts: ITweenOption | undefined; if (index === 0) { tweenOpts = { onStart: opts.onStart } } else if (index === tos.length - 1) { tweenOpts = { onComplete: opts.onComplete } } punchTween.then(tween().to(d, { position: to }, tweenOpts)); }); return punchTween.union(); } Node.prototype.qtPunchRotation = function (punch: Vec3, duration: number, opts?: IPunchTweenOption) { const vibrato = opts?.vibrato ?? 3; const elasticity = opts?.elasticity ?? 0.5; const { tos, durations } = calcPunchData(this.rotation.clone(), punch, duration, vibrato, elasticity); const punchTween = tween(this); tos.forEach((to, index) => { const d = durations[index]; let tweenOpts: ITweenOption | undefined; if (index === 0) { tweenOpts = { onStart: opts.onStart } } else if (index === tos.length - 1) { tweenOpts = { onComplete: opts.onComplete } } punchTween.then(tween().to(d, { eulerAngles: to }, tweenOpts)); }); return punchTween.union(); } Node.prototype.qtPunchScale = function (punch: Vec3, duration: number, opts?: IPunchTweenOption) { const vibrato = opts?.vibrato ?? 3; const elasticity = opts?.elasticity ?? 0.5; const { tos, durations } = calcPunchData(this.scale.clone(), punch, duration, vibrato, elasticity); const punchTween = tween(this); tos.forEach((to, index) => { const d = durations[index]; let tweenOpts: ITweenOption | undefined; if (index === 0) { tweenOpts = { onStart: opts.onStart } } else if (index === tos.length - 1) { tweenOpts = { onComplete: opts.onComplete } } punchTween.then(tween().to(d, { scale: to }, tweenOpts)); }); return punchTween.union(); } Node.prototype.qtJumpDistance = function (distance: Vec3, jumpHeight: number, jumpNum: number, duration: number, opts: ITweenOption = {}) { const tweenPos = new Vec3(); const jumpTween = tween(this); const totalNum = jumpNum * 2; this.jumpY = 0; let startPosY = 0; const yUpTween = tween().to(duration / totalNum, { jumpY: jumpHeight }, { onStart: (target: Node) => { startPosY = target.position.y; target.jumpY = 0; }, onUpdate: (target: Node, ratio) => { tweenPos.set(target.position); tweenPos.y = startPosY + target.jumpY; target.position = tweenPos; }, onComplete: (target: Node) => { target.jumpY = 0; }, easing: 'quadOut' }).to(duration / totalNum, { jumpY: jumpHeight }, { onStart: (target: Node) => { startPosY = target.position.y; }, onUpdate: (target: Node, ratio) => { tweenPos.set(target.position); tweenPos.y = startPosY - target.jumpY; target.position = tweenPos; }, onComplete: (target: Node) => { target.jumpY = 0; }, easing: 'quadIn', }).union().repeat(jumpNum); this.jumpOffsetY = 0; let offsetY = 0; const offsetYTween = tween().to(duration, { jumpOffsetY: distance.y }, { onStart: (target: Node) => { offsetY = distance.y; target.jumpOffsetY = 0; }, onUpdate: (target: Node, ratio) => { const interpOffsetY = easing.quadOut(ratio) * offsetY; tweenPos.set(target.position); tweenPos.y += interpOffsetY; target.position = tweenPos; }, onComplete: (target: Node) => { target.jumpOffsetY = 0; }, easing: 'quadOut' }); this.jumpX = this.position.x; this.jumpZ = this.position.z; const xzTween = tween().to(duration, { jumpX: this.position.x + distance.x, jumpZ: this.position.z + distance.z }, { onStart: opts.onStart, // 使用默认空对象后的 opts onUpdate: (target: Node, ratio) => { tweenPos.set(target.position); tweenPos.x = target.jumpX; tweenPos.z = target.jumpZ; target.position = tweenPos; opts.onUpdate?.(); // 通过可选链访问方法,避免 undefined 错误 }, onComplete: (target: Node) => { target.jumpX = target.position.x; target.jumpZ = target.position.z; opts.onComplete?.(); } }); jumpTween.parallel(yUpTween, offsetYTween, xzTween); return jumpTween; } Node.prototype.qtJumpPosition = function (to: Vec3, jumpHeight: number, jumpNum: number, duration: number, opts: ITweenOption = {}) { const tweenPos = new Vec3(); const jumpTween = tween(this); const totalNum = jumpNum * 2; this.jumpY = 0; let startPosY = 0; const yUpTween = tween().to(duration / totalNum, { jumpY: jumpHeight }, { onStart: (target: Node) => { startPosY = target.position.y; target.jumpY = 0; }, onUpdate: (target: Node, ratio) => { tweenPos.set(target.position); tweenPos.y = startPosY + target.jumpY; target.position = tweenPos; }, onComplete: (target: Node) => { target.jumpY = 0; }, easing: 'quadOut' }).to(duration / totalNum, { jumpY: jumpHeight }, { onStart: (target: Node) => { startPosY = target.position.y; }, onUpdate: (target: Node, ratio) => { tweenPos.set(target.position); tweenPos.y = startPosY - target.jumpY; target.position = tweenPos; }, onComplete: (target: Node) => { target.jumpY = 0; }, easing: 'quadIn', }).union().repeat(jumpNum); this.jumpOffsetY = 0; let offsetY = 0; const offsetYTween = tween().to(duration, { jumpOffsetY: to.y - this.position.y }, { onStart: (target: Node) => { offsetY = to.y - target.position.y; target.jumpOffsetY = 0; }, onUpdate: (target: Node, ratio) => { const interpOffsetY = easing.quadOut(ratio) * offsetY; tweenPos.set(target.position); tweenPos.y += interpOffsetY; target.position = tweenPos; }, onComplete: (target: Node) => { target.jumpOffsetY = 0; }, easing: 'quadOut' }); this.jumpX = this.position.x; this.jumpZ = this.position.z; const xzTween = tween().to(duration, { jumpX: to.x, jumpZ: to.z }, { onStart: opts.onStart, // 使用 opts 前先检查 onUpdate: (target: Node, ratio) => { tweenPos.set(target.position); tweenPos.x = target.jumpX; tweenPos.z = target.jumpZ; target.position = tweenPos; opts.onUpdate?.(); // 通过可选链访问方法,避免 undefined 错误 }, onComplete: (target: Node) => { target.jumpX = target.position.x; target.jumpZ = target.position.z; opts.onComplete?.(); } }); jumpTween.parallel(yUpTween, offsetYTween, xzTween); return jumpTween; } Node.prototype.qtShakePosition = function (strength: Vec3 | number, duration: number, opts?: IShakeTweenOption) { const vibrato = opts?.vibrato ?? 10; const randomness = opts?.randomness ?? 90; const fadeOut = opts?.fadeOut ?? true; let toStrength: Vec3; let vectorBased = false; if (!(strength instanceof Vec3)) { toStrength = new Vec3(strength, strength, strength); } else { toStrength = strength; vectorBased = true; } const { tos, durations } = calcShakeData(this.position.clone(), duration, toStrength, vibrato, randomness, false, vectorBased, fadeOut) const shakeTween = tween(this); tos.forEach((to, index) => { const d = durations[index]; let tweenOpts: ITweenOption | undefined; if (index === 0) { tweenOpts = { onStart: opts.onStart } } else if (index === tos.length - 1) { tweenOpts = { onComplete: opts.onComplete } } shakeTween.then(tween().to(d, { position: to }, tweenOpts)); }); return shakeTween.union(); } Node.prototype.qtShakeRotation = function (strength: Vec3 | number, duration: number, opts?: IShakeTweenOption) { const vibrato = opts?.vibrato ?? 10; const randomness = opts?.randomness ?? 90; const fadeOut = opts?.fadeOut ?? true; let toStrength: Vec3; let vectorBased = false; if (!(strength instanceof Vec3)) { toStrength = new Vec3(strength, strength, strength); } else { toStrength = strength; vectorBased = true; } const { tos, durations } = calcShakeData(this.eulerAngles.clone(), duration, toStrength, vibrato, randomness, false, vectorBased, fadeOut) const shakeTween = tween(this); tos.forEach((to, index) => { const d = durations[index]; let tweenOpts: ITweenOption | undefined; if (index === 0) { tweenOpts = { onStart: opts.onStart } } else if (index === tos.length - 1) { tweenOpts = { onComplete: opts.onComplete } } shakeTween.then(tween().to(d, { eulerAngles: to }, tweenOpts)); }); return shakeTween.union(); } Node.prototype.qtShakeScale = function (strength: Vec3 | number, duration: number, opts?: IShakeTweenOption) { const vibrato = opts?.vibrato ?? 10; const randomness = opts?.randomness ?? 90; const fadeOut = opts?.fadeOut ?? true; let toStrength: Vec3; let vectorBased = false; if (!(strength instanceof Vec3)) { toStrength = new Vec3(strength, strength, strength); } else { toStrength = strength; vectorBased = true; } const { tos, durations } = calcShakeData(this.scale.clone(), duration, toStrength, vibrato, randomness, false, vectorBased, fadeOut) const shakeTween = tween(this); tos.forEach((to, index) => { const d = durations[index]; let tweenOpts: ITweenOption | undefined; if (index === 0) { tweenOpts = { onStart: opts.onStart } } else if (index === tos.length - 1) { tweenOpts = { onComplete: opts.onComplete } } shakeTween.then(tween().to(d, { scale: to }, tweenOpts)); }); return shakeTween.union(); } ////////////////////// // Sprite ////////////////////// // good color lerp // https://www.alanzucconi.com/2016/01/06/colour-interpolation/ Sprite.prototype.qtColor = function (to: Color, duration: number, opts?: ITweenOption) { return tween(this).to(duration, { color: to }, opts); } Sprite.prototype.qtOpacity = function (to: number, duration: number, opts?: ITweenOption) { const startColor = this.color.clone(); const tempColor = new Color(); return tween(this).to(duration, { color: new Color(startColor.r, startColor.g, startColor.b, to) }, { onStart: opts.onStart, onUpdate: (target: { _val: number }, ratio: number) => { const lerpA = startColor.a + (to - startColor.a) * ratio tempColor.set(startColor.r, startColor.g, startColor.b, lerpA); this.color = tempColor; opts.onUpdate?.(); }, onComplete: opts.onComplete }); } ////////////////////// // Camera ////////////////////// Camera.prototype.qtShakePosition = function (strength: Vec3 | number, duration: number, opts?: IShakeTweenOption) { const vibrato = opts?.vibrato ?? 10; const randomness = opts?.randomness ?? 90; const fadeOut = opts?.fadeOut ?? true; let toStrength: Vec3; let vectorBased = false; if (!(strength instanceof Vec3)) { toStrength = new Vec3(strength, strength, strength); } else { toStrength = strength; vectorBased = true; } const { tos, durations } = calcShakeData(this.node.position.clone(), duration, toStrength, vibrato, randomness, true, vectorBased, fadeOut) const shakeTween = tween(this.node); tos.forEach((to, index) => { const d = durations[index]; let tweenOpts: ITweenOption | undefined; if (index === 0) { tweenOpts = { onStart: opts.onStart } } else if (index === tos.length - 1) { tweenOpts = { onComplete: opts.onComplete } } shakeTween.then(tween().to(d, { position: to }, tweenOpts)); }); return shakeTween.union(); }