Files
MatchMaster/assets/Script/RainbowTrail.ts
T

145 lines
5.9 KiB
TypeScript

// 彩虹飞行专用粒子,纹理在内存中生成,避免改动魔法棒的共享特效。
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;
}
}