145 lines
5.9 KiB
TypeScript
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;
|
||
}
|
||
}
|