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