/** * GDevelop - Particle System Extension * Copyright (c) 2010-2016 Florian Rival (Florian.Rival@gmail.com) * This project is released under the MIT License. */ /** * @typedef {Object} ParticleEmitterObjectDataType * @property {boolean} emissionEditionSimpleMode * @property {number} emitterAngleA * @property {number} emitterAngleB * @property {number} emitterForceMin * @property {number} emitterForceMax * @property {number} zoneRadius * @property {number} particleLifeTimeMin * @property {number} particleLifeTimeMax * @property {number} particleGravityX * @property {number} particleGravityY * @property {number} particleRed1 * @property {number} particleRed2 * @property {number} particleGreen1 * @property {number} particleGreen2 * @property {number} particleBlue1 * @property {number} particleBlue2 * @property {number} particleSize1 * @property {number} particleSize2 * @property {number} particleAngle1 * @property {number} particleAngle2 * @property {string} sizeParam * @property {number} particleAlpha1 * @property {number} particleAlpha2 * @property {string} rendererType * @property {number} rendererParam1 * @property {number} rendererParam2 * @property {string} textureParticleName Resource name for image in particle * @property {number} flow * @property {number} tank * @property {boolean} destroyWhenNoParticles Destroy the object when there is no particles? * * @typedef {ObjectData & ParticleEmitterObjectDataType} ParticleEmitterObjectData */ /** * Displays particles. * * @memberof gdjs * @class ParticleEmitterObject * @extends RuntimeObject * @param {gdjs.RuntimeScene} runtimeScene The {@link gdjs.RuntimeScene} the object belongs to * @param {ParticleEmitterObjectData} particleObjectData The initial properties of the object */ gdjs.ParticleEmitterObject = function(runtimeScene, particleObjectData){ gdjs.RuntimeObject.call(this, runtimeScene, particleObjectData); /** @type {gdjs.ParticleEmitterObjectRenderer} */ this._renderer = new gdjs.ParticleEmitterObjectRenderer(runtimeScene, this, particleObjectData); /** @type {boolean} */ this.singleAngle = particleObjectData.emissionEditionSimpleMode; /** @type {number} */ this.angleA = particleObjectData.emitterAngleA; /** @type {number} */ this.angleB = particleObjectData.emitterAngleB; /** @type {number} */ this.forceMin = particleObjectData.emitterForceMin; /** @type {number} */ this.forceMax = particleObjectData.emitterForceMax; /** @type {number} */ this.zoneRadius = particleObjectData.zoneRadius; /** @type {number} */ this.lifeTimeMin = particleObjectData.particleLifeTimeMin; /** @type {number} */ this.lifeTimeMax = particleObjectData.particleLifeTimeMax; /** @type {number} */ this.gravityX = particleObjectData.particleGravityX; /** @type {number} */ this.gravityY = particleObjectData.particleGravityY; /** @type {number} */ this.colorR1 = particleObjectData.particleRed1; /** @type {number} */ this.colorR2 = particleObjectData.particleRed2; /** @type {number} */ this.colorG1 = particleObjectData.particleGreen1; /** @type {number} */ this.colorG2 = particleObjectData.particleGreen2; /** @type {number} */ this.colorB1 = particleObjectData.particleBlue1; /** @type {number} */ this.colorB2 = particleObjectData.particleBlue2; /** @type {number} */ this.size1 = particleObjectData.particleSize1; /** @type {number} */ this.size2 = particleObjectData.particleSize2; /** @type {string} */ this.sizeParam = particleObjectData.sizeParam; /** @type {number} */ this.alpha1 = particleObjectData.particleAlpha1; /** @type {number} */ this.alpha2 = particleObjectData.particleAlpha2; /** @type {string} */ this.rendererType = particleObjectData.rendererType; /** @type {number} */ this.rendererParam1 = particleObjectData.rendererParam1; /** @type {number} */ this.rendererParam2 = particleObjectData.rendererParam2; /** @type {string} */ this.texture = particleObjectData.textureParticleName; /** @type {number} */ this.flow = particleObjectData.flow; /** @type {number} */ this.tank = particleObjectData.tank; /** @type {boolean} */ this.destroyWhenNoParticles = particleObjectData.destroyWhenNoParticles; /** @type {boolean} */ this._posDirty = true; /** @type {boolean} */ this._angleDirty = true; /** @type {boolean} */ this._forceDirty = true; /** @type {boolean} */ this._zoneRadiusDirty = true; /** @type {boolean} */ this._lifeTimeDirty = true; /** @type {boolean} */ this._gravityDirty = true; /** @type {boolean} */ this._colorDirty = true; /** @type {boolean} */ this._sizeDirty = true; /** @type {boolean} */ this._alphaDirty = true; /** @type {boolean} */ this._textureDirty = this.texture !== ''; // Don't mark texture as dirty if not using one. /** @type {boolean} */ this._flowDirty = true; // *ALWAYS* call `this.onCreated()` at the very end of your object constructor. this.onCreated(); }; gdjs.ParticleEmitterObject.prototype = Object.create(gdjs.RuntimeObject.prototype); gdjs.registerObject("ParticleSystem::ParticleEmitter", gdjs.ParticleEmitterObject); gdjs.ParticleEmitterObject.prototype.setX = function(x){ if(this.x !== x) this._posDirty = true; gdjs.RuntimeObject.prototype.setX.call(this, x); }; gdjs.ParticleEmitterObject.prototype.setY = function(y){ if(this.y !== y) this._posDirty = true; gdjs.RuntimeObject.prototype.setY.call(this, y); }; gdjs.ParticleEmitterObject.prototype.setAngle = function(angle){ if(this.angle !== angle) this._angleDirty = true; gdjs.RuntimeObject.prototype.setAngle.call(this, angle); }; gdjs.ParticleEmitterObject.prototype.getRendererObject = function(){ return this._renderer.getRendererObject(); }; /** * @param {ParticleEmitterObjectData} oldObjectData * @param {ParticleEmitterObjectData} newObjectData */ gdjs.ParticleEmitterObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) { if (oldObjectData.emissionEditionSimpleMode !== newObjectData.emissionEditionSimpleMode) { this.singleAngle = newObjectData.emissionEditionSimpleMode; this._angleDirty = true; } if (oldObjectData.emitterAngleA !== newObjectData.emitterAngleA) { this.setEmitterAngleA(newObjectData.emitterAngleA); } if (oldObjectData.emitterAngleB !== newObjectData.emitterAngleB) { this.setEmitterAngleB(newObjectData.emitterAngleB); } if (oldObjectData.emitterForceMin !== newObjectData.emitterForceMin) { this.setEmitterForceMin(newObjectData.emitterForceMin); } if (oldObjectData.emitterForceMax !== newObjectData.emitterForceMax) { this.setEmitterForceMax(newObjectData.emitterForceMax); } if (oldObjectData.zoneRadius !== newObjectData.zoneRadius) { this.setZoneRadius(newObjectData.zoneRadius); } if (oldObjectData.particleLifeTimeMin !== newObjectData.particleLifeTimeMin) { this.setParticleLifeTimeMin(newObjectData.particleLifeTimeMin); } if (oldObjectData.particleLifeTimeMax !== newObjectData.particleLifeTimeMax) { this.setParticleLifeTimeMax(newObjectData.particleLifeTimeMax); } if (oldObjectData.particleGravityX !== newObjectData.particleGravityX) { this.setParticleGravityX(newObjectData.particleGravityX); } if (oldObjectData.particleGravityY !== newObjectData.particleGravityY) { this.setParticleGravityY(newObjectData.particleGravityY); } if (oldObjectData.particleRed1 !== newObjectData.particleRed1) { this.setParticleRed1(newObjectData.particleRed1); } if (oldObjectData.particleRed2 !== newObjectData.particleRed2) { this.setParticleRed2(newObjectData.particleRed2); } if (oldObjectData.particleGreen1 !== newObjectData.particleGreen1) { this.setParticleGreen1(newObjectData.particleGreen1); } if (oldObjectData.particleGreen2 !== newObjectData.particleGreen2) { this.setParticleGreen2(newObjectData.particleGreen2); } if (oldObjectData.particleBlue1 !== newObjectData.particleBlue1) { this.setParticleBlue1(newObjectData.particleBlue1); } if (oldObjectData.particleBlue2 !== newObjectData.particleBlue2) { this.setParticleBlue2(newObjectData.particleBlue2); } if (oldObjectData.particleSize1 !== newObjectData.particleSize1) { this.setParticleSize1(newObjectData.particleSize1); } if (oldObjectData.particleSize2 !== newObjectData.particleSize2) { this.setParticleSize2(newObjectData.particleSize2); } if (oldObjectData.sizeParam !== newObjectData.sizeParam) { this.sizeParam = newObjectData.sizeParam; this._sizeDirty = true; } if (oldObjectData.particleAlpha1 !== newObjectData.particleAlpha1) { this.setParticleAlpha1(newObjectData.particleAlpha1); } if (oldObjectData.particleAlpha2 !== newObjectData.particleAlpha2) { this.setParticleAlpha2(newObjectData.particleAlpha2); } if (oldObjectData.textureParticleName !== newObjectData.textureParticleName) { this.setTexture(newObjectData.textureParticleName, this._runtimeScene); } if (oldObjectData.flow !== newObjectData.flow) { this.setFlow(newObjectData.flow); } if (oldObjectData.tank !== newObjectData.tank) { this.setTank(newObjectData.tank); } if (oldObjectData.destroyWhenNoParticles !== newObjectData.destroyWhenNoParticles) { this.destroyWhenNoParticles = newObjectData.destroyWhenNoParticles; } if (oldObjectData.particleSizeRandomness1 !== newObjectData.particleSizeRandomness1 || oldObjectData.particleSizeRandomness2 !== newObjectData.particleSizeRandomness2 || oldObjectData.particleAngle1 !== newObjectData.particleAngle1 || oldObjectData.particleAngle2 !== newObjectData.particleAngle2 || oldObjectData.maxParticleNb !== newObjectData.maxParticleNb || oldObjectData.additive !== newObjectData.additive || oldObjectData.rendererType !== newObjectData.rendererType || oldObjectData.rendererParam1 !== newObjectData.rendererParam1 || oldObjectData.rendererParam2 !== newObjectData.rendererParam2) { // Destroy the renderer, ensure it's removed from the layer. var layer = this._runtimeScene.getLayer(this.layer); layer.getRenderer().removeRendererObject(this._renderer.getRendererObject()); this._renderer.destroy(); // and recreate the renderer, which will add itself to the layer. this._renderer = new gdjs.ParticleEmitterObjectRenderer(this._runtimeScene, this, newObjectData); // Consider every state dirty as the renderer was just re-created, so it needs // to be repositioned, angle updated, etc... this._posDirty = this._angleDirty = this._forceDirty = this._zoneRadiusDirty = true; this._lifeTimeDirty = this._gravityDirty = this._colorDirty = this._sizeDirty = true; this._alphaDirty = this._flowDirty = this._textureDirty = true; } return true; }; gdjs.ParticleEmitterObject.prototype.update = function(runtimeScene){ if(this._posDirty){ this._renderer.setPosition(this.getX(), this.getY()); } if(this._angleDirty){ var angle = this.getAngle(); if(this.singleAngle) this._renderer.setAngle(this.angle - this.angleB/2.0, this.angle + this.angleB/2.0); else this._renderer.setAngle(this.angle + this.angleA, this.angle + this.angleB); } if(this._forceDirty){ this._renderer.setForce(this.forceMin, this.forceMax); } if(this._zoneRadiusDirty){ this._renderer.setZoneRadius(this.zoneRadius); } if(this._lifeTimeDirty){ this._renderer.setLifeTime(this.lifeTimeMin, this.lifeTimeMax); } if(this._gravityDirty){ this._renderer.setGravity(this.gravityX, this.gravityY); } if(this._colorDirty){ this._renderer.setColor(this.colorR1, this.colorG1, this.colorB1, this.colorR2, this.colorG2, this.colorB2); } if(this._sizeDirty && this.sizeParam === "Mutable"){ this._renderer.setSize(this.size1, this.size2); } if(this._alphaDirty){ this._renderer.setAlpha(this.alpha1, this.alpha2); } if(this._flowDirty){ this._renderer.setFlow(this.flow, this.tank); } if(this._textureDirty){ this._renderer.setTextureName(this.texture, runtimeScene); } this._posDirty = this._angleDirty = this._forceDirty = this._zoneRadiusDirty = false; this._lifeTimeDirty = this._gravityDirty = this._colorDirty = this._sizeDirty = false; this._alphaDirty = this._flowDirty = this._textureDirty = false; this._renderer.update(this.getElapsedTime(runtimeScene)/1000.0); if(this.tank > 0 && this._renderer.getTotalParticleCount() > this.tank){ this._renderer.stop(); } if(this._renderer.hasStarted() && this._renderer.getParticleCount() === 0 && this.destroyWhenNoParticles){ this.deleteFromScene(runtimeScene); } }; gdjs.ParticleEmitterObject.prototype.onDestroyFromScene = function(runtimeScene){ this._renderer.destroy(); gdjs.RuntimeObject.prototype.onDestroyFromScene.call(this, runtimeScene); }; gdjs.ParticleEmitterObject.prototype.getEmitterForceMin = function(){ return this.forceMin; }; gdjs.ParticleEmitterObject.prototype.setEmitterForceMin = function(force){ if(force < 0) force = 0; if(this.forceMin !== force){ this._forceDirty = true; this.forceMin = force; } }; gdjs.ParticleEmitterObject.prototype.getEmitterForceMax = function(){ return this.forceMax; }; gdjs.ParticleEmitterObject.prototype.setEmitterForceMax = function(force){ if(force < 0) force = 0; if(this.forceMax !== force){ this._forceDirty = true; this.forceMax = force; } }; gdjs.ParticleEmitterObject.prototype.getEmitterAngle = function(){ return (this.angleA + this.angleB)/2.0; }; gdjs.ParticleEmitterObject.prototype.setEmitterAngle = function(angle){ var oldAngle = this.getEmitterAngle(); if(angle !== oldAngle){ this._angleDirty = true; this.angleA += angle - oldAngle; this.angleB += angle - oldAngle; } }; gdjs.ParticleEmitterObject.prototype.getEmitterAngleA = function(){ return this.angleA; }; gdjs.ParticleEmitterObject.prototype.setEmitterAngleA = function(angle){ if(this.angleA !== angle){ this._angleDirty = true; this.angleA = angle; } }; gdjs.ParticleEmitterObject.prototype.getEmitterAngleB = function(){ return this.angleB; }; gdjs.ParticleEmitterObject.prototype.setEmitterAngleB = function(angle){ if(this.angleB !== angle){ this._angleDirty = true; this.angleB = angle; } }; gdjs.ParticleEmitterObject.prototype.getConeSprayAngle = function(){ return Math.abs(this.angleB - this.angleA); }; gdjs.ParticleEmitterObject.prototype.setConeSprayAngle = function(angle){ var oldCone = this.getConeSprayAngle(); if(oldCone !== angle){ this._angleDirty = true; var midAngle = this.getEmitterAngle(); this.angleA = midAngle - angle/2.0; this.angleB = midAngle + angle/2.0; } }; gdjs.ParticleEmitterObject.prototype.getZoneRadius = function(){ return this.zoneRadius; }; gdjs.ParticleEmitterObject.prototype.setZoneRadius = function(radius){ if(radius < 0) radius = 0; if(this.zoneRadius !== radius && radius > 0){ this._zoneRadiusDirty = true; this.zoneRadius = radius; } }; gdjs.ParticleEmitterObject.prototype.getParticleLifeTimeMin = function(){ return this.lifeTimeMin; }; gdjs.ParticleEmitterObject.prototype.setParticleLifeTimeMin = function(lifeTime){ if(lifeTime < 0) lifeTime = 0; if(this.lifeTimeMin !== lifeTime){ this._lifeTimeDirty = true; this.lifeTimeMin = lifeTime; } }; gdjs.ParticleEmitterObject.prototype.getParticleLifeTimeMax = function(){ return this.lifeTimeMax; }; gdjs.ParticleEmitterObject.prototype.setParticleLifeTimeMax = function(lifeTime){ if(lifeTime < 0) lifeTime = 0; if(this.lifeTimeMax !== lifeTime){ this._lifeTimeDirty = true; this.lifeTimeMax = lifeTime; } }; gdjs.ParticleEmitterObject.prototype.getParticleGravityX = function(){ return this.gravityX; }; gdjs.ParticleEmitterObject.prototype.setParticleGravityX = function(x){ if(this.gravityX !== x){ this._gravityDirty = true; this.gravityX = x; } }; gdjs.ParticleEmitterObject.prototype.getParticleGravityY = function(){ return this.gravityY; }; gdjs.ParticleEmitterObject.prototype.setParticleGravityY = function(y){ if(this.gravityY !== y){ this._gravityDirty = true; this.gravityY = y; } }; gdjs.ParticleEmitterObject.prototype.getParticleGravityAngle = function(){ return Math.atan2(this.gravityY, this.gravityX)*180.0/Math.PI; }; gdjs.ParticleEmitterObject.prototype.setParticleGravityAngle = function(angle){ var oldAngle = this.getParticleGravityAngle(); if(oldAngle !== angle){ this._gravityDirty = true; var length = this.getParticleGravityLength(); this.gravityX = length*Math.cos(angle*Math.PI/180.0); this.gravityY = length*Math.sin(angle*Math.PI/180.0); } }; gdjs.ParticleEmitterObject.prototype.getParticleGravityLength = function(){ return Math.sqrt(this.gravityX*this.gravityX + this.gravityY*this.gravityY); }; gdjs.ParticleEmitterObject.prototype.setParticleGravityLength = function(length){ if(length < 0) length = 0; var oldLength = this.getParticleGravityLength(); if(oldLength !== length){ this._gravityDirty = true; this.gravityX *= length/oldLength; this.gravityY *= length/oldLength; } }; gdjs.ParticleEmitterObject.prototype.getParticleRed1 = function(){ return this.colorR1; }; gdjs.ParticleEmitterObject.prototype.setParticleRed1 = function(red){ if(red < 0) red = 0; if(red > 255) red = 255; if(this.colorR1 !== red){ this._colorDirty = true; this.colorR1 = red; } }; gdjs.ParticleEmitterObject.prototype.getParticleRed2 = function(){ return this.colorR2; }; gdjs.ParticleEmitterObject.prototype.setParticleRed2 = function(red){ if(red < 0) red = 0; if(red > 255) red = 255; if(this.colorR2 !== red){ this._colorDirty = true; this.colorR2 = red; } }; gdjs.ParticleEmitterObject.prototype.getParticleGreen1 = function(){ return this.colorG1; }; gdjs.ParticleEmitterObject.prototype.setParticleGreen1 = function(green){ if(green < 0) green = 0; if(green > 255) green = 255; if(this.colorG1 !== green){ this._colorDirty = true; this.colorG1 = green; } }; gdjs.ParticleEmitterObject.prototype.getParticleGreen2 = function(){ return this.colorG2; }; gdjs.ParticleEmitterObject.prototype.setParticleGreen2 = function(green){ if(green < 0) green = 0; if(green > 255) green = 255; if(this.colorG2 !== green){ this._colorDirty = true; this.colorG2 = green; } }; gdjs.ParticleEmitterObject.prototype.getParticleBlue1 = function(){ return this.colorB1; }; gdjs.ParticleEmitterObject.prototype.setParticleBlue1 = function(blue){ if(blue < 0) blue = 0; if(blue > 255) blue = 255; if(this.colorB1 !== blue){ this._colorDirty = true; this.colorB1 = blue; } }; gdjs.ParticleEmitterObject.prototype.getParticleBlue2 = function(){ return this.colorB2; }; gdjs.ParticleEmitterObject.prototype.setParticleBlue2 = function(blue){ if(blue < 0) blue = 0; if(blue > 255) blue = 255; if(this.colorB2 !== blue){ this._colorDirty = true; this.colorB2 = blue; } }; gdjs.ParticleEmitterObject.prototype.setParticleColor1 = function(rgbColor){ var colors = rgbColor.split(";"); if ( colors.length < 3 ) return; this.setParticleRed1(parseInt(colors[0], 10)); this.setParticleGreen1(parseInt(colors[1], 10)); this.setParticleBlue1(parseInt(colors[2], 10)); }; gdjs.ParticleEmitterObject.prototype.setParticleColor2 = function(rgbColor){ var colors = rgbColor.split(";"); if ( colors.length < 3 ) return; this.setParticleRed2(parseInt(colors[0], 10)); this.setParticleGreen2(parseInt(colors[1], 10)); this.setParticleBlue2(parseInt(colors[2], 10)); }; gdjs.ParticleEmitterObject.prototype.getParticleSize1 = function(){ return this.size1; }; gdjs.ParticleEmitterObject.prototype.setParticleSize1 = function(size){ if(size < 0) size = 0; if(this.size1 !== size){ this._sizeDirty = true; this.size1 = size; } }; gdjs.ParticleEmitterObject.prototype.getParticleSize2 = function(){ return this.size2; }; gdjs.ParticleEmitterObject.prototype.setParticleSize2 = function(size){ if(this.size2 !== size){ this._sizeDirty = true; this.size2 = size; } }; gdjs.ParticleEmitterObject.prototype.getParticleAlpha1 = function(){ return this.alpha1; }; gdjs.ParticleEmitterObject.prototype.setParticleAlpha1 = function(alpha){ if(this.alpha1 !== alpha){ this._alphaDirty = true; this.alpha1 = alpha; } }; gdjs.ParticleEmitterObject.prototype.getParticleAlpha2 = function(){ return this.alpha2; }; gdjs.ParticleEmitterObject.prototype.setParticleAlpha2 = function(alpha){ if(this.alpha2 !== alpha){ this._alphaDirty = true; this.alpha2 = alpha; } }; gdjs.ParticleEmitterObject.prototype.noMoreParticles = function(){ this._renderer.stop(); }; gdjs.ParticleEmitterObject.prototype.recreateParticleSystem = function(){ this._renderer.recreate(); }; gdjs.ParticleEmitterObject.prototype.getFlow = function(){ return this.flow; }; gdjs.ParticleEmitterObject.prototype.setFlow = function(flow){ if(this.flow !== flow){ this.flow = flow; this._flowDirty = true; } }; gdjs.ParticleEmitterObject.prototype.getTank = function(){ return this.tank; }; gdjs.ParticleEmitterObject.prototype.setTank = function(tank){ this.tank = tank; }; gdjs.ParticleEmitterObject.prototype.getTexture = function(){ return this.texture; }; gdjs.ParticleEmitterObject.prototype.setTexture = function(texture, runtimeScene){ if(this.texture !== texture){ if(this._renderer.isTextureNameValid(texture, runtimeScene)){ this.texture = texture; this._textureDirty = true; } } };