Card/CardEmitter/CardEmitter.js

/**
* Эмиттер карт.
* @class
* @extends {Phaser.Particles.Arcade.Emitter}
*/
var CardEmitter = function(){

	/**
	* Время пропадания партиклей эмиттера, когда он остановлен.
	* @type {Number}
	*/
	this.fadeTime = 500;

	/**
	* Смещение партиклей в сторону
	* @type {Number}
	*/
	this.sway = 0;

	/**
	* Интервал спавна партиклей.
	* @type {Number}
	*/
	this.interval = 0;

	/**
	* Интервал до того, как к нему были применены ограничения.
	* @type {Number}
	*/
	this._preferedInterval = 0;

	/**
	* Скорость игры при последнем запуске эмиттера.
	* @type {number}
	*/
	this._cachedGameSpeed = game.speed;

	/**
	* Максимальное количество частиц эмиттера.
	* Для применения максимума используется {@link CardEmitter#makeMaxParticles}
	* @type {Number}
	*/
	this.maxParticles = 50;

	Phaser.Particles.Arcade.Emitter.call(this, game, game.world.centerX, -skinManager.skin.height, this.maxParticles);
	this.name = 'cardEmitter';

	this.makeMaxParicles();
};

extend(CardEmitter, Phaser.Particles.Arcade.Emitter);

/**
* Приводит кол-во партиклей эмиттера к заданному значению, по необходимости удаляя и добавляя партикли.
* Перезапускает эмиттер.
* @param {number} [max=CardEmitter#maxParticles] во партиклей.
*/
CardEmitter.prototype.makeMaxParicles = function(max){
	if(typeof max != 'number' || isNaN(max)){
		max = this.maxParticles;
	}
	else{
		this.maxParticles = max;
	}

	var	current = this.children.length;
	if(max < current){
		if(this.on){
			setTimeout(this.removeBetween.bind(this, max, current - 1), this.fadeTime/game.speed);
		}
		else{
			this.removeBetween(max, current - 1);
		}
	}
	else if(max > current){
		var frames = [];
		for(var i = skinManager.skin.firstValueFrame; i < skinManager.skin.firstValueFrame + 52; i++){
			frames.push(i);
		}
		this.makeParticles(skinManager.skin.sheetName, frames, max - current);
	}

	this.restart();
};

CardEmitter.prototype._start = Phaser.Particles.Arcade.Emitter.prototype.start;

/**
* Запускает эмиттер карт. Предварительно останавливает эмиттер, если он уже запущен.  
* Не указанные параметры остаются с предыдущего запуска.
* @param {number}           [minSpeed] минимальная вертикальная скорость партиклей
* @param {number}           [maxSpeed] максимальная вертикальная скорость партиклей
* @param {number}           [sway]     максимальная скорость по горизонтали
* @param {(number|boolean)} [interval] Интервал между спавном партиклей.
*                                      `false` рассчитывает интервал на основе времени жизни и максимального кол-ва партиклей.
* @param {number}           [rotation] максимальная скорость поворота партиклей
* @param {number}           [gravity]  вертикальное ускорение партиклей
*/
CardEmitter.prototype.start = function(minSpeed, maxSpeed, sway, interval, rotation, gravity){

	this.stop();

	if(minSpeed === undefined){
		minSpeed = this.minParticleSpeed.y/this._cachedGameSpeed;
	}
	if(maxSpeed === undefined){
		maxSpeed = this.maxParticleSpeed.y/this._cachedGameSpeed;
	}
	if(sway === undefined){
		sway = this.sway;
	}
	else{
		this.sway = sway;
	}
	if(rotation === undefined){
		rotation = this.maxRotation;
	}
	if(gravity !== undefined){
		this.gravity = gravity * game.speed;
	}
	else{
		this.gravity = this.gravity / this._cachedGameSpeed * game.speed;
	}
	if(interval === undefined){
		interval = this._preferedInterval;
	}
	else{
		this._preferedInterval = interval;
	}

	this.minParticleSpeed = {x: -sway * game.speed, y: minSpeed * game.speed};
	this.maxParticleSpeed = {x: sway * game.speed, y: maxSpeed * game.speed};

	this.minRotation = -rotation;
	this.maxRotation = rotation;

	this.minParticleScale = this.maxParticleScale = skinManager.skin.scale;

	this.x = game.world.centerX;
	this.width = game.screenWidth;

	// Вычисляем длину жизни частиц из уравнения движения 
	// x(t) = x0 + vt + (at^2)/2
	// a/2 * t^2 + vt + x0 = 0
	function solveQuadEq(a, b, c) {
		return Math.abs((-1 * b + Math.sqrt(Math.pow(b, 2) - (4 * a * c))) / (2 * a));
	}	
	var lifespan = solveQuadEq(this.gravity / 2, minSpeed * game.speed, - (game.screenHeight + skinManager.skin.height * 2)) * 1000;

	// Ограничиваем минимальный интервал
	var minInterval = lifespan/this.maxParticles;
	if(interval === false || interval < minInterval){
		interval = minInterval;
	}
	this.interval = interval;
	this._cachedGameSpeed = game.speed;	
	this._start(false, lifespan, interval, undefined, undefined);
};

/**
* Останавливает эмиттер карт.
*/
CardEmitter.prototype.stop = function(){
	if(!this.on){
		return;
	}
	this.on = false;
	this.forEachAlive(function(p){
		if(p.visible){
			var tween = game.add.tween(p);
			tween.to({alpha: 0}, this.fadeTime/game.speed);
			tween.start();
			tween.onComplete.addOnce(function(){
				this.visible = false;
			}, p);
		}
	}, this);
};

/**
* Перезапускает эмиттер карт с текущими настройками если он запущен.
* @param  {boolean} [noFadeOut] отключает фейд существующих партиклей
*/
CardEmitter.prototype.restart = function(noFadeOut){
	if(this.on){
		if(noFadeOut){
			this.on = false;
		}
		this.start();
	}
};

/**
* Применяет скин к эмиттеру.
*/
CardEmitter.prototype.applySkin = function(){
	if(this.on){
		this.restart();
		setTimeout(this._applySkinToEmitter.bind(this), this.fadeTime/game.speed);
	}
	else{
		this._applySkinToEmitter(this);
	}
};

/** 
* Применяет скин к эмиттеру карт.
*/
CardEmitter.prototype._applySkinToEmitter = function(){
	this.forEach(function(p){
		p.loadTexture(skinManager.skin.sheetName);
	}, this);
};