//const { ccclass, property } = cc._decorator; /** * 单个粒子信息 */ export class MyParticle { pos = cc.v2(0, 0); startPos = cc.v2(0, 0); color = cc.color(0, 0, 0, 255); deltaColor = { r: 0, g: 0, b: 0, a: 255 }; size = 0; deltaSize = 0; rotation = 0; deltaRotation = 0; timeToLive = 0; drawPos = cc.v2(0, 0); aspectRatio = 1; // Mode A dir = cc.v2(0, 0); radialAccel = 0; tangentialAccel = 0; // Mode B angle = 0; degreesPerSecond = 0; radius = 0; deltaRadius = 0; frameindex = 0//当前帧 lastFrameTime = 0; topLife = 0; //当刷新帧率为-1时,自动按总生活周期 init() { this.pos.set(cc.Vec2.ZERO) this.startPos.set(cc.Vec2.ZERO) this.color.fromHEX('0xFF000000'); this.deltaColor.r = this.deltaColor.g = this.deltaColor.b = 0; this.deltaColor.a = 255; this.size = 0; this.deltaSize = 0; this.rotation = 0; this.deltaRotation = 0; this.timeToLive = 0; this.drawPos.set(cc.Vec2.ZERO) this.aspectRatio = 1; // Mode A this.dir.set(cc.Vec2.ZERO) this.radialAccel = 0; this.tangentialAccel = 0; // Mode B this.angle = 0; this.degreesPerSecond = 0; this.radius = 0; this.deltaRadius = 0; this.frameindex = 0//当前帧 this.lastFrameTime = 0; this.topLife = 0; //当刷新帧率为-1时,自动按总生活周期 } } export default class MyParticleSimulator { pool: cc.js.Pool; // sys: MyParticleSysInfo; sys: cc.ParticleSystem; particles: MyParticle[] = null; active: boolean = false; readyToPlay: boolean = true; finished: boolean = false; elapsed: number = 0; emitCounter: number = 0; _uvFilled: number = 0; _worldRotation: number = 0; animationRate: number = 0; //帧率 animFrameNums: number = 0; //图片有几帧 _pos = new cc.Vec2(0, 0); _tpa = new cc.Vec2(0, 0); _tpb = new cc.Vec2(0, 0); _tpc = new cc.Vec2(0, 0); constructor(system, frameNums, animRate) { this.pool = new cc.js.Pool((par: MyParticle) => { par.pos.set(cc.Vec2.ZERO) par.startPos.set(cc.Vec2.ZERO) par.color.fromHEX('0xFF000000'); par.deltaColor.r = par.deltaColor.g = par.deltaColor.b = 0; par.deltaColor.a = 255; par.size = 0; par.deltaSize = 0; par.rotation = 0; par.deltaRotation = 0; par.timeToLive = 0; par.drawPos.set(cc.Vec2.ZERO) par.aspectRatio = 1; // Mode A par.dir.set(cc.Vec2.ZERO) par.radialAccel = 0; par.tangentialAccel = 0; // Mode B par.angle = 0; par.degreesPerSecond = 0; par.radius = 0; par.deltaRadius = 0; par.frameindex = 0//当前帧 par.lastFrameTime = 0; par.topLife = 0; //当刷新帧率为-1时,自动按总生活周期 }, 1024); this.sys = system; this.particles = []; this.active = false; this.readyToPlay = true; this.finished = false; this.elapsed = 0; this.emitCounter = 0; this._uvFilled = 0; this._worldRotation = 0; this.animationRate = animRate; this.animFrameNums = frameNums; this.pool.get = function () { return this._get() || new MyParticle(); }; } stop() { this.active = false; this.readyToPlay = false; this.elapsed = this.sys.duration; this.emitCounter = 0; } reset() { cc.js.Pool this.active = true; this.readyToPlay = true; this.elapsed = 0; this.emitCounter = 0; this.finished = false; let particles = this.particles; for (let id = 0; id < particles.length; ++id) { this.pool.put(particles[id]); } particles.length = 0; } /** * 开始喷射 */ emitParticle(pos) { let psys = this.sys; let clampf = cc.misc.clampf; let particle = this.pool.get(); this.particles.push(particle); particle.timeToLive = psys.life + psys.lifeVar * (Math.random() - 0.5) * 2; // Init particle // timeToLive // no negative life. prevent division by 0 particle.timeToLive = psys.life + psys.lifeVar * (Math.random() - 0.5) * 2; let timeToLive = particle.timeToLive = Math.max(0, particle.timeToLive); // position particle.pos.x = psys.sourcePos.x + psys.posVar.x * (Math.random() - 0.5) * 2; particle.pos.y = psys.sourcePos.y + psys.posVar.y * (Math.random() - 0.5) * 2; // Color let sr, sg, sb, sa; let startColor = psys.startColor, startColorVar = psys.startColorVar; let endColor = psys.endColor, endColorVar = psys.endColorVar; particle.color.r = sr = clampf(startColor.r + startColorVar.r * (Math.random() - 0.5) * 2, 0, 255); particle.color.g = sg = clampf(startColor.g + startColorVar.g * (Math.random() - 0.5) * 2, 0, 255); particle.color.b = sb = clampf(startColor.b + startColorVar.b * (Math.random() - 0.5) * 2, 0, 255); particle.color.a = sa = clampf(startColor.a + startColorVar.a * (Math.random() - 0.5) * 2, 0, 255); particle.deltaColor.r = (clampf(endColor.r + endColorVar.r * (Math.random() - 0.5) * 2, 0, 255) - sr) / timeToLive; particle.deltaColor.g = (clampf(endColor.g + endColorVar.g * (Math.random() - 0.5) * 2, 0, 255) - sg) / timeToLive; particle.deltaColor.b = (clampf(endColor.b + endColorVar.b * (Math.random() - 0.5) * 2, 0, 255) - sb) / timeToLive; particle.deltaColor.a = (clampf(endColor.a + endColorVar.a * (Math.random() - 0.5) * 2, 0, 255) - sa) / timeToLive; // size let startS = psys.startSize + psys.startSizeVar * (Math.random() - 0.5) * 2; startS = Math.max(0, startS); // No negative value particle.size = startS; if (psys.endSize === cc.ParticleSystem.START_SIZE_EQUAL_TO_END_SIZE) { particle.deltaSize = 0; } else { var endS = psys.endSize + psys.endSizeVar * (Math.random() - 0.5) * 2; endS = Math.max(0, endS); // No negative values particle.deltaSize = (endS - startS) / timeToLive; } // rotation var startA = psys.startSpin + psys.startSpinVar * (Math.random() - 0.5) * 2; var endA = psys.endSpin + psys.endSpinVar * (Math.random() - 0.5) * 2; particle.rotation = startA; particle.deltaRotation = (endA - startA) / timeToLive; // position particle.startPos.x = pos.x; particle.startPos.y = pos.y; // aspect ratio particle.aspectRatio = this.sys['_aspectRatio'] || 1; // direction let a = cc.misc.degreesToRadians(psys.angle + this._worldRotation + psys.angleVar * (Math.random() - 0.5) * 2); // Mode Gravity: A if (psys.emitterMode === cc.ParticleSystem.EmitterMode.GRAVITY) { let s = psys.speed + psys.speedVar * (Math.random() - 0.5) * 2; // direction particle.dir.x = Math.cos(a); particle.dir.y = Math.sin(a); particle.dir.mulSelf(s); // radial accel particle.radialAccel = psys.radialAccel + psys.radialAccelVar * (Math.random() - 0.5) * 2; // tangential accel particle.tangentialAccel = psys.tangentialAccel + psys.tangentialAccelVar * (Math.random() - 0.5) * 2; // rotation is dir if (psys.rotationIsDir) { particle.rotation = -cc.misc.radiansToDegrees(Math.atan2(particle.dir.y, particle.dir.x)); } } // Mode Radius: B else { // Set the default diameter of the particle from the source position var startRadius = psys.startRadius + psys.startRadiusVar * (Math.random() - 0.5) * 2; var endRadius = psys.endRadius + psys.endRadiusVar * (Math.random() - 0.5) * 2; particle.radius = startRadius; particle.deltaRadius = (psys.endRadius === cc.ParticleSystem.START_RADIUS_EQUAL_TO_END_RADIUS) ? 0 : (endRadius - startRadius) / timeToLive; particle.angle = a; particle.degreesPerSecond = cc.misc.degreesToRadians(psys.rotatePerS + psys.rotatePerSVar * (Math.random() - 0.5) * 2); } particle.frameindex = 0; particle.lastFrameTime = particle.timeToLive; particle.topLife = particle.timeToLive; } getWorldRotation(node) { let rotation = 0; let tempNode = node; while (tempNode) { rotation += tempNode.angle; tempNode = tempNode.parent; } return rotation; } updateUVs(force) { let assembler = this.sys['_assembler']; if (!assembler) { return; } let buffer = this.sys['_assembler'].getBuffer(); if (buffer) { const FLOAT_PER_PARTICLE = 4 * this.sys['_assembler']._vfmt._bytes / 4; let vbuf = buffer._vData; let uv = this.sys['_renderSpriteFrame'].uv; let start = force ? 0 : this._uvFilled; let particleCount = this.particles.length; for (let i = start; i < particleCount; i++) { let offset = i * FLOAT_PER_PARTICLE; vbuf[offset + 2] = uv[0]; vbuf[offset + 3] = uv[1]; vbuf[offset + 7] = uv[2]; vbuf[offset + 8] = uv[3]; vbuf[offset + 12] = uv[4]; vbuf[offset + 13] = uv[5]; vbuf[offset + 17] = uv[6]; vbuf[offset + 18] = uv[7]; } this._uvFilled = particleCount; } } updateParticleBuffer(particle, pos, buffer, offset) { let vbuf = buffer._vData; let uintbuf = buffer._uintVData; let x = pos.x, y = pos.y; let width = particle.size; let height = width; let aspectRatio = particle.aspectRatio; aspectRatio > 1 ? (height = width / aspectRatio) : (width = height * aspectRatio); width = width / this.animFrameNums; let halfWidth = width / 2; let halfHeight = height / 2; // pos if (particle.rotation) { let x1 = -halfWidth, y1 = -halfHeight; let x2 = halfWidth, y2 = halfHeight; let rad = -cc.misc.degreesToRadians(particle.rotation); let cr = Math.cos(rad), sr = Math.sin(rad); // bl vbuf[offset] = x1 * cr - y1 * sr + x; vbuf[offset + 1] = x1 * sr + y1 * cr + y; // br vbuf[offset + 5] = x2 * cr - y1 * sr + x; vbuf[offset + 6] = x2 * sr + y1 * cr + y; // tl vbuf[offset + 10] = x1 * cr - y2 * sr + x; vbuf[offset + 11] = x1 * sr + y2 * cr + y; // tr vbuf[offset + 15] = x2 * cr - y2 * sr + x; vbuf[offset + 16] = x2 * sr + y2 * cr + y; } else { // bl vbuf[offset] = x - halfWidth; vbuf[offset + 1] = y - halfHeight; // br vbuf[offset + 5] = x + halfWidth; vbuf[offset + 6] = y - halfHeight; // tl vbuf[offset + 10] = x - halfWidth; vbuf[offset + 11] = y + halfHeight; // tr vbuf[offset + 15] = x + halfWidth; vbuf[offset + 16] = y + halfHeight; } if (this.animFrameNums > 1) { if (this.animationRate == -1) { //自动的 var fix = 1 / this.animFrameNums; let fixTimes = particle.topLife / this.animFrameNums; let idx = Math.floor(particle.timeToLive / fixTimes); idx = this.animFrameNums - idx - 1; vbuf[offset + 2] = fix * idx; vbuf[offset + 12] = fix * idx; vbuf[offset + 7] = vbuf[offset + 2] + fix; vbuf[offset + 17] = vbuf[offset + 7]; } else { let duration = 1 / this.animationRate; var fix = 1 / this.animFrameNums; vbuf[offset + 2] = fix * particle.frameindex; vbuf[offset + 12] = fix * particle.frameindex; vbuf[offset + 7] = vbuf[offset + 2] + fix; vbuf[offset + 17] = vbuf[offset + 7]; if (particle.lastFrameTime - particle.timeToLive > duration) { particle.frameindex += 1; particle.lastFrameTime = particle.timeToLive; if (particle.frameindex >= this.animFrameNums) { particle.frameindex = 0; } } } } // color uintbuf[offset + 4] = particle.color._val; uintbuf[offset + 9] = particle.color._val; uintbuf[offset + 14] = particle.color._val; uintbuf[offset + 19] = particle.color._val; } step(dt) { dt = dt > cc.director['_maxParticleDeltaTime'] ? cc.director['_maxParticleDeltaTime'] : dt; let psys = this.sys; let node = psys.node; let particles = this.particles; const FLOAT_PER_PARTICLE = 4 * this.sys['_assembler']._vfmt._bytes / 4; const PositionType = cc.ParticleSystem.PositionType; // Calculate pos node['_updateWorldMatrix'](); if (psys.positionType === PositionType.FREE) { this._worldRotation = this.getWorldRotation(node); let m = node['_worldMatrix'].m; this._pos.x = m[12]; this._pos.y = m[13]; } else if (psys.positionType === PositionType.RELATIVE) { this._worldRotation = node.angle; this._pos.x = node.x; this._pos.y = node.y; } else { this._worldRotation = 0; } // Emission if (this.active && psys.emissionRate) { var rate = 1.0 / psys.emissionRate; //issue #1201, prevent bursts of particles, due to too high emitCounter if (particles.length < psys.totalParticles) this.emitCounter += dt; while ((particles.length < psys.totalParticles) && (this.emitCounter > rate)) { this.emitParticle(this._pos); this.emitCounter -= rate; } this.elapsed += dt; if (psys.duration !== -1 && psys.duration < this.elapsed) { psys.stopSystem(); } } // Request buffer for particles let buffer = psys['_assembler'].getBuffer(); let particleCount = particles.length; buffer.reset(); buffer.request(particleCount * 4, particleCount * 6); // Fill up uvs if (particleCount > this._uvFilled) { this.updateUVs(false); } // Used to reduce memory allocation / creation within the loop let particleIdx = 0; while (particleIdx < particles.length) { // Reset temporary vectors this._tpa.x = this._tpa.y = this._tpb.x = this._tpb.y = this._tpc.x = this._tpc.y = 0; let particle = particles[particleIdx]; // life particle.timeToLive -= dt; if (particle.timeToLive > 0) { // Mode A: gravity, direction, tangential accel & radial accel if (psys.emitterMode === cc.ParticleSystem.EmitterMode.GRAVITY) { let tmp = this._tpc, radial = this._tpa, tangential = this._tpb; // radial acceleration if (particle.pos.x || particle.pos.y) { radial.set(particle.pos); radial.normalizeSelf(); } tangential.set(radial); radial.mulSelf(particle.radialAccel); // tangential acceleration let newy = tangential.x; tangential.x = -tangential.y; tangential.y = newy; tangential.mulSelf(particle.tangentialAccel); tmp.set(radial); tmp.addSelf(tangential); tmp.addSelf(psys.gravity); tmp.mulSelf(dt); particle.dir.addSelf(tmp); tmp.set(particle.dir); tmp.mulSelf(dt); particle.pos.addSelf(tmp); } // Mode B: radius movement else { // Update the angle and radius of the particle. particle.angle += particle.degreesPerSecond * dt; particle.radius += particle.deltaRadius * dt; particle.pos.x = -Math.cos(particle.angle) * particle.radius; particle.pos.y = -Math.sin(particle.angle) * particle.radius; } // color particle.color.r += particle.deltaColor.r * dt; particle.color.g += particle.deltaColor.g * dt; particle.color.b += particle.deltaColor.b * dt; particle.color.a += particle.deltaColor.a * dt; // size particle.size += particle.deltaSize * dt; if (particle.size < 0) { particle.size = 0; } // angle particle.rotation += particle.deltaRotation * dt; // update values in quad buffer let newPos = this._tpa; newPos.set(particle.pos); if (psys.positionType !== PositionType.GROUPED) { newPos.addSelf(particle.startPos); } let offset = FLOAT_PER_PARTICLE * particleIdx; this.updateParticleBuffer(particle, newPos, buffer, offset); // update particle counter ++particleIdx; } else { // life < 0 let deadParticle = particles[particleIdx]; if (particleIdx !== particles.length - 1) { particles[particleIdx] = particles[particles.length - 1]; } this.pool.put(deadParticle); particles.length--; } } if (particles.length > 0) { buffer.uploadData(); psys['_assembler']._ia._count = particles.length * 6; } else if (!this.active && !this.readyToPlay) { this.finished = true; psys['_finishedSimulation'](); } } // Simulator.prototype.step = function (dt) { // dt = dt > cc.director._maxParticleDeltaTime ? cc.director._maxParticleDeltaTime : dt; // let psys = this.sys; // let node = psys.node; // let particles = this.particles; // const FLOAT_PER_PARTICLE = 4 * this.sys._assembler._vfmt._bytes / 4; // const PositionType = cc.ParticleSystem.PositionType; // // Calculate pos // node._updateWorldMatrix(); // if (psys.positionType === PositionType.FREE) { // this._worldRotation = getWorldRotation(node); // let m = node._worldMatrix.m; // _pos.x = m[12]; // _pos.y = m[13]; // } else if (psys.positionType === PositionType.RELATIVE) { // this._worldRotation = node.angle; // _pos.x = node.x; // _pos.y = node.y; // } else { // this._worldRotation = 0; // } // // Emission // if (this.active && psys.emissionRate) { // var rate = 1.0 / psys.emissionRate; // //issue #1201, prevent bursts of particles, due to too high emitCounter // if (particles.length < psys.totalParticles) // this.emitCounter += dt; // while ((particles.length < psys.totalParticles) && (this.emitCounter > rate)) { // this.emitParticle(_pos); // this.emitCounter -= rate; // } // this.elapsed += dt; // if (psys.duration !== -1 && psys.duration < this.elapsed) { // psys.stopSystem(); // } // } // // Request buffer for particles // let buffer = psys._assembler.getBuffer(); // let particleCount = particles.length; // buffer.reset(); // buffer.request(particleCount * 4, particleCount * 6); // // Fill up uvs // if (particleCount > this._uvFilled) { // this.updateUVs(); // } // // Used to reduce memory allocation / creation within the loop // let particleIdx = 0; // while (particleIdx < particles.length) { // // Reset temporary vectors // _tpa.x = _tpa.y = _tpb.x = _tpb.y = _tpc.x = _tpc.y = 0; // let particle = particles[particleIdx]; // // life // particle.timeToLive -= dt; // if (particle.timeToLive > 0) { // // Mode A: gravity, direction, tangential accel & radial accel // if (psys.emitterMode === cc.ParticleSystem.EmitterMode.GRAVITY) { // let tmp = _tpc, radial = _tpa, tangential = _tpb; // // radial acceleration // if (particle.pos.x || particle.pos.y) { // radial.set(particle.pos); // radial.normalizeSelf(); // } // tangential.set(radial); // radial.mulSelf(particle.radialAccel); // // tangential acceleration // let newy = tangential.x; // tangential.x = -tangential.y; // tangential.y = newy; // tangential.mulSelf(particle.tangentialAccel); // tmp.set(radial); // tmp.addSelf(tangential); // tmp.addSelf(psys.gravity); // tmp.mulSelf(dt); // particle.dir.addSelf(tmp); // tmp.set(particle.dir); // tmp.mulSelf(dt); // particle.pos.addSelf(tmp); // } // // Mode B: radius movement // else { // // Update the angle and radius of the particle. // particle.angle += particle.degreesPerSecond * dt; // particle.radius += particle.deltaRadius * dt; // particle.pos.x = -Math.cos(particle.angle) * particle.radius; // particle.pos.y = -Math.sin(particle.angle) * particle.radius; // } // // color // particle.color.r += particle.deltaColor.r * dt; // particle.color.g += particle.deltaColor.g * dt; // particle.color.b += particle.deltaColor.b * dt; // particle.color.a += particle.deltaColor.a * dt; // // size // particle.size += particle.deltaSize * dt; // if (particle.size < 0) { // particle.size = 0; // } // // angle // particle.rotation += particle.deltaRotation * dt; // // update values in quad buffer // let newPos = _tpa; // newPos.set(particle.pos); // if (psys.positionType !== PositionType.GROUPED) { // newPos.addSelf(particle.startPos); // } // let offset = FLOAT_PER_PARTICLE * particleIdx; // this.updateParticleBuffer(particle, newPos, buffer, offset); // // update particle counter // ++particleIdx; // } else { // // life < 0 // let deadParticle = particles[particleIdx]; // if (particleIdx !== particles.length - 1) { // particles[particleIdx] = particles[particles.length - 1]; // } // pool.put(deadParticle); // particles.length--; // } // } // if (particles.length > 0) { // buffer.uploadData(); // psys._assembler._ia._count = particles.length * 6; // } // else if (!this.active && !this.readyToPlay) { // this.finished = true; // psys._finishedSimulation(); // } // } }