MatchMaster/assets/Script/RainbowOldOpening.ts
WIN-GKKD951VVMJ\Administrator bd4984c909 老彩虹最新更新
2026-09-21 18:03:23 +08:00

141 lines
5.9 KiB
TypeScript

import RainbowTrail from "./RainbowTrail";
import RainbowBottleWater from "./RainbowBottleWater";
const ENTER_SECONDS = 0.5;
const HOLD_SECONDS = 0.5;
const CAST_SECONDS = 0.7;
const FADE_SECONDS = 0.15;
/** C 方案开局自动施放一次;地图在尾迹消散后才开放操作。 */
export default class RainbowOldOpening {
private flight: RainbowTrail = null;
private bottleNode = new cc.Node("rainbowOldBottle");
private from: cc.Vec2;
private entryFrom: cc.Vec2;
private entryControl: cc.Vec2;
private to: cc.Vec2;
private elapsed = 0;
private landed = false;
private disposed = false;
private hidden = false;
private hide = () => {
this.hidden = true;
if (this.flight) this.flight.update(true);
this.bottleNode.active = false;
};
private show = () => {
this.hidden = false;
};
constructor(private parent: cc.Node, bottle: cc.Node, to: cc.Vec2, private onLand: () => void, bottleBg: cc.Node) {
const centerX = cc.winSize.width / 2, centerY = cc.winSize.height / 2;
this.from = parent.convertToNodeSpaceAR(cc.v2(centerX, centerY));
this.entryFrom = parent.convertToNodeSpaceAR(cc.v2(-200, centerY - 80));
this.entryControl = parent.convertToNodeSpaceAR(cc.v2(centerX * 0.35, centerY + 150));
this.to = cc.v2(to.x, to.y);
bottle.active = false;
bottleBg.active = false;
// 只复制两张图,避免把底栏的按钮事件和其他组件带入开场动画。
const water = this.copySprite(bottle, bottleBg);
this.copySprite(bottleBg, bottleBg);
water.getComponent(cc.Sprite).fillRange = 1;
water.addComponent(RainbowBottleWater);
this.bottleNode.setPosition(this.entryFrom);
this.bottleNode.angle = -18;
this.bottleNode.setScale(0.9);
this.bottleNode.active = false;
parent.addChild(this.bottleNode);
this.bottleNode.zIndex = cc.macro.MAX_ZINDEX;
cc.game.on(cc.game.EVENT_HIDE, this.hide);
cc.game.on(cc.game.EVENT_SHOW, this.show);
}
private copySprite(source: cc.Node, origin: cc.Node): cc.Node {
const node = new cc.Node(source.name);
const original = source.getComponent(cc.Sprite);
const sprite = node.addComponent(cc.Sprite);
sprite.spriteFrame = original.spriteFrame;
sprite.sizeMode = cc.Sprite.SizeMode.CUSTOM;
sprite.type = original.type;
sprite.trim = original.trim;
sprite.fillType = original.fillType;
sprite.fillStart = original.fillStart;
sprite.fillRange = original.fillRange;
node.setContentSize(source.getContentSize());
node.setAnchorPoint(source.getAnchorPoint());
node.setScale(source.scaleX, source.scaleY);
node.angle = source.angle;
node.setPosition(source.x - origin.x, source.y - origin.y);
node.color = cc.Color.WHITE;
this.bottleNode.addChild(node);
return node;
}
/** 返回 true 表示飞行和尾迹均已结束;开局不依赖游戏倒计时。 */
update(dt: number, paused = false): boolean {
if (this.disposed) return true;
const stopped = this.hidden || paused;
this.bottleNode.active = !stopped && this.bottleNode.opacity > 0;
if (this.flight && !this.flight.update(stopped)) {
// RainbowTrail 已自行释放资源,不能在 dispose 中再销毁一次。
this.flight = null;
this.dispose();
return true;
}
if (stopped || this.landed) return false;
this.elapsed += dt;
if (this.elapsed < ENTER_SECONDS) {
const t = this.elapsed / ENTER_SECONDS, u = 1 - t;
this.bottleNode.setPosition(u * u * this.entryFrom.x + 2 * u * t * this.entryControl.x + t * t * this.from.x,
u * u * this.entryFrom.y + 2 * u * t * this.entryControl.y + t * t * this.from.y);
this.bottleNode.angle = -18 * u;
this.bottleNode.setScale(0.9 + 0.1 * t);
return false;
}
if (this.elapsed < ENTER_SECONDS + HOLD_SECONDS) {
const t = (this.elapsed - ENTER_SECONDS) / HOLD_SECONDS;
this.bottleNode.setPosition(this.from.x, this.from.y + Math.sin(t * Math.PI * 2) * 4);
this.bottleNode.angle = Math.sin(t * Math.PI * 4) * 6 * (1 - t);
this.bottleNode.setScale(1 + 0.08 * Math.sin(t * Math.PI));
return false;
}
if (!this.flight) {
this.bottleNode.setPosition(this.from);
this.bottleNode.angle = 0;
this.bottleNode.setScale(1);
this.flight = new RainbowTrail(this.parent, CAST_SECONDS);
this.flight.node.setPosition(this.from);
}
const castElapsed = this.elapsed - ENTER_SECONDS - HOLD_SECONDS;
this.bottleNode.opacity = Math.round(255 * (1 - Math.min(1, castElapsed / FADE_SECONDS)));
this.bottleNode.active = this.bottleNode.opacity > 0;
const t = Math.min(1, castElapsed / CAST_SECONDS);
// 与 B 同一条弧线:先抛到目标上方,再竖直落入目标。
const top = Math.max(this.from.y, this.to.y) + 180;
const controlY = top + Math.sqrt((top - this.from.y) * (top - this.to.y));
const u = 1 - t;
this.flight.node.setPosition(this.to.x + (this.from.x - this.to.x) * u * u,
u * u * this.from.y + 2 * u * t * controlY + t * t * this.to.y);
this.flight.setProgress(t);
if (t >= 1) {
this.landed = true;
this.flight.stop();
this.onLand();
}
return this.disposed;
}
dispose(): void {
if (this.disposed) return;
this.disposed = true;
if (this.flight) {
this.flight.destroy();
this.flight = null;
}
cc.game.off(cc.game.EVENT_HIDE, this.hide);
cc.game.off(cc.game.EVENT_SHOW, this.show);
this.bottleNode.active = false;
this.bottleNode.destroy();
}
}