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.
120 lines
3.2 KiB
120 lines
3.2 KiB
|
3 weeks ago
|
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;
|
||
|
|
}
|
||
|
|
}
|