141 lines
5.9 KiB
TypeScript
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();
|
|
}
|
|
}
|