MatchMaster/assets/Script/RainbowTrail.ts
WIN-GKKD951VVMJ\Administrator 2029441aae 更新迭代动画效果
2026-09-15 18:31:24 +08:00

145 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 彩虹飞行专用粒子,纹理在内存中生成,避免改动魔法棒的共享特效。
export default class RainbowTrail {
readonly node = new cc.Node("rainbowFlight");
private mist: cc.ParticleSystem;
private stars: cc.ParticleSystem;
private head: cc.Node;
private frames: cc.SpriteFrame[] = [];
private textures: cc.Texture2D[] = [];
private stopped = false;
constructor(parent: cc.Node, duration: number) {
const glow = this.texture(false);
const star = this.texture(true);
this.mist = this.emitter("rainbowMist", glow);
this.mist.totalParticles = 200;
this.mist.emissionRate = 170;
this.mist.life = Math.max(0.45, duration * 0.6);
this.mist.lifeVar = 0.08;
this.mist.startSize = 58;
this.mist.startSizeVar = 10;
this.mist.endSize = 24;
this.mist.endSizeVar = 6;
this.mist.posVar = cc.v2(3, 3);
this.mist.speed = 6;
this.mist.speedVar = 4;
this.stars = this.emitter("rainbowStars", star);
this.stars.totalParticles = 30;
this.stars.emissionRate = 26;
this.stars.life = 0.55;
this.stars.lifeVar = 0.1;
this.stars.startSize = 15;
this.stars.startSizeVar = 5;
this.stars.endSize = 3;
this.stars.endSizeVar = 2;
this.stars.posVar = cc.v2(13, 13);
this.stars.speed = 16;
this.stars.speedVar = 8;
this.stars.startSpinVar = 180;
this.stars.endSpinVar = 180;
this.stars.startColor = cc.color(255, 244, 178, 255);
this.stars.startColorVar = cc.color(0, 10, 45, 0);
this.stars.endColor = cc.color(255, 225, 130, 0);
this.head = new cc.Node("rainbowHead");
const sprite = this.head.addComponent(cc.Sprite);
sprite.spriteFrame = glow;
sprite.sizeMode = cc.Sprite.SizeMode.CUSTOM;
this.head.setContentSize(42, 42);
this.head.color = cc.color(255, 252, 224);
this.node.addChild(this.head);
this.setProgress(0);
parent.addChild(this.node);
this.node.zIndex = cc.macro.MAX_ZINDEX;
this.mist.resetSystem();
this.stars.resetSystem();
}
private emitter(name: string, frame: cc.SpriteFrame): cc.ParticleSystem {
const node = new cc.Node(name);
const particles = node.addComponent(cc.ParticleSystem);
particles.custom = true;
particles.playOnLoad = false;
particles.autoRemoveOnFinish = false;
particles.spriteFrame = frame;
particles.duration = -1;
particles.positionType = cc.ParticleSystem.PositionType.FREE;
particles.emitterMode = cc.ParticleSystem.EmitterMode.GRAVITY;
particles.gravity = cc.v2(0, 0);
particles.sourcePos = cc.v2(0, 0);
particles.angle = 0;
particles.angleVar = 360;
particles.radialAccel = particles.radialAccelVar = 0;
particles.tangentialAccel = particles.tangentialAccelVar = 0;
particles.startSpin = particles.endSpin = 0;
particles.startSpinVar = particles.endSpinVar = 0;
particles.startColorVar = particles.endColorVar = cc.color(0, 0, 0, 0);
particles.srcBlendFactor = cc.macro.SRC_ALPHA;
particles.dstBlendFactor = cc.macro.ONE_MINUS_SRC_ALPHA;
this.node.addChild(node);
return particles;
}
setProgress(progress: number): void {
// 只改变新发射粒子的颜色,已经发射的粒子在原位置保留颜色并渐隐。
const palette = [[255, 72, 93], [255, 166, 57], [255, 232, 80], [77, 220, 112],
[57, 215, 245], [85, 130, 255], [187, 99, 244], [255, 72, 93]];
const phase = ((progress * 2) % 1) * (palette.length - 1);
const index = Math.floor(phase);
const fraction = phase - index;
const rgb = palette[index].map((value, channel) => Math.round(value
+ (palette[index + 1][channel] - value) * fraction));
this.mist.startColor = cc.color(rgb[0], rgb[1], rgb[2], 185);
this.mist.endColor = cc.color(rgb[0], rgb[1], rgb[2], 0);
}
stop(): void {
this.stopped = true;
this.head.active = false;
this.mist.stopSystem();
this.stars.stopSystem();
}
update(paused: boolean): boolean {
// 禁用节点同时停止粒子模拟,恢复时不 reset,保留既有尾迹。
this.node.active = !paused;
if (this.stopped && !paused && this.mist.particleCount === 0 && this.stars.particleCount === 0) {
this.destroy();
return false;
}
return true;
}
destroy(): void {
this.node.active = false;
this.node.destroy();
this.frames.forEach(frame => frame.destroy());
this.textures.forEach(texture => texture.destroy());
this.frames = [];
this.textures = [];
}
private texture(star: boolean): cc.SpriteFrame {
const size = 64;
const pixels = new Uint8Array(size * size * 4);
for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) {
const nx = (x + 0.5 - size / 2) / (size / 2);
const ny = (y + 0.5 - size / 2) / (size / 2);
const radius = Math.sqrt(nx * nx + ny * ny);
const glow = Math.max(0, 1 - radius) * Math.exp(-3 * radius * radius);
const rays = Math.max(0, Math.min(1, (1 - Math.sqrt(Math.abs(nx)) - Math.sqrt(Math.abs(ny))) * 16));
const alpha = star ? Math.max(rays, glow * 0.2) : glow;
const offset = (y * size + x) * 4;
pixels[offset] = pixels[offset + 1] = pixels[offset + 2] = 255;
pixels[offset + 3] = Math.round(alpha * 255);
}
const texture = new cc.Texture2D();
texture.initWithData(pixels, cc.Texture2D.PixelFormat.RGBA8888, size, size);
const frame = new cc.SpriteFrame(texture);
this.textures.push(texture);
this.frames.push(frame);
return frame;
}
}