diff --git a/build.json b/build.json index f1600726..1800a6c8 100644 --- a/build.json +++ b/build.json @@ -7,6 +7,7 @@ "main" : [ "src/ParticleUtils.js", "src/Particle.js", + "src/AnimatedParticle.js", "src/Emitter.js" ] } diff --git a/dist/pixi-particles.js b/dist/pixi-particles.js index 26f3d678..47479d1d 100644 --- a/dist/pixi-particles.js +++ b/dist/pixi-particles.js @@ -65,7 +65,7 @@ /** * Multiplies the x and y values of this point by a value. - * @method scaleBy + * @method scaleBy * @static * @param {PIXI.Point} point The point to scaleBy * @param value {Number} The value to scale by. @@ -89,8 +89,9 @@ }; /** - * Converts a hex string from "#AARRGGBB", "#RRGGBB", "0xAARRGGBB", "0xRRGGBB", "AARRGGBB", or "RRGGBB" - * to an array of ints of 0-255 or Numbers from 0-1, as [r, g, b, (a)]. + * Converts a hex string from "#AARRGGBB", "#RRGGBB", "0xAARRGGBB", "0xRRGGBB", + * "AARRGGBB", or "RRGGBB" to an array of ints of 0-255 or Numbers from 0-1, as + * [r, g, b, (a)]. * @method hexToRGB * @param {String} color The input color string. * @param {Array} output An array to put the output in. If omitted, a new array is created. @@ -122,11 +123,13 @@ }; /** - * Generates a custom ease function, based on the GreenSock custom ease, as demonstrated + * Generates a custom ease function, based on the GreenSock custom ease, as demonstrated * by the related tool at http://www.greensock.com/customease/. * @method generateEase - * @param {Array} segments An array of segments, as created by http://www.greensock.com/customease/. - * @return {Function} A function that calculates the percentage of change at a given point in time (0-1 inclusive). + * @param {Array} segments An array of segments, as created by + * http://www.greensock.com/customease/. + * @return {Function} A function that calculates the percentage of change at + * a given point in time (0-1 inclusive). * @static */ ParticleUtils.generateEase = function(segments) @@ -135,8 +138,9 @@ var oneOverQty = 1 / qty; /* * Calculates the percentage of change at a given point in time (0-1 inclusive). - * @param time The time of the ease, 0-1 inclusive. - * @return The percentage of the change, 0-1 inclusive (unless your ease goes outside those bounds). + * @param {Number} time The time of the ease, 0-1 inclusive. + * @return {Number} The percentage of the change, 0-1 inclusive (unless your + * ease goes outside those bounds). */ var simpleEase = function(time) { @@ -187,7 +191,7 @@ // to make it not enumerable set the enumerable property to false Object.defineProperty(Array.prototype, 'shuffle', { enumerable: false, - writable:false, + writable:false, value: function() { for(var j, x, i = this.length; i; j = Math.floor(Math.random() * i), x = this[--i], this[i] = this[j], this[j] = x); return this; @@ -228,7 +232,9 @@ */ var Particle = function(emitter) { - var art = emitter.particleImages[0] instanceof PIXI.Texture ? [emitter.particleImages[0]] : emitter.particleImages[0]; + var art = emitter.particleImages[0] instanceof PIXI.Texture ? + [emitter.particleImages[0]] : + emitter.particleImages[0]; PIXI.MovieClip.call(this, art); /** @@ -238,7 +244,7 @@ this.emitter = emitter; this.anchor.x = this.anchor.y = 0.5; /** - * The velocity of the particle. Speed may change, but the angle also + * The velocity of the particle. Speed may change, but the angle also * contained in velocity is constant. * @property {PIXI.Point} velocity */ @@ -461,7 +467,20 @@ //determine our interpolation value var lerp = this.age * this._oneOverLife;//lifetime / maxLife; if (this.ease) - lerp = this.ease(lerp); + { + if(this.ease.length == 4) + { + //the t, b, c, d parameters that some tween libraries use + //(time, initial value, end value, duration) + lerp = this.ease(lerp, 0, 1, 1); + } + else + { + //the simplified version that we like that takes + //one parameter, time from 0-1. TweenJS eases provide this usage. + lerp = this.ease(lerp); + } + } //interpolate alpha if (this._doAlpha) @@ -534,6 +553,147 @@ cloudkid.Particle = Particle; +}(cloudkid)); +/** +* @module cloudkid +*/ +(function(cloudkid, undefined) { + + "use strict"; + + var ParticleUtils = cloudkid.ParticleUtils, + Particle = cloudkid.Particle; + + /** + * An individual particle image with an animation. You shouldn't have to deal with these. + * @class AnimatedParticle + * @constructor + * @param {Emitter} emitter The emitter that controls this AnimatedParticle. + */ + var AnimatedParticle = function(emitter) + { + Particle.call(this, emitter); + + /** + * Array used to avoid damaging previous texture arrays + * when applyArt() passes a texture instead of an array. + * @property {Array} _helperTextures + * @private + */ + this._helperTextures = []; + }; + + // Reference to the super class + var s = Particle.prototype; + // Reference to the prototype + var p = AnimatedParticle.prototype = Object.create(s); + + /** + * Initializes the particle for use, based on the properties that have to + * have been set already on the particle. + * @method init + */ + p.init = function() + { + s.init.call(this); + + //set the standard PIXI animationSpeed + if(this.extraData) + { + //fps will work differently for CloudKid's fork of PIXI than + //standard PIXI, where it will just be a variable + if(this.extraData.fps) + { + this.fps = this.extraData.fps; + } + else + { + this.fps = 60; + } + var animationSpeed = this.extraData.animationSpeed || 1; + if(animationSpeed == "matchLife") + { + this.loop = false; + //animation should end when the particle does + if(this.hasOwnProperty("_duration")) + { + //CloudKid's fork of PIXI redoes how MovieClips animate, + //with duration and elapsed time + this.animationSpeed = this._duration / this.maxLife; + } + else + { + //standard PIXI - assume game tick rate of 60 fps + this.animationSpeed = this.textures.length / this.maxLife / 60; + } + } + else + { + this.loop = true; + this.animationSpeed = animationSpeed; + } + } + else + { + this.loop = true; + this.animationSpeed = 1; + } + this.play();//start playing + }; + + /** + * Sets the textures for the particle. + * @method applyArt + * @param {Array} art An array of PIXI.Texture objects for this animated particle. + */ + p.applyArt = function(art) + { + if(Array.isArray(art)) + this.textures = art; + else + { + this._helperTextures[0] = art; + this.textures = this._helperTextures; + } + this.gotoAndStop(0); + }; + + /** + * Updates the particle. + * @method update + * @param {Number} delta Time elapsed since the previous frame, in __seconds__. + */ + p.update = function(delta) + { + s.update.call(this, delta); + if(this.age < this.maxLife) + { + //only animate the particle if it is still alive + if(this._duration) + { + //work with CloudKid's fork + this.updateAnim(delta); + } + else + { + //standard PIXI - movieclip will advance automatically - this means + //that the movieclip will animate even if the emitter (and the particles) + //are paused + } + } + }; + + /** + * Destroys the particle, removing references and preventing future use. + * @method destroy + */ + p.destroy = function() + { + s.destroy.call(this); + }; + + cloudkid.AnimatedParticle = AnimatedParticle; + }(cloudkid)); /** * @module cloudkid @@ -549,18 +709,20 @@ * A particle emitter. * @class Emitter * @constructor - * @param {PIXI.DisplayObjectContainer} particleParent The display object to add the particles to. - * @param {Array|PIXI.Texture} [particleImages] A texture or array of textures to use for the particles. + * @param {PIXI.DisplayObjectContainer} particleParent The display object to add the + * particles to. + * @param {Array|PIXI.Texture} [particleImages] A texture or array of textures to use + * for the particles. * @param {Object} [config] A configuration object containing settings for the emitter. */ var Emitter = function(particleParent, particleImages, config) { /** - * The constructor used to create new particles. The default is - * the built in particle class. - * @property {Function} _particleConstructor - * @private - */ + * The constructor used to create new particles. The default is + * the built in particle class. + * @property {Function} _particleConstructor + * @private + */ this._particleConstructor = Particle; //properties for individual particles /** @@ -672,8 +834,9 @@ */ this.particleBlendMode = 0; /** - * An easing function for nonlinear interpolation of values. Accepts a single parameter of time - * as a value from 0-1, inclusive. Expected outputs are values from 0-1, inclusive. + * An easing function for nonlinear interpolation of values. Accepts a single + * parameter of time as a value from 0-1, inclusive. Expected outputs are values + * from 0-1, inclusive. * @property {Function} customEase */ this.customEase = null; @@ -705,7 +868,8 @@ this.emitterLifetime = -1; /** * Position at which to spawn particles, relative to the emitter's owner's origin. - * For example, the flames of a rocket travelling right might have a spawnPos of {x:-50, y:0} + * For example, the flames of a rocket travelling right might have a spawnPos + * of {x:-50, y:0}. * to spawn at the rear of the rocket. * To change this, use updateSpawnPos(). * @property {PIXI.Point} spawnPos @@ -713,7 +877,8 @@ */ this.spawnPos = null; /** - * How the particles will be spawned. Valid types are "point", "rectangle", "circle", "burst". + * How the particles will be spawned. Valid types are "point", "rectangle", + * "circle", "burst". * @property {String} spawnType * @readOnly */ @@ -753,7 +918,8 @@ */ this.angleStart = 0; /** - * Rotation of the emitter or emitter's owner in degrees. This is added to the calculated spawn angle. + * Rotation of the emitter or emitter's owner in degrees. This is added to + * the calculated spawn angle. * To change this, use rotate(). * @property {Number} rotation * @default 0 @@ -761,8 +927,8 @@ */ this.rotation = 0; /** - * The world position of the emitter's owner, to add spawnPos to when spawning particles. To change this, - * use updateSpawnOrigin(). + * The world position of the emitter's owner, to add spawnPos to when + * spawning particles. To change this, use updateSpawnOrigin(). * @property {PIXI.Point} ownerPos * @default {x:0, y:0} * @readOnly @@ -839,11 +1005,11 @@ /** - * The constructor used to create new particles. The default is - * the built in Particle class. Setting this will dump any active or - * pooled particles, if the emitter has already been used. - * @property {Function} particleConstructor - */ + * The constructor used to create new particles. The default is + * the built in Particle class. Setting this will dump any active or + * pooled particles, if the emitter has already been used. + * @property {Function} particleConstructor + */ Object.defineProperty(p, "particleConstructor", { get: function() { return this._particleConstructor; }, @@ -863,7 +1029,8 @@ /** * Sets up the emitter based on the config settings. * @method init - * @param {Array|PIXI.Texture} particleImages A texture or array of textures to use for the particles. + * @param {Array|PIXI.Texture} particleImages A texture or array of textures to + * use for the particles. * @param {Object} config A configuration object containing settings for the emitter. */ p.init = function(particleImages, config) @@ -873,9 +1040,12 @@ //clean up any existing particles this.cleanup(); //set up the array of textures - this.particleImages = particleImages instanceof PIXI.Texture ? [particleImages] : particleImages; - //particles from different base textures will be slower in WebGL than if they were from one spritesheet - if(this.particleImages.length > 1) + this.particleImages = particleImages instanceof PIXI.Texture ? + [particleImages] : + particleImages; + //particles from different base textures will be slower in WebGL than if they + //were from one spritesheet + if(true && this.particleImages.length > 1) { for(var i = this.particleImages.length - 1; i > 0; --i) { @@ -961,8 +1131,12 @@ //use the custom ease if provided if (config.ease) { - this.customEase = typeof config.ease == "function" ? config.ease : ParticleUtils.generateEase(config.ease); + this.customEase = typeof config.ease == "function" ? + config.ease : + ParticleUtils.generateEase(config.ease); } + else + this.customEase = null; this.extraData = config.extraData || null; ////////////////////////// // Emitter Properties // @@ -1176,11 +1350,13 @@ //only make the particle if it wouldn't immediately destroy itself if(-this._spawnTimer < lifetime) { - //If the position has changed and this isn't the first spawn, interpolate the spawn position + //If the position has changed and this isn't the first spawn, + //interpolate the spawn position var emitPosX, emitPosY; if (this._prevPosIsValid && this._posChanged) { - var lerp = 1 + this._spawnTimer / delta;//1 - _spawnTimer / delta, but _spawnTimer is negative + //1 - _spawnTimer / delta, but _spawnTimer is negative + var lerp = 1 + this._spawnTimer / delta; emitPosX = (curX - prevX) * lerp + prevX; emitPosY = (curY - prevY) * lerp + prevY; } @@ -1194,12 +1370,20 @@ for(var len = Math.min(this.particlesPerWave, this.maxParticles - this._activeParticles.length); i < len; ++i) { //create particle - var p = this._pool.length ? this._pool.pop() : new this.particleConstructor(this); + var p = this._pool.length ? + this._pool.pop() : + new this.particleConstructor(this); //set a random texture if we have more than one if(this.particleImages.length > 1) + { p.applyArt(this.particleImages.random()); + } else - p.applyArt(this.particleImages[0]);//if they are actually the same texture, this call will quit early + { + //if they are actually the same texture, a standard particle + //will quit early from the texture setting in setTexture(). + p.applyArt(this.particleImages[0]); + } //set up the start and end values p.startAlpha = this.startAlpha; p.endAlpha = this.endAlpha; @@ -1272,7 +1456,8 @@ */ p._spawnPoint = function(p, emitPosX, emitPosY, i) { - //set the initial rotation/direction of the particle based on starting particle angle and rotation of emitter + //set the initial rotation/direction of the particle based on + //starting particle angle and rotation of emitter if (this.minStartRotation == this.maxStartRotation) p.rotation = this.minStartRotation + this.rotation; else @@ -1293,7 +1478,8 @@ */ p._spawnRect = function(p, emitPosX, emitPosY, i) { - //set the initial rotation/direction of the particle based on starting particle angle and rotation of emitter + //set the initial rotation/direction of the particle based on starting + //particle angle and rotation of emitter if (this.minStartRotation == this.maxStartRotation) p.rotation = this.minStartRotation + this.rotation; else @@ -1318,7 +1504,8 @@ */ p._spawnCircle = function(p, emitPosX, emitPosY, i) { - //set the initial rotation/direction of the particle based on starting particle angle and rotation of emitter + //set the initial rotation/direction of the particle based on starting + //particle angle and rotation of emitter if (this.minStartRotation == this.maxStartRotation) p.rotation = this.minStartRotation + this.rotation; else @@ -1346,7 +1533,8 @@ */ p._spawnBurst = function(p, emitPosX, emitPosY, i) { - //set the initial rotation/direction of the particle based on spawn angle and rotation of emitter + //set the initial rotation/direction of the particle based on spawn + //angle and rotation of emitter if(this.particleSpacing === 0) p.rotation = Math.random() * 360; else diff --git a/dist/pixi-particles.min.js b/dist/pixi-particles.min.js index e2b6baa4..f1864fa0 100644 --- a/dist/pixi-particles.min.js +++ b/dist/pixi-particles.min.js @@ -1,2 +1,2 @@ /*! PixiParticles 1.2.2 */ -!function(){"use strict";window.cloudkid=window.cloudkid||{};var a={},b=a.DEG_TO_RADS=Math.PI/180;a.rotatePoint=function(a,c){if(a){a*=b;var d=Math.sin(a),e=Math.cos(a),f=c.x*e-c.y*d,g=c.x*d+c.y*e;c.x=f,c.y=g}},a.combineRGBComponents=function(a,b,c){return a<<16|b<<8|c},a.normalize=function(b){var c=1/a.length(b);b.x*=c,b.y*=c},a.scaleBy=function(a,b){a.x*=b,a.y*=b},a.length=function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},a.hexToRGB=function(a,b){b?b.length=0:b=[],"#"==a.charAt(0)?a=a.substr(1):0===a.indexOf("0x")&&(a=a.substr(2));var c;return 8==a.length&&(c=a.substr(0,2),a=a.substr(2)),b.push(parseInt(a.substr(0,2),16)),b.push(parseInt(a.substr(2,2),16)),b.push(parseInt(a.substr(4,2),16)),c&&b.push(parseInt(c,16)),b},a.generateEase=function(a){var b=a.length,c=1/b,d=function(d){var e,f,g=b*d|0;return e=(d-g*c)*b,f=a[g]||a[b-1],f.s+e*(2*(1-e)*(f.cp-f.s)+e*(f.e-f.s))};return d},a.getBlendMode=function(a){if(!a)return PIXI.blendModes.NORMAL;for(a=a.toUpperCase();a.indexOf(" ")>=0;)a=a.replace(" ","_");return PIXI.blendModes[a]||PIXI.blendModes.NORMAL},cloudkid.ParticleUtils=a,Array.prototype.shuffle||Object.defineProperty(Array.prototype,"shuffle",{enumerable:!1,writable:!1,value:function(){for(var a,b,c=this.length;c;a=Math.floor(Math.random()*c),b=this[--c],this[c]=this[a],this[a]=b);return this}}),Array.prototype.random||Object.defineProperty(Array.prototype,"random",{enumerable:!1,writable:!1,value:function(){return this[Math.floor(Math.random()*this.length)]}})}(),function(a){"use strict";var b=a.ParticleUtils,c=function(a){var b=a.particleImages[0]instanceof PIXI.Texture?[a.particleImages[0]]:a.particleImages[0];PIXI.MovieClip.call(this,b),this.emitter=a,this.anchor.x=this.anchor.y=.5,this.velocity=new PIXI.Point,this.maxLife=0,this.age=0,this.ease=null,this.extraData=null,this.startAlpha=0,this.endAlpha=0,this.startSpeed=0,this.endSpeed=0,this.acceleration=null,this.startScale=0,this.endScale=0,this.startColor=null,this._sR=0,this._sG=0,this._sB=0,this.endColor=null,this._eR=0,this._eG=0,this._eB=0,this._doAlpha=!1,this._doScale=!1,this._doSpeed=!1,this._doColor=!1,this._oneOverLife=0},d=c.prototype=Object.create(PIXI.MovieClip.prototype);d.init=function(){this.age=0,this.velocity.x=this.startSpeed,this.velocity.y=0,b.rotatePoint(this.rotation,this.velocity),this.rotation*=b.DEG_TO_RADS,this.rotationSpeed*=b.DEG_TO_RADS,this.alpha=this.startAlpha,this.scale.x=this.scale.y=this.startScale,this.startColor&&(this._sR=this.startColor[0],this._sG=this.startColor[1],this._sB=this.startColor[2],this.endColor&&(this._eR=this.endColor[0],this._eG=this.endColor[1],this._eB=this.endColor[2])),this._doAlpha=this.startAlpha!=this.endAlpha,this._doSpeed=this.startSpeed!=this.endSpeed,this._doScale=this.startScale!=this.endScale,this._doColor=!!this.endColor,this._oneOverLife=1/this.maxLife,this.tint=b.combineRGBComponents(this._sR,this._sG,this._sB)},d.applyArt=function(a){this.setTexture(a)},d.update=function(a){if(this.age+=a,this.age>=this.maxLife)return void this.kill();var c=this.age*this._oneOverLife;if(this.ease&&(c=this.ease(c)),this._doAlpha&&(this.alpha=(this.endAlpha-this.startAlpha)*c+this.startAlpha),this._doScale){var d=(this.endScale-this.startScale)*c+this.startScale;this.scale.x=this.scale.y=d}if(this._doSpeed||0!==this.startSpeed||this.acceleration){if(this._doSpeed){var e=(this.endSpeed-this.startSpeed)*c+this.startSpeed;b.normalize(this.velocity),b.scaleBy(this.velocity,e)}else this.acceleration&&(this.velocity.x+=this.acceleration.x*a,this.velocity.y+=this.acceleration.y*a);this.position.x+=this.velocity.x*a,this.position.y+=this.velocity.y*a}if(this._doColor){var f=(this._eR-this._sR)*c+this._sR,g=(this._eG-this._sG)*c+this._sG,h=(this._eB-this._sB)*c+this._sB;this.tint=b.combineRGBComponents(f,g,h)}0!==this.rotationSpeed?this.rotation+=this.rotationSpeed*a:this.acceleration&&(this.rotation=Math.atan2(this.velocity.y,this.velocity.x))},d.kill=function(){this.emitter.recycle(this)},d.destroy=function(){this.emitter=null,this.velocity=null,this.startColor=this.endColor=null,this.ease=null},a.Particle=c}(cloudkid),function(a){"use strict";var b=a.ParticleUtils,c=a.Particle,d=function(a,b,d){this._particleConstructor=c,this.particleImages=null,this.startAlpha=1,this.endAlpha=1,this.startSpeed=0,this.endSpeed=0,this.acceleration=null,this.startScale=1,this.endScale=1,this.minimumScaleMultiplier=1,this.startColor=null,this.endColor=null,this.minLifetime=0,this.maxLifetime=0,this.minStartRotation=0,this.maxStartRotation=0,this.minRotationSpeed=0,this.maxRotationSpeed=0,this.particleBlendMode=0,this.customEase=null,this.extraData=null,this.frequency=0,this.maxParticles=1e3,this.emitterLifetime=-1,this.spawnPos=null,this.spawnType=null,this._spawnFunc=null,this.spawnRect=null,this.spawnCircle=null,this.particlesPerWave=1,this.particleSpacing=0,this.angleStart=0,this.rotation=0,this.ownerPos=null,this._prevEmitterPos=null,this._prevPosIsValid=!1,this._posChanged=!1,this.parent=a,this.addAtBack=!1,this._emit=!1,this._spawnTimer=0,this._emitterLife=-1,this._activeParticles=[],this._pool=[],b&&d&&this.init(b,d)},e=d.prototype={},f=new PIXI.Point;Object.defineProperty(e,"particleConstructor",{get:function(){return this._particleConstructor},set:function(a){a!=this._particleConstructor&&(this._particleConstructor=a,this._activeParticles.length&&(this._activeParticles.length=0),this._pool.length&&(this._pool.length=0))}}),e.init=function(a,c){if(a&&c){if(this.cleanup(),this.particleImages=a instanceof PIXI.Texture?[a]:a,this.particleImages.length>1)for(var d=this.particleImages.length-1;d>0;--d)if(this.particleImages[d].baseTexture!=this.particleImages[d-1].baseTexture){window.Debug?Debug.warn("PixiParticles: using particle textures from different images may hinder performance in WebGL"):window.console&&console.warn("PixiParticles: using particle textures from different images may hinder performance in WebGL");break}c.alpha?(this.startAlpha=c.alpha.start,this.endAlpha=c.alpha.end):this.startAlpha=this.endAlpha=1,c.speed?(this.startSpeed=c.speed.start,this.endSpeed=c.speed.end):this.startSpeed=this.endSpeed=0;var e=c.acceleration;switch(e&&(e.x||e.y)?(this.endSpeed=this.startSpeed,this.acceleration=new PIXI.Point(e.x,e.y)):this.acceleration=null,c.scale?(this.startScale=c.scale.start,this.endScale=c.scale.end,this.minimumScaleMultiplier=c.scale.minimumScaleMultiplier||1):this.startScale=this.endScale=this.minimumScaleMultiplier=1,c.color&&(this.startColor=b.hexToRGB(c.color.start),this.endColor=c.color.start!=c.color.end?b.hexToRGB(c.color.end):null),c.startRotation?(this.minStartRotation=c.startRotation.min,this.maxStartRotation=c.startRotation.max):this.minStartRotation=this.maxStartRotation=0,c.rotationSpeed?(this.minRotationSpeed=c.rotationSpeed.min,this.maxRotationSpeed=c.rotationSpeed.max):this.minRotationSpeed=this.maxRotationSpeed=0,this.minLifetime=c.lifetime.min,this.maxLifetime=c.lifetime.max,this.particleBlendMode=b.getBlendMode(c.blendMode),c.ease&&(this.customEase="function"==typeof c.ease?c.ease:b.generateEase(c.ease)),this.extraData=c.extraData||null,this.minAngle=this.maxAngle=0,this.spawnRect=this.spawnCircle=null,this.particlesPerWave=1,this.particleSpacing=0,this.angleStart=0,c.spawnType){case"rect":this.spawnType="rect",this._spawnFunc=this._spawnRect;var f=c.spawnRect;this.spawnRect=new PIXI.Rectangle(f.x,f.y,f.w,f.h);break;case"circle":this.spawnType="circle",this._spawnFunc=this._spawnCircle;var g=c.spawnCircle;this.spawnCircle=new PIXI.Circle(g.x,g.y,g.r);break;case"arc":this.spawnType="arc",this._spawnFunc=this._spawnArc,this.minAngle=c.angle.min,this.maxAngle=c.angle.max;break;case"burst":this.spawnType="burst",this._spawnFunc=this._spawnBurst,this.particlesPerWave=c.particlesPerWave,this.particleSpacing=c.particleSpacing,this.angleStart=c.angleStart?c.angleStart:0;break;case"point":this.spawnType="point",this._spawnFunc=this._spawnPoint;break;default:this.spawnType="point",this._spawnFunc=this._spawnPoint}this.frequency=c.frequency,this.emitterLifetime=c.emitterLifetime||-1,this.maxParticles=c.maxParticles>0?c.maxParticles:1e3,this.addAtBack=!!c.addAtBack,this.rotation=0,this.ownerPos=new PIXI.Point,this.spawnPos=new PIXI.Point(c.pos.x,c.pos.y),this._prevEmitterPos=this.spawnPos.clone(),this._prevPosIsValid=!1,this._spawnTimer=0,this.emit=!0}},e.recycle=function(a){var b=this._activeParticles.indexOf(a);b=0;--b)this._activeParticles[b].update(a);var c,d;this._prevPosIsValid&&(c=this._prevEmitterPos.x,d=this._prevEmitterPos.y);var e=this.ownerPos.x+this.spawnPos.x,f=this.ownerPos.y+this.spawnPos.y;if(this.emit)for(this._spawnTimer-=a;this._spawnTimer<=0;){if(this._emitterLife>0&&(this._emitterLife-=this.frequency,this._emitterLife<=0)){this._spawnTimer=0,this._emitterLife=0,this.emit=!1;break}if(this._activeParticles.length>=this.maxParticles)this._spawnTimer+=this.frequency;else{var g;if(g=this.minLifetime==this.maxLifetime?this.minLifetime:Math.random()*(this.maxLifetime-this.minLifetime)+this.minLifetime,-this._spawnTimerb;++b){var l=this._pool.length?this._pool.pop():new this.particleConstructor(this);if(l.applyArt(this.particleImages.length>1?this.particleImages.random():this.particleImages[0]),l.startAlpha=this.startAlpha,l.endAlpha=this.endAlpha,l.startSpeed=this.startSpeed,l.endSpeed=this.endSpeed,l.acceleration=this.acceleration,1!=this.minimumScaleMultiplier){var m=Math.random()*(1-this.minimumScaleMultiplier)+this.minimumScaleMultiplier;l.startScale=this.startScale*m,l.endScale=this.endScale*m}else l.startScale=this.startScale,l.endScale=this.endScale;l.startColor=this.startColor,l.endColor=this.endColor,l.rotationSpeed=this.minRotationSpeed==this.maxRotationSpeed?this.minRotationSpeed:Math.random()*(this.maxRotationSpeed-this.minRotationSpeed)+this.minRotationSpeed,l.maxLife=g,l.blendMode=this.particleBlendMode,l.ease=this.customEase,l.extraData=this.extraData,this._spawnFunc(l,h,i,b),l.init(),l.update(-this._spawnTimer),this.addAtBack?this.parent.addChildAt(l,0):this.parent.addChild(l),this._activeParticles.push(l)}}this._spawnTimer+=this.frequency}}this._posChanged&&(this._prevEmitterPos.x=e,this._prevEmitterPos.y=f,this._prevPosIsValid=!0,this._posChanged=!1)},e._spawnPoint=function(a,b,c){a.rotation=this.minStartRotation==this.maxStartRotation?this.minStartRotation+this.rotation:Math.random()*(this.maxStartRotation-this.minStartRotation)+this.minStartRotation+this.rotation,a.position.x=b,a.position.y=c},e._spawnRect=function(a,c,d){a.rotation=this.minStartRotation==this.maxStartRotation?this.minStartRotation+this.rotation:Math.random()*(this.maxStartRotation-this.minStartRotation)+this.minStartRotation+this.rotation,f.x=Math.random()*this.spawnRect.width+this.spawnRect.x,f.y=Math.random()*this.spawnRect.height+this.spawnRect.y,0!==this.rotation&&b.rotatePoint(this.rotation,f),a.position.x=c+f.x,a.position.y=d+f.y},e._spawnCircle=function(a,c,d){a.rotation=this.minStartRotation==this.maxStartRotation?this.minStartRotation+this.rotation:Math.random()*(this.maxStartRotation-this.minStartRotation)+this.minStartRotation+this.rotation,f.x=Math.random()*this.spawnCircle.radius,f.y=0,b.rotatePoint(360*Math.random(),f),f.x+=this.spawnCircle.x,f.y+=this.spawnCircle.y,0!==this.rotation&&b.rotatePoint(this.rotation,f),a.position.x=c+f.x,a.position.y=d+f.y},e._spawnBurst=function(a,b,c,d){a.rotation=0===this.particleSpacing?360*Math.random():this.angleStart+this.particleSpacing*d+this.rotation,a.position.x=b,a.position.y=c},e.cleanup=function(){for(var a=this._activeParticles.length-1;a>=0;--a)this.recycle(this._activeParticles[a])},e.destroy=function(){this.cleanup();for(var a=this._pool.length-1;a>=0;--a)this._pool[a].destroy();this._pool=null,this._activeParticles=null,this.parent=null,this.particleImages=null,this.spawnPos=null,this.ownerPos=null,this.startColor=null,this.endColor=null,this.customEase=null},a.Emitter=d}(cloudkid); \ No newline at end of file +!function(){"use strict";window.cloudkid=window.cloudkid||{};var a={},b=a.DEG_TO_RADS=Math.PI/180;a.rotatePoint=function(a,c){if(a){a*=b;var d=Math.sin(a),e=Math.cos(a),f=c.x*e-c.y*d,g=c.x*d+c.y*e;c.x=f,c.y=g}},a.combineRGBComponents=function(a,b,c){return a<<16|b<<8|c},a.normalize=function(b){var c=1/a.length(b);b.x*=c,b.y*=c},a.scaleBy=function(a,b){a.x*=b,a.y*=b},a.length=function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},a.hexToRGB=function(a,b){b?b.length=0:b=[],"#"==a.charAt(0)?a=a.substr(1):0===a.indexOf("0x")&&(a=a.substr(2));var c;return 8==a.length&&(c=a.substr(0,2),a=a.substr(2)),b.push(parseInt(a.substr(0,2),16)),b.push(parseInt(a.substr(2,2),16)),b.push(parseInt(a.substr(4,2),16)),c&&b.push(parseInt(c,16)),b},a.generateEase=function(a){var b=a.length,c=1/b,d=function(d){var e,f,g=b*d|0;return e=(d-g*c)*b,f=a[g]||a[b-1],f.s+e*(2*(1-e)*(f.cp-f.s)+e*(f.e-f.s))};return d},a.getBlendMode=function(a){if(!a)return PIXI.blendModes.NORMAL;for(a=a.toUpperCase();a.indexOf(" ")>=0;)a=a.replace(" ","_");return PIXI.blendModes[a]||PIXI.blendModes.NORMAL},cloudkid.ParticleUtils=a,Array.prototype.shuffle||Object.defineProperty(Array.prototype,"shuffle",{enumerable:!1,writable:!1,value:function(){for(var a,b,c=this.length;c;a=Math.floor(Math.random()*c),b=this[--c],this[c]=this[a],this[a]=b);return this}}),Array.prototype.random||Object.defineProperty(Array.prototype,"random",{enumerable:!1,writable:!1,value:function(){return this[Math.floor(Math.random()*this.length)]}})}(),function(a){"use strict";var b=a.ParticleUtils,c=function(a){var b=a.particleImages[0]instanceof PIXI.Texture?[a.particleImages[0]]:a.particleImages[0];PIXI.MovieClip.call(this,b),this.emitter=a,this.anchor.x=this.anchor.y=.5,this.velocity=new PIXI.Point,this.maxLife=0,this.age=0,this.ease=null,this.extraData=null,this.startAlpha=0,this.endAlpha=0,this.startSpeed=0,this.endSpeed=0,this.acceleration=null,this.startScale=0,this.endScale=0,this.startColor=null,this._sR=0,this._sG=0,this._sB=0,this.endColor=null,this._eR=0,this._eG=0,this._eB=0,this._doAlpha=!1,this._doScale=!1,this._doSpeed=!1,this._doColor=!1,this._oneOverLife=0},d=c.prototype=Object.create(PIXI.MovieClip.prototype);d.init=function(){this.age=0,this.velocity.x=this.startSpeed,this.velocity.y=0,b.rotatePoint(this.rotation,this.velocity),this.rotation*=b.DEG_TO_RADS,this.rotationSpeed*=b.DEG_TO_RADS,this.alpha=this.startAlpha,this.scale.x=this.scale.y=this.startScale,this.startColor&&(this._sR=this.startColor[0],this._sG=this.startColor[1],this._sB=this.startColor[2],this.endColor&&(this._eR=this.endColor[0],this._eG=this.endColor[1],this._eB=this.endColor[2])),this._doAlpha=this.startAlpha!=this.endAlpha,this._doSpeed=this.startSpeed!=this.endSpeed,this._doScale=this.startScale!=this.endScale,this._doColor=!!this.endColor,this._oneOverLife=1/this.maxLife,this.tint=b.combineRGBComponents(this._sR,this._sG,this._sB)},d.applyArt=function(a){this.setTexture(a)},d.update=function(a){if(this.age+=a,this.age>=this.maxLife)return void this.kill();var c=this.age*this._oneOverLife;if(this.ease&&(c=4==this.ease.length?this.ease(c,0,1,1):this.ease(c)),this._doAlpha&&(this.alpha=(this.endAlpha-this.startAlpha)*c+this.startAlpha),this._doScale){var d=(this.endScale-this.startScale)*c+this.startScale;this.scale.x=this.scale.y=d}if(this._doSpeed||0!==this.startSpeed||this.acceleration){if(this._doSpeed){var e=(this.endSpeed-this.startSpeed)*c+this.startSpeed;b.normalize(this.velocity),b.scaleBy(this.velocity,e)}else this.acceleration&&(this.velocity.x+=this.acceleration.x*a,this.velocity.y+=this.acceleration.y*a);this.position.x+=this.velocity.x*a,this.position.y+=this.velocity.y*a}if(this._doColor){var f=(this._eR-this._sR)*c+this._sR,g=(this._eG-this._sG)*c+this._sG,h=(this._eB-this._sB)*c+this._sB;this.tint=b.combineRGBComponents(f,g,h)}0!==this.rotationSpeed?this.rotation+=this.rotationSpeed*a:this.acceleration&&(this.rotation=Math.atan2(this.velocity.y,this.velocity.x))},d.kill=function(){this.emitter.recycle(this)},d.destroy=function(){this.emitter=null,this.velocity=null,this.startColor=this.endColor=null,this.ease=null},a.Particle=c}(cloudkid),function(a){"use strict";var b=(a.ParticleUtils,a.Particle),c=function(a){b.call(this,a),this._helperTextures=[]},d=b.prototype,e=c.prototype=Object.create(d);e.init=function(){if(d.init.call(this),this.extraData){this.fps=this.extraData.fps?this.extraData.fps:60;var a=this.extraData.animationSpeed||1;"matchLife"==a?(this.loop=!1,this.animationSpeed=this.hasOwnProperty("_duration")?this._duration/this.maxLife:this.textures.length/this.maxLife/60):(this.loop=!0,this.animationSpeed=a)}else this.loop=!0,this.animationSpeed=1;this.play()},e.applyArt=function(a){Array.isArray(a)?this.textures=a:(this._helperTextures[0]=a,this.textures=this._helperTextures),this.gotoAndStop(0)},e.update=function(a){d.update.call(this,a),this.age0?c.maxParticles:1e3,this.addAtBack=!!c.addAtBack,this.rotation=0,this.ownerPos=new PIXI.Point,this.spawnPos=new PIXI.Point(c.pos.x,c.pos.y),this._prevEmitterPos=this.spawnPos.clone(),this._prevPosIsValid=!1,this._spawnTimer=0,this.emit=!0}},e.recycle=function(a){var b=this._activeParticles.indexOf(a);b=0;--b)this._activeParticles[b].update(a);var c,d;this._prevPosIsValid&&(c=this._prevEmitterPos.x,d=this._prevEmitterPos.y);var e=this.ownerPos.x+this.spawnPos.x,f=this.ownerPos.y+this.spawnPos.y;if(this.emit)for(this._spawnTimer-=a;this._spawnTimer<=0;){if(this._emitterLife>0&&(this._emitterLife-=this.frequency,this._emitterLife<=0)){this._spawnTimer=0,this._emitterLife=0,this.emit=!1;break}if(this._activeParticles.length>=this.maxParticles)this._spawnTimer+=this.frequency;else{var g;if(g=this.minLifetime==this.maxLifetime?this.minLifetime:Math.random()*(this.maxLifetime-this.minLifetime)+this.minLifetime,-this._spawnTimerb;++b){var l=this._pool.length?this._pool.pop():new this.particleConstructor(this);if(l.applyArt(this.particleImages.length>1?this.particleImages.random():this.particleImages[0]),l.startAlpha=this.startAlpha,l.endAlpha=this.endAlpha,l.startSpeed=this.startSpeed,l.endSpeed=this.endSpeed,l.acceleration=this.acceleration,1!=this.minimumScaleMultiplier){var m=Math.random()*(1-this.minimumScaleMultiplier)+this.minimumScaleMultiplier;l.startScale=this.startScale*m,l.endScale=this.endScale*m}else l.startScale=this.startScale,l.endScale=this.endScale;l.startColor=this.startColor,l.endColor=this.endColor,l.rotationSpeed=this.minRotationSpeed==this.maxRotationSpeed?this.minRotationSpeed:Math.random()*(this.maxRotationSpeed-this.minRotationSpeed)+this.minRotationSpeed,l.maxLife=g,l.blendMode=this.particleBlendMode,l.ease=this.customEase,l.extraData=this.extraData,this._spawnFunc(l,h,i,b),l.init(),l.update(-this._spawnTimer),this.addAtBack?this.parent.addChildAt(l,0):this.parent.addChild(l),this._activeParticles.push(l)}}this._spawnTimer+=this.frequency}}this._posChanged&&(this._prevEmitterPos.x=e,this._prevEmitterPos.y=f,this._prevPosIsValid=!0,this._posChanged=!1)},e._spawnPoint=function(a,b,c){a.rotation=this.minStartRotation==this.maxStartRotation?this.minStartRotation+this.rotation:Math.random()*(this.maxStartRotation-this.minStartRotation)+this.minStartRotation+this.rotation,a.position.x=b,a.position.y=c},e._spawnRect=function(a,c,d){a.rotation=this.minStartRotation==this.maxStartRotation?this.minStartRotation+this.rotation:Math.random()*(this.maxStartRotation-this.minStartRotation)+this.minStartRotation+this.rotation,f.x=Math.random()*this.spawnRect.width+this.spawnRect.x,f.y=Math.random()*this.spawnRect.height+this.spawnRect.y,0!==this.rotation&&b.rotatePoint(this.rotation,f),a.position.x=c+f.x,a.position.y=d+f.y},e._spawnCircle=function(a,c,d){a.rotation=this.minStartRotation==this.maxStartRotation?this.minStartRotation+this.rotation:Math.random()*(this.maxStartRotation-this.minStartRotation)+this.minStartRotation+this.rotation,f.x=Math.random()*this.spawnCircle.radius,f.y=0,b.rotatePoint(360*Math.random(),f),f.x+=this.spawnCircle.x,f.y+=this.spawnCircle.y,0!==this.rotation&&b.rotatePoint(this.rotation,f),a.position.x=c+f.x,a.position.y=d+f.y},e._spawnBurst=function(a,b,c,d){a.rotation=0===this.particleSpacing?360*Math.random():this.angleStart+this.particleSpacing*d+this.rotation,a.position.x=b,a.position.y=c},e.cleanup=function(){for(var a=this._activeParticles.length-1;a>=0;--a)this.recycle(this._activeParticles[a])},e.destroy=function(){this.cleanup();for(var a=this._pool.length-1;a>=0;--a)this._pool[a].destroy();this._pool=null,this._activeParticles=null,this.parent=null,this.particleImages=null,this.spawnPos=null,this.ownerPos=null,this.startColor=null,this.endColor=null,this.customEase=null},a.Emitter=d}(cloudkid); \ No newline at end of file diff --git a/src/AnimatedParticle.js b/src/AnimatedParticle.js new file mode 100644 index 00000000..3eaf5e15 --- /dev/null +++ b/src/AnimatedParticle.js @@ -0,0 +1,141 @@ +/** +* @module cloudkid +*/ +(function(cloudkid, undefined) { + + "use strict"; + + var ParticleUtils = cloudkid.ParticleUtils, + Particle = cloudkid.Particle; + + /** + * An individual particle image with an animation. You shouldn't have to deal with these. + * @class AnimatedParticle + * @constructor + * @param {Emitter} emitter The emitter that controls this AnimatedParticle. + */ + var AnimatedParticle = function(emitter) + { + Particle.call(this, emitter); + + /** + * Array used to avoid damaging previous texture arrays + * when applyArt() passes a texture instead of an array. + * @property {Array} _helperTextures + * @private + */ + this._helperTextures = []; + }; + + // Reference to the super class + var s = Particle.prototype; + // Reference to the prototype + var p = AnimatedParticle.prototype = Object.create(s); + + /** + * Initializes the particle for use, based on the properties that have to + * have been set already on the particle. + * @method init + */ + p.init = function() + { + s.init.call(this); + + //set the standard PIXI animationSpeed + if(this.extraData) + { + //fps will work differently for CloudKid's fork of PIXI than + //standard PIXI, where it will just be a variable + if(this.extraData.fps) + { + this.fps = this.extraData.fps; + } + else + { + this.fps = 60; + } + var animationSpeed = this.extraData.animationSpeed || 1; + if(animationSpeed == "matchLife") + { + this.loop = false; + //animation should end when the particle does + if(this.hasOwnProperty("_duration")) + { + //CloudKid's fork of PIXI redoes how MovieClips animate, + //with duration and elapsed time + this.animationSpeed = this._duration / this.maxLife; + } + else + { + //standard PIXI - assume game tick rate of 60 fps + this.animationSpeed = this.textures.length / this.maxLife / 60; + } + } + else + { + this.loop = true; + this.animationSpeed = animationSpeed; + } + } + else + { + this.loop = true; + this.animationSpeed = 1; + } + this.play();//start playing + }; + + /** + * Sets the textures for the particle. + * @method applyArt + * @param {Array} art An array of PIXI.Texture objects for this animated particle. + */ + p.applyArt = function(art) + { + if(Array.isArray(art)) + this.textures = art; + else + { + this._helperTextures[0] = art; + this.textures = this._helperTextures; + } + this.gotoAndStop(0); + }; + + /** + * Updates the particle. + * @method update + * @param {Number} delta Time elapsed since the previous frame, in __seconds__. + */ + p.update = function(delta) + { + s.update.call(this, delta); + if(this.age < this.maxLife) + { + //only animate the particle if it is still alive + if(this._duration) + { + //work with CloudKid's fork + this.updateAnim(delta); + } + else + { + //standard PIXI - movieclip will advance automatically - this means + //that the movieclip will animate even if the emitter (and the particles) + //are paused + } + } + }; + + /** + * Destroys the particle, removing references and preventing future use. + * @method destroy + */ + p.destroy = function() + { + s.destroy.call(this); + }; + + cloudkid.AnimatedParticle = AnimatedParticle; + +}(cloudkid)); \ No newline at end of file diff --git a/src/Emitter.js b/src/Emitter.js index 09177f69..14496d46 100644 --- a/src/Emitter.js +++ b/src/Emitter.js @@ -348,7 +348,7 @@ particleImages; //particles from different base textures will be slower in WebGL than if they //were from one spritesheet - if(this.particleImages.length > 1) + if(DEBUG && this.particleImages.length > 1) { for(var i = this.particleImages.length - 1; i > 0; --i) { @@ -438,6 +438,8 @@ config.ease : ParticleUtils.generateEase(config.ease); } + else + this.customEase = null; this.extraData = config.extraData || null; ////////////////////////// // Emitter Properties //