Files
MatchMaster/assets/Script/WashingEffect.ts
T
2026-10-23 16:43:58 +08:00

101 lines
4.0 KiB
TypeScript

/** 清洗状态由 Block 立即更新;这里只有逐格泥巴渐隐及气泡尾效。 */
export default class WashingEffect {
private layer = new cc.Node("washingEffect");
private bubbles = new cc.Node("washingBubbles");
private pending = 0;
private callbacks: (() => void)[] = [];
private disposed = false;
constructor(private parent: cc.Node) {
parent.addChild(this.layer);
this.bubbles.zIndex = 1000;
const icon = parent.getChildByName("icon");
if (icon) {
this.layer.setSiblingIndex(icon.getSiblingIndex() + 1);
this.layer.zIndex = icon.zIndex;
}
// 单色地块以 simple 格图替代 icon。
parent.children.filter(child => child.name === "simple").forEach(child => {
this.layer.zIndex = Math.max(this.layer.zIndex, child.zIndex + 1);
});
}
get isCleaning(): boolean { return this.pending > 0; }
play(cells: { x: number; y: number; frame: number }[], dirtFrames: cc.SpriteFrame[], bubbleFrame: cc.SpriteFrame): void {
if (this.disposed || !cc.isValid(this.layer, true)) return;
for (const cell of cells) {
const x = -65 + cell.x * 120, y = 60 + cell.y * 120;
const frame = dirtFrames[cell.frame];
if (frame) {
const dirt = this.sprite("washingDirt", frame, x, y, 120);
this.pending++;
cc.tween(dirt).to(0.2, { opacity: 0 }).call(() => {
if (this.disposed) return;
this.remove(dirt);
this.pending--;
this.flush();
}).start();
}
if (!bubbleFrame || !this.parent.parent) continue;
if (!this.bubbles.parent) this.parent.parent.addChild(this.bubbles);
// 气泡留在洗澡地形上,方块移走时不跟随。
const center = this.bubbles.convertToNodeSpaceAR(this.parent.convertToWorldSpaceAR(cc.v2(x, y)));
for (let i = 0; i < 12; i++) {
const bubble = this.sprite("washingBubble", bubbleFrame,
center.x + (i % 3 - 1) * 28 + Math.random() * 12 - 6,
center.y - 42 + Math.floor(i / 3) * 24 + Math.random() * 12, 18 + Math.random() * 18, this.bubbles);
bubble.opacity = 210;
bubble.color = i % 3 === 1 ? cc.color(220, 249, 255) : cc.Color.WHITE;
cc.tween(bubble).to(0.8 + Math.random() * 0.2, {
x: bubble.x + Math.random() * 28 - 14,
y: bubble.y + 126 + Math.random() * 72,
opacity: 0,
}, { easing: "sineOut" }).call(() => {
this.remove(bubble);
if (!this.bubbles.children.length) this.bubbles.removeFromParent();
}).start();
}
}
}
whenClean(callback: () => void): void {
if (this.disposed) return;
this.callbacks.push(callback);
this.flush();
}
private flush(): void {
while (!this.disposed && this.pending === 0 && this.callbacks.length) this.callbacks.shift()();
}
private sprite(name: string, frame: cc.SpriteFrame, x: number, y: number, size: number, layer = this.layer): cc.Node {
const node = new cc.Node(name);
const sprite = node.addComponent(cc.Sprite);
sprite.spriteFrame = frame;
sprite.sizeMode = cc.Sprite.SizeMode.CUSTOM;
sprite.trim = false;
node.setContentSize(size, size);
node.setPosition(x, y);
layer.addChild(node);
return node;
}
private remove(node: cc.Node): void {
if (!cc.isValid(node, true)) return;
node.removeFromParent();
node.destroy();
}
dispose(): void {
if (this.disposed) return;
this.disposed = true;
this.callbacks = [];
this.pending = 0;
[this.layer, this.bubbles].forEach(layer => {
layer.children.slice().forEach(node => cc.Tween.stopAllByTarget(node));
this.remove(layer);
});
}
}