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.
101 lines
3.5 KiB
101 lines
3.5 KiB
|
1 week ago
|
import { _decorator, Component, Vec3, tween, Node, UIOpacity, isValid, Tween } from 'cc';
|
||
|
|
const { ccclass, property } = _decorator;
|
||
|
|
|
||
|
|
@ccclass('FlyCurrency')
|
||
|
|
export class FlyCurrency extends Component {
|
||
|
|
|
||
|
|
// 动画参数配置
|
||
|
|
private readonly SPAWN_RADIUS: number = 120; // 初始扩散半径增大
|
||
|
|
private readonly PEAK_HEIGHT: number = 200; // 抛物线高度增加
|
||
|
|
private readonly FLY_DURATION: number = 0.9; // 基础飞行时间
|
||
|
|
|
||
|
|
// 初始化方法(使用世界坐标系)
|
||
|
|
public init(worldStart: Vec3, worldEnd: Vec3) {
|
||
|
|
// 确保使用世界坐标
|
||
|
|
const spawnPos = this.calcSpawnPosition(worldStart);
|
||
|
|
this.setInitialState(spawnPos);
|
||
|
|
|
||
|
|
// 动画参数计算
|
||
|
|
const delay = Math.random() * 0.4;
|
||
|
|
const duration = this.FLY_DURATION + Math.random() * 0.3;
|
||
|
|
const rotation = 720 * (Math.random() > 0.5 ? 1 : -1);
|
||
|
|
|
||
|
|
// 入场动画序列
|
||
|
|
this.playEntranceAnimation(delay, duration, rotation, spawnPos, worldEnd);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 计算初始散布位置
|
||
|
|
private calcSpawnPosition(center: Vec3): Vec3 {
|
||
|
|
const angle = Math.random() * Math.PI * 2;
|
||
|
|
const radius = Math.random() * this.SPAWN_RADIUS;
|
||
|
|
return new Vec3(
|
||
|
|
center.x + Math.cos(angle) * radius,
|
||
|
|
center.y + Math.sin(angle) * radius,
|
||
|
|
0
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 设置初始状态
|
||
|
|
private setInitialState(pos: Vec3) {
|
||
|
|
const opacity = this.node.addComponent(UIOpacity);
|
||
|
|
opacity.opacity = 0; // 初始完全透明
|
||
|
|
|
||
|
|
this.node.setWorldPosition(pos);
|
||
|
|
this.node.setScale(Vec3.ZERO); // 初始缩放为0
|
||
|
|
this.node.angle = Math.random() * 360;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 播放入场动画
|
||
|
|
private playEntranceAnimation(
|
||
|
|
delay: number,
|
||
|
|
duration: number,
|
||
|
|
rotation: number,
|
||
|
|
start: Vec3,
|
||
|
|
end: Vec3
|
||
|
|
) {
|
||
|
|
tween(this.node)
|
||
|
|
.delay(delay)
|
||
|
|
// 第一阶段:弹性弹出 + 渐显
|
||
|
|
.parallel(
|
||
|
|
tween()
|
||
|
|
.to(0.3, { scale: new Vec3(1.2, 1.2, 1) }, { easing: 'backOut' }),
|
||
|
|
tween(this.node.getComponent(UIOpacity)!)
|
||
|
|
.to(0.2, { opacity: 255 })
|
||
|
|
)
|
||
|
|
// 第二阶段:飞行主动画
|
||
|
|
.parallel(
|
||
|
|
this.createParabolaTween(start, end, duration),
|
||
|
|
tween().by(duration, { angle: rotation }),
|
||
|
|
tween().to(duration * 0.5, { scale: Vec3.ONE })
|
||
|
|
)
|
||
|
|
// 第三阶段:离场缩小
|
||
|
|
.to(0.2, { scale: Vec3.ZERO })
|
||
|
|
.call(() => {
|
||
|
|
if (isValid(this.node)) {
|
||
|
|
this.node.destroy();
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.start();
|
||
|
|
}
|
||
|
|
|
||
|
|
protected onDestroy(): void {
|
||
|
|
Tween.stopAllByTarget(this.node);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 创建抛物线动画
|
||
|
|
private createParabolaTween(start: Vec3, end: Vec3, duration: number) {
|
||
|
|
return tween().to(duration, {}, {
|
||
|
|
onUpdate: (_, ratio: number) => {
|
||
|
|
if (!isValid(this.node)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const currentX = start.x + (end.x - start.x) * ratio;
|
||
|
|
const yProgress = Math.sin(ratio * Math.PI); // 正弦曲线
|
||
|
|
const currentY = start.y + (end.y - start.y) * ratio +
|
||
|
|
yProgress * this.PEAK_HEIGHT;
|
||
|
|
this.node.setWorldPosition(new Vec3(currentX, currentY, 0));
|
||
|
|
},
|
||
|
|
easing: 'sineOut' // 添加缓动函数
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|