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