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