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(); } }