1 /**
  2  * Hilo
  3  * Copyright 2015 alibaba.com
  4  * Licensed under the MIT License
  5  */
  6 
  7 /**
  8  * <iframe src='../../../examples/ParticleSystem.html?noHeader' width = '550' height = '400' scrolling='no'></iframe>
  9  * <br/>
 10  * @class 粒子系统
 11  * @augments Container
 12  * @module hilo/game/ParticleSystem
 13  * @requires hilo/core/Hilo
 14  * @requires hilo/core/Class
 15  * @requires hilo/view/View
 16  * @requires hilo/view/Container
 17  * @requires hilo/view/Bitmap
 18  * @requires hilo/view/Drawable
 19  * @requires hilo/util/util
 20  * @property {Number} [emitTime=0.2] 发射间隔
 21  * @property {Number} [emitTimeVar=0] 发射间隔变化量
 22  * @property {Number} [emitNum=10] 每次发射数量
 23  * @property {Number} [emitNumVar=0] 每次发射数量变化量
 24  * @property {Number} [emitterX=0] 发射器位置x
 25  * @property {Number} [emitterY=0] 发射器位置y
 26  * @property {Number} [totalTime=Infinity] 总时间
 27  * @property {Number} [gx=0] 重力加速度x
 28  * @property {Number} [gy=0] 重力加速度y
 29  * @param {Object} properties 创建对象的属性参数。可包含此类所有可写属性。
 30  * @param {Object} properties.particle 粒子属性配置
 31  * @param {Number} [properties.particle.x=0] x位置
 32  * @param {Number} [properties.particle.y=0] y位置
 33  * @param {Number} [properties.particle.vx=0] x速度
 34  * @param {Number} [properties.particle.vy=0] y速度
 35  * @param {Number} [properties.particle.ax=0] x加速度
 36  * @param {Number} [properties.particle.ay=0] y加速度
 37  * @param {Number} [properties.particle.life=1] 粒子存活时间,单位s
 38  * @param {Number} [properties.particle.alpha=1] 透明度
 39  * @param {Number} [properties.particle.alphaV=0] 透明度变化
 40  * @param {Number} [properties.particle.scale=1] 缩放
 41  * @param {Number} [properties.particle.scaleV=0] 缩放变化速度
 42 */
 43 var ParticleSystem = (function(){
 44     //粒子属性
 45     var props = ['x', 'y', 'vx', 'vy', 'ax', 'ay', 'rotation', 'rotationV', 'scale', 'scaleV', 'alpha', 'alphaV', 'life'];
 46     var PROPS = [];
 47     for(var i = 0, l = props.length;i < l;i ++){
 48         var p = props[i];
 49         PROPS.push(p);
 50         PROPS.push(p + "Var");
 51     }
 52 
 53     //粒子默认值
 54     var PROPS_DEFAULT = {
 55         x: 0,
 56         y: 0,
 57         vx: 0,
 58         vy: 0,
 59         ax: 0,
 60         ay: 0,
 61         scale:1,
 62         scaleV:0,
 63         alpha:1,
 64         alphaV:0,
 65         rotation: 0,
 66         rotationV: 0,
 67         life: 1
 68     };
 69 
 70     var diedParticles = [];
 71 
 72     var ParticleSystem = Class.create(/** @lends ParticleSystem.prototype */{
 73         Extends:Container,
 74         constructor:function(properties){
 75             this.id = this.id || properties.id || Hilo.getUid("ParticleSystem");
 76 
 77             this.emitterX = 0;
 78             this.emitterY = 0;
 79 
 80             this.gx = 0;
 81             this.gy = 0;
 82             this.totalTime = Infinity;
 83 
 84             this.emitNum = 10;
 85             this.emitNumVar = 0;
 86 
 87             this.emitTime = .2;
 88             this.emitTimeVar = 0;
 89 
 90             this.particle = {};
 91 
 92             ParticleSystem.superclass.constructor.call(this, properties);
 93 
 94             this.reset(properties);
 95         },
 96         Statics:{
 97             PROPS:PROPS,
 98             PROPS_DEFAULT:PROPS_DEFAULT,
 99             diedParticles:diedParticles
100         },
101         /**
102          * 重置属性
103          * @param {Object} cfg
104         */
105         reset: function(cfg) {
106             util.copy(this, cfg);
107             this.particle.system = this;
108             if(this.totalTime <= 0){
109                 this.totalTime = Infinity;
110             }
111         },
112         /**
113          * 更新
114          * @param {Number} dt 间隔时间 单位ms
115         */
116         onUpdate: function(dt) {
117             dt *= .001;
118             if (this._isRun) {
119                 this._totalRunTime += dt;
120                 this._currentRunTime += dt;
121                 if (this._currentRunTime >= this._emitTime) {
122                     this._currentRunTime = 0;
123                     this._emitTime = getRandomValue(this.emitTime, this.emitTimeVar);
124                     this._emit();
125                 }
126 
127                 if (this._totalRunTime >= this.totalTime) {
128                     this.stop();
129                 }
130             }
131         },
132         /**
133          * 发射粒子
134         */
135         _emit: function() {
136             var num = getRandomValue(this.emitNum, this.emitNumVar)>>0;
137             for (var i = 0; i < num; i++) {
138                 this.addChild(Particle.create(this.particle));
139             }
140         },
141         /**
142          * 开始发射粒子
143         */
144         start: function() {
145             this.stop(true);
146             this._currentRunTime = 0;
147             this._totalRunTime = 0;
148             this._isRun = true;
149             this._emitTime = getRandomValue(this.emitTime, this.emitTimeVar);
150         },
151         /**
152          * 停止发射粒子
153          * @param {Boolean} clear 是否清除所有粒子
154         */
155         stop: function(clear) {
156             this._isRun = false;
157             if (clear) {
158                 for (var i = this.children.length - 1; i >= 0; i--) {
159                     this.children[i].destroy();
160                 }
161             }
162         }
163     });
164 
165     /**
166      * @class 粒子
167      * @inner
168      * @param {Number} vx x速度
169      * @param {Number} vy y速度
170      * @param {Number} ax x加速度
171      * @param {Number} ay y加速度
172      * @param {Number} scaleV 缩放变化速度
173      * @param {Number} alphaV 透明度变换速度
174      * @param {Number} rotationV 旋转速度
175      * @param {Number} life 存活时间
176     */
177     var Particle = Class.create({
178         Extends:View,
179         constructor:function(properties){
180             this.id = this.id || properties.id || Hilo.getUid("Particle");
181             Particle.superclass.constructor.call(this, properties);
182             this.init(properties);
183         },
184         /**
185          * 更新粒子。
186         */
187         onUpdate: function(dt) {
188             dt *= .001;
189             if(this._died){
190                 return false;
191             }
192             var ax = this.ax + this.system.gx;
193             var ay = this.ay + this.system.gy;
194 
195             this.vx += ax * dt;
196             this.vy += ay * dt;
197             this.x += this.vx * dt;
198             this.y += this.vy * dt;
199 
200             this.rotation += this.rotationV;
201 
202             if (this._time > .1) {
203                 this.alpha += this.alphaV;
204             }
205 
206             this.scale += this.scaleV;
207             this.scaleX = this.scaleY = this.scale;
208 
209             this._time += dt;
210             if (this._time >= this.life || this.alpha <= 0) {
211                 this.destroy();
212                 return false;
213             }
214         },
215         /**
216          * 设置粒子图像。
217         */
218         setImage: function(img, frame) {
219             this.drawable = this.drawable||new Drawable();
220             frame = frame || [0, 0, img.width, img.height];
221 
222             this.width = frame[2];
223             this.height = frame[3];
224             this.drawable.rect = frame;
225             this.drawable.image = img;
226         },
227         /**
228          * 销毁粒子
229         */
230         destroy: function() {
231             this._died = true;
232             this.alpha = 0;
233             this.removeFromParent();
234             diedParticles.push(this);
235         },
236         /**
237          * 初始化粒子。
238         */
239         init: function(cfg) {
240             this.system = cfg.system;
241             this._died = false;
242             this._time = 0;
243             this.alpha = 1;
244             for (var i = 0, l = PROPS.length; i < l; i++) {
245                 var p = PROPS[i];
246                 var v = cfg[p] === undefined ? PROPS_DEFAULT[p] : cfg[p];
247                 this[p] = getRandomValue(v, cfg[p + 'Var']);
248             }
249 
250             this.x += this.system.emitterX;
251             this.y += this.system.emitterY;
252 
253             if (cfg.image) {
254                 var frame = cfg.frame;
255                 if(frame && frame[0].length){
256                     frame = frame[(Math.random() * frame.length) >> 0];
257                 }
258                 this.setImage(cfg.image, frame);
259                 if(cfg.pivotX !== undefined){
260                     this.pivotX = cfg.pivotX * frame[2];
261                 }
262                 if(cfg.pivotY !== undefined){
263                     this.pivotY = cfg.pivotY * frame[3];
264                 }
265             }
266         },
267         Statics:{
268             /**
269              * 生成粒子。
270              * @param {Object} cfg 粒子参数。
271             */
272             create:function(cfg) {
273                 if (diedParticles.length > 0) {
274                     var particle = diedParticles.pop();
275                     particle.init(cfg);
276                     return particle;
277                 } else {
278                     return new Particle(cfg);
279                 }
280             }
281         }
282 
283     });
284 
285     /**
286      * Get the random value.
287      * @private
288      * @param  {Number} value     The value.
289      * @param  {Number} variances The variances.
290      * @return {Number}
291      */
292     function getRandomValue(value, variances){
293         return variances ? value + (Math.random() - .5) * 2 * variances : value;
294     }
295 
296     return ParticleSystem;
297 })();