You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
AdLike2048/assets/script/ui/BattleGame/BattleGameItemAdBtnPulse.ts

102 lines
3.1 KiB

3 weeks ago
import { Node, tween, Tween, Vec3 } from 'cc';
/** 放大阶段(秒) */
const PULSE_UP_SEC = 0.4;
/** 峰顶停留(秒) */
const PULSE_PEAK_SEC = 0.14;
/** 缩回阶段(秒) */
const PULSE_DOWN_SEC = 0.4;
/** 峰顶 Y 偏移(像素) */
const PULSE_LIFT_Y = 6;
/** 跳动形变代理(sx/sy 相对基准 scale 的倍率) */
interface PulsePose {
sx: number;
sy: number;
y: number;
}
/**
* BattleGame btnItem 循环跳动:非等比 scale(拉宽/拉高)+ 轻微上浮。
*/
export class BattleGameItemAdBtnPulse {
private _node: Node | null = null;
private readonly _baseScale = new Vec3(1, 1, 1);
private _baseX = 0;
private _baseY = 0;
private _baseZ = 0;
private _loopTween: Tween<PulsePose> | null = null;
private readonly _pose: PulsePose = { sx: 1, sy: 1, y: 0 };
private readonly _onPulseUpdate = (): void => { this._applyPose(); };
/** 绑定节点并启动循环跳动 */
bind(node: Node | null): void {
this.unbind();
if (!node?.isValid) return;
this._node = node;
const p = node.position;
const s = node.scale;
this._baseX = p.x;
this._baseY = p.y;
this._baseZ = p.z;
this._baseScale.set(s.x, s.y, s.z);
this._resetPose();
this._startLoop();
}
/** 停止跳动并还原节点姿态 */
unbind(): void {
const node = this._node;
if (this._loopTween) {
this._loopTween.stop();
this._loopTween = null;
}
if (node?.isValid) {
Tween.stopAllByTarget(node);
Tween.stopAllByTarget(this._pose);
node.setScale(this._baseScale);
node.setPosition(this._baseX, this._baseY, this._baseZ);
}
this._node = null;
}
/** 循环宽高形变脉冲 */
private _startLoop(): void {
const node = this._node;
if (!node?.isValid) return;
const pose = this._pose;
this._resetPose();
const opts = { onUpdate: this._onPulseUpdate };
this._loopTween = tween(pose)
.repeatForever(
tween(pose)
.to(PULSE_UP_SEC, {
sx: 0.96, sy: 1.12, y: PULSE_LIFT_Y * 0.65,
}, { easing: 'sineInOut', ...opts })
.to(PULSE_PEAK_SEC, {
sx: 1.14, sy: 0.90, y: PULSE_LIFT_Y,
}, { easing: 'sineOut', ...opts })
.to(PULSE_DOWN_SEC, {
sx: 1, sy: 1, y: 0,
}, { easing: 'sineInOut', ...opts }),
)
.start();
}
/** 将代理姿态应用到节点 */
private _applyPose(): void {
const node = this._node;
if (!node?.isValid) return;
const p = this._pose;
node.setScale(this._baseScale.x * p.sx, this._baseScale.y * p.sy, 1);
node.setPosition(this._baseX, this._baseY + p.y, this._baseZ);
}
/** 重置代理到常态 */
private _resetPose(): void {
this._pose.sx = 1;
this._pose.sy = 1;
this._pose.y = 0;
}
}