import { _decorator, Component, Vec3, v3, UIOpacity } from 'cc'; const { ccclass, property, executeInEditMode } = _decorator; /** * 呼吸动画(缩放 + 透明度) * 在 update 中按 speed 做 min↔max 往复插值,周期 = 1 / speed 秒。 */ @ccclass('TweenBreathe') @executeInEditMode(true) export class TweenBreathe extends Component { @property speed = 1; @property set playing(value: boolean) { this._playing = value; if (value) { this.play(); } else { this.stop(); } } get playing(): boolean { return this._playing; } private _playing: boolean = true; @property useScale = true; @property defaultScale: Vec3 = v3(1, 1, 1); @property minScale: Vec3 = v3(0.9, 0.9, 0.9); @property maxScale: Vec3 = v3(1.1, 1.1, 1.1); @property useOpacity = true; @property defaultOpacity = 255; @property opacity = 255; @property minOpacity = 100; @property({ tooltip: '几秒钟后暂停,-1 表示不暂停' }) timePause = -1; private _elapsed = 0; private uiOpacity: UIOpacity = null; protected onLoad(): void { this.uiOpacity = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity); } protected onEnable(): void { this.play(); } protected onDisable(): void { this.stop(); } protected update(dt: number): void { if (!this._playing) return; this._elapsed += dt; if (this.timePause > 0 && this._elapsed >= this.timePause) { this.stop(); return; } const factor = this._calcFactor(this._elapsed); if (this.useScale) { this.node.setScale( this.defaultScale.x * this._lerp(this.minScale.x, this.maxScale.x, factor), this.defaultScale.y * this._lerp(this.minScale.y, this.maxScale.y, factor), this.defaultScale.z * this._lerp(this.minScale.z, this.maxScale.z, factor), ); } if (this.useOpacity) { const opacity = this.bindOpacity(); if (opacity) opacity.opacity = this._lerp(this.minOpacity, this.opacity, factor); } } play(): void { this._elapsed = 0; this._playing = true; } stop(): void { this._playing = false; this.unscheduleAllCallbacks(); if (this.node?.isValid) this.node.setScale(this.defaultScale); const opacity = this.bindOpacity(); if (opacity) opacity.opacity = this.defaultOpacity; } /** 父节点 onLoad 可能早于本组件,stop/update 须自行取 UIOpacity */ private bindOpacity(): UIOpacity | null { if (this.uiOpacity) return this.uiOpacity; if (!this.node?.isValid) return null; this.uiOpacity = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity); return this.uiOpacity; } /** 0→1→0 往复,完整周期 1/speed 秒 */ private _calcFactor(elapsed: number): number { const angle = elapsed * this.speed * Math.PI * 2; return (1 - Math.cos(angle)) * 0.5; } private _lerp(min: number, max: number, t: number): number { return min + (max - min) * t; } }