phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
920 lines (812 loc) • 29.1 kB
JavaScript
/**
* @author Richard Davey <rich@phaser.io>
* @copyright 2013-2025 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var Between = require('../../math/Between');
var Clamp = require('../../math/Clamp');
var Class = require('../../utils/Class');
var FloatBetween = require('../../math/FloatBetween');
var GetEaseFunction = require('../../tweens/builders/GetEaseFunction');
var GetFastValue = require('../../utils/object/GetFastValue');
var GetInterpolationFunction = require('../../tweens/builders/GetInterpolationFunction');
var SnapTo = require('../../math/snap/SnapTo');
var Wrap = require('../../math/Wrap');
/**
* @classdesc
* This class is responsible for taking control over a single property
* in the Particle class and managing its emission and updating functions.
*
* Particles properties such as `x`, `y`, `scaleX`, `lifespan` and others all use
* EmitterOp instances to manage them, as they can be given in a variety of
* formats: from simple values, to functions, to dynamic callbacks.
*
* See the `ParticleEmitter` class for more details on emitter op configuration.
*
* @class EmitterOp
* @memberof Phaser.GameObjects.Particles
* @constructor
* @since 3.0.0
*
* @param {string} key - The name of the property.
* @param {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType|Phaser.Types.GameObjects.Particles.EmitterOpOnUpdateType} defaultValue - The default value of the property.
* @param {boolean} [emitOnly=false] - Whether the property can only be modified when a Particle is emitted.
*/
var EmitterOp = new Class({
initialize:
function EmitterOp (key, defaultValue, emitOnly)
{
if (emitOnly === undefined) { emitOnly = false; }
/**
* The name of this property.
*
* @name Phaser.GameObjects.Particles.EmitterOp#propertyKey
* @type {string}
* @since 3.0.0
*/
this.propertyKey = key;
/**
* The current value of this property.
*
* This can be a simple value, an array, a function or an onEmit
* configuration object.
*
* @name Phaser.GameObjects.Particles.EmitterOp#propertyValue
* @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType|Phaser.Types.GameObjects.Particles.EmitterOpOnUpdateType}
* @since 3.0.0
*/
this.propertyValue = defaultValue;
/**
* The default value of this property.
*
* This can be a simple value, an array, a function or an onEmit
* configuration object.
*
* @name Phaser.GameObjects.Particles.EmitterOp#defaultValue
* @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType|Phaser.Types.GameObjects.Particles.EmitterOpOnUpdateType}
* @since 3.0.0
*/
this.defaultValue = defaultValue;
/**
* The number of steps for stepped easing between {@link Phaser.GameObjects.Particles.EmitterOp#start} and
* {@link Phaser.GameObjects.Particles.EmitterOp#end} values, per emit.
*
* @name Phaser.GameObjects.Particles.EmitterOp#steps
* @type {number}
* @default 0
* @since 3.0.0
*/
this.steps = 0;
/**
* The step counter for stepped easing, per emit.
*
* @name Phaser.GameObjects.Particles.EmitterOp#counter
* @type {number}
* @default 0
* @since 3.0.0
*/
this.counter = 0;
/**
* When the step counter reaches it's maximum, should it then
* yoyo back to the start again, or flip over to it?
*
* @name Phaser.GameObjects.Particles.EmitterOp#yoyo
* @type {boolean}
* @default false
* @since 3.60.0
*/
this.yoyo = false;
/**
* The counter direction. 0 for up and 1 for down.
*
* @name Phaser.GameObjects.Particles.EmitterOp#direction
* @type {number}
* @default 0
* @since 3.60.0
*/
this.direction = 0;
/**
* The start value for this property to ease between.
*
* If an interpolation this holds a reference to the number data array.
*
* @name Phaser.GameObjects.Particles.EmitterOp#start
* @type {number|number[]}
* @default 0
* @since 3.0.0
*/
this.start = 0;
/**
* The most recently calculated value. Updated every time an
* emission or update method is called. Treat as read-only.
*
* @name Phaser.GameObjects.Particles.EmitterOp#current
* @type {number}
* @since 3.60.0
*/
this.current = 0;
/**
* The end value for this property to ease between.
*
* @name Phaser.GameObjects.Particles.EmitterOp#end
* @type {number}
* @default 0
* @since 3.0.0
*/
this.end = 0;
/**
* The easing function to use for updating this property, if any.
*
* @name Phaser.GameObjects.Particles.EmitterOp#ease
* @type {?function}
* @since 3.0.0
*/
this.ease = null;
/**
* The interpolation function to use for updating this property, if any.
*
* @name Phaser.GameObjects.Particles.EmitterOp#interpolation
* @type {?function}
* @since 3.60.0
*/
this.interpolation = null;
/**
* Whether this property can only be modified when a Particle is emitted.
*
* Set to `true` to allow only {@link Phaser.GameObjects.Particles.EmitterOp#onEmit} callbacks to be set and
* affect this property.
*
* Set to `false` to allow both {@link Phaser.GameObjects.Particles.EmitterOp#onEmit} and
* {@link Phaser.GameObjects.Particles.EmitterOp#onUpdate} callbacks to be set and affect this property.
*
* @name Phaser.GameObjects.Particles.EmitterOp#emitOnly
* @type {boolean}
* @since 3.0.0
*/
this.emitOnly = emitOnly;
/**
* The callback to run for Particles when they are emitted from the Particle Emitter.
*
* @name Phaser.GameObjects.Particles.EmitterOp#onEmit
* @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitCallback}
* @since 3.0.0
*/
this.onEmit = this.defaultEmit;
/**
* The callback to run for Particles when they are updated.
*
* @name Phaser.GameObjects.Particles.EmitterOp#onUpdate
* @type {Phaser.Types.GameObjects.Particles.EmitterOpOnUpdateCallback}
* @since 3.0.0
*/
this.onUpdate = this.defaultUpdate;
/**
* Set to `false` to disable this EmitterOp.
*
* @name Phaser.GameObjects.Particles.EmitterOp#active
* @type {boolean}
* @since 3.60.0
*/
this.active = true;
/**
* The onEmit method type of this EmitterOp.
*
* Set as part of `setMethod` and cached here to avoid
* re-setting when only the value changes.
*
* @name Phaser.GameObjects.Particles.EmitterOp#method
* @type {number}
* @since 3.60.0
*/
this.method = 0;
/**
* The callback to run for Particles when they are emitted from the Particle Emitter.
* This is set during `setMethods` and used by `proxyEmit`.
*
* @name Phaser.GameObjects.Particles.EmitterOp#_onEmit
* @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitCallback}
* @private
* @since 3.60.0
*/
this._onEmit;
/**
* The callback to run for Particles when they are updated.
* This is set during `setMethods` and used by `proxyUpdate`.
*
* @name Phaser.GameObjects.Particles.EmitterOp#_onUpdate
* @type {Phaser.Types.GameObjects.Particles.EmitterOpOnUpdateCallback}
* @private
* @since 3.60.0
*/
this._onUpdate;
},
/**
* Load the property from a Particle Emitter configuration object.
*
* Optionally accepts a new property key to use, replacing the current one.
*
* @method Phaser.GameObjects.Particles.EmitterOp#loadConfig
* @since 3.0.0
*
* @param {Phaser.Types.GameObjects.Particles.ParticleEmitterConfig} [config] - Settings for the Particle Emitter that owns this property.
* @param {string} [newKey] - The new key to use for this property, if any.
*/
loadConfig: function (config, newKey)
{
if (config === undefined)
{
config = {};
}
if (newKey)
{
this.propertyKey = newKey;
}
this.propertyValue = GetFastValue(
config,
this.propertyKey,
this.defaultValue
);
this.method = this.getMethod();
this.setMethods();
if (this.emitOnly)
{
// Reset it back again
this.onUpdate = this.defaultUpdate;
}
},
/**
* Build a JSON representation of this Particle Emitter property.
*
* @method Phaser.GameObjects.Particles.EmitterOp#toJSON
* @since 3.0.0
*
* @return {object} A JSON representation of this Particle Emitter property.
*/
toJSON: function ()
{
return JSON.stringify(this.propertyValue);
},
/**
* Change the current value of the property and update its callback methods.
*
* @method Phaser.GameObjects.Particles.EmitterOp#onChange
* @since 3.0.0
*
* @param {number} value - The new numeric value of this property.
*
* @return {this} This Emitter Op object.
*/
onChange: function (value)
{
var current;
switch (this.method)
{
// Number
// Custom Callback (onEmit only)
// Custom onEmit and/or onUpdate callbacks
case 1:
case 3:
case 8:
current = value;
break;
// Random Array
case 2:
if (this.propertyValue.indexOf(value) >= 0)
{
current = value;
}
break;
// Stepped start/end
case 4:
var step = (this.end - this.start) / this.steps;
current = SnapTo(value, step);
this.counter = current;
break;
// Eased start/end
// min/max (random float or int)
// Random object (random integer)
case 5:
case 6:
case 7:
current = Clamp(value, this.start, this.end);
break;
// Interpolation
case 9:
current = this.start[0];
break;
}
this.current = current;
return this;
},
/**
* Checks the type of `EmitterOp.propertyValue` to determine which
* method is required in order to return values from this op function.
*
* @method Phaser.GameObjects.Particles.EmitterOp#getMethod
* @since 3.60.0
*
* @return {number} A number between 0 and 9 which should be passed to `setMethods`.
*/
getMethod: function ()
{
var value = this.propertyValue;
// `moveToX` and `moveToY` are null by default
if (value === null)
{
return 0;
}
var t = typeof value;
if (t === 'number')
{
// Number
return 1;
}
else if (Array.isArray(value))
{
// Random Array
return 2;
}
else if (t === 'function')
{
// Custom Callback
return 3;
}
else if (t === 'object')
{
if (this.hasBoth(value, 'start', 'end'))
{
if (this.has(value, 'steps'))
{
// Stepped start/end
return 4;
}
else
{
// Eased start/end
return 5;
}
}
else if (this.hasBoth(value, 'min', 'max'))
{
// min/max
return 6;
}
else if (this.has(value, 'random'))
{
// Random object
return 7;
}
else if (this.hasEither(value, 'onEmit', 'onUpdate'))
{
// Custom onEmit onUpdate
return 8;
}
else if (this.hasEither(value, 'values', 'interpolation'))
{
// Interpolation
return 9;
}
}
return 0;
},
/**
* Update the {@link Phaser.GameObjects.Particles.EmitterOp#onEmit} and
* {@link Phaser.GameObjects.Particles.EmitterOp#onUpdate} callbacks based on the method returned
* from `getMethod`. The method is stored in the `EmitterOp.method` property
* and is a number between 0 and 9 inclusively.
*
* @method Phaser.GameObjects.Particles.EmitterOp#setMethods
* @since 3.0.0
*
* @return {this} This Emitter Op object.
*/
setMethods: function ()
{
var value = this.propertyValue;
var current = value;
var onEmit = this.defaultEmit;
var onUpdate = this.defaultUpdate;
switch (this.method)
{
// Number
case 1:
onEmit = this.staticValueEmit;
break;
// Random Array
case 2:
onEmit = this.randomStaticValueEmit;
current = value[0];
break;
// Custom Callback (onEmit only)
case 3:
this._onEmit = value;
onEmit = this.proxyEmit;
current = this.defaultValue;
break;
// Stepped start/end
case 4:
this.start = value.start;
this.end = value.end;
this.steps = value.steps;
this.counter = this.start;
this.yoyo = this.has(value, 'yoyo') ? value.yoyo : false;
this.direction = 0;
onEmit = this.steppedEmit;
current = this.start;
break;
// Eased start/end
case 5:
this.start = value.start;
this.end = value.end;
var easeType = this.has(value, 'ease') ? value.ease : 'Linear';
this.ease = GetEaseFunction(easeType, value.easeParams);
onEmit = (this.has(value, 'random') && value.random) ? this.randomRangedValueEmit : this.easedValueEmit;
onUpdate = this.easeValueUpdate;
current = this.start;
break;
// min/max (random float or int)
case 6:
this.start = value.min;
this.end = value.max;
onEmit = (this.has(value, 'int') && value.int) ? this.randomRangedIntEmit : this.randomRangedValueEmit;
current = this.start;
break;
// Random object (random integer)
case 7:
var rnd = value.random;
if (Array.isArray(rnd))
{
this.start = rnd[0];
this.end = rnd[1];
}
onEmit = this.randomRangedIntEmit;
current = this.start;
break;
// Custom onEmit and/or onUpdate callbacks
case 8:
this._onEmit = (this.has(value, 'onEmit')) ? value.onEmit : this.defaultEmit;
this._onUpdate = (this.has(value, 'onUpdate')) ? value.onUpdate : this.defaultUpdate;
onEmit = this.proxyEmit;
onUpdate = this.proxyUpdate;
current = this.defaultValue;
break;
// Interpolation
case 9:
this.start = value.values;
var easeTypeI = this.has(value, 'ease') ? value.ease : 'Linear';
this.ease = GetEaseFunction(easeTypeI, value.easeParams);
this.interpolation = GetInterpolationFunction(value.interpolation);
onEmit = this.easedValueEmit;
onUpdate = this.easeValueUpdate;
current = this.start[0];
break;
}
this.onEmit = onEmit;
this.onUpdate = onUpdate;
this.current = current;
return this;
},
/**
* Check whether an object has the given property.
*
* @method Phaser.GameObjects.Particles.EmitterOp#has
* @since 3.0.0
*
* @param {object} object - The object to check.
* @param {string} key - The key of the property to look for in the object.
*
* @return {boolean} `true` if the property exists in the object, `false` otherwise.
*/
has: function (object, key)
{
return object.hasOwnProperty(key);
},
/**
* Check whether an object has both of the given properties.
*
* @method Phaser.GameObjects.Particles.EmitterOp#hasBoth
* @since 3.0.0
*
* @param {object} object - The object to check.
* @param {string} key1 - The key of the first property to check the object for.
* @param {string} key2 - The key of the second property to check the object for.
*
* @return {boolean} `true` if both properties exist in the object, `false` otherwise.
*/
hasBoth: function (object, key1, key2)
{
return object.hasOwnProperty(key1) && object.hasOwnProperty(key2);
},
/**
* Check whether an object has at least one of the given properties.
*
* @method Phaser.GameObjects.Particles.EmitterOp#hasEither
* @since 3.0.0
*
* @param {object} object - The object to check.
* @param {string} key1 - The key of the first property to check the object for.
* @param {string} key2 - The key of the second property to check the object for.
*
* @return {boolean} `true` if at least one of the properties exists in the object, `false` if neither exist.
*/
hasEither: function (object, key1, key2)
{
return object.hasOwnProperty(key1) || object.hasOwnProperty(key2);
},
/**
* The returned value sets what the property will be at the START of the particles life, on emit.
*
* @method Phaser.GameObjects.Particles.EmitterOp#defaultEmit
* @since 3.0.0
*
* @return {number} The new value of the property.
*/
defaultEmit: function ()
{
return this.defaultValue;
},
/**
* The returned value updates the property for the duration of the particles life.
*
* @method Phaser.GameObjects.Particles.EmitterOp#defaultUpdate
* @since 3.0.0
*
* @param {Phaser.GameObjects.Particles.Particle} particle - The particle.
* @param {string} key - The name of the property.
* @param {number} t - The current normalized lifetime of the particle, between 0 (birth) and 1 (death).
* @param {number} value - The current value of the property.
*
* @return {number} The new value of the property.
*/
defaultUpdate: function (particle, key, t, value)
{
return value;
},
/**
* The returned value sets what the property will be at the START of the particles life, on emit.
*
* This method is only used when you have provided a custom emit callback.
*
* @method Phaser.GameObjects.Particles.EmitterOp#proxyEmit
* @since 3.60.0
*
* @param {Phaser.GameObjects.Particles.Particle} particle - The particle.
* @param {string} key - The name of the property.
* @param {number} [value] - The current value of the property.
*
* @return {number} The new value of the property.
*/
proxyEmit: function (particle, key, value)
{
var result = this._onEmit(particle, key, value);
this.current = result;
return result;
},
/**
* The returned value updates the property for the duration of the particles life.
*
* This method is only used when you have provided a custom update callback.
*
* @method Phaser.GameObjects.Particles.EmitterOp#proxyUpdate
* @since 3.60.0
*
* @param {Phaser.GameObjects.Particles.Particle} particle - The particle.
* @param {string} key - The name of the property.
* @param {number} t - The current normalized lifetime of the particle, between 0 (birth) and 1 (death).
* @param {number} value - The current value of the property.
*
* @return {number} The new value of the property.
*/
proxyUpdate: function (particle, key, t, value)
{
var result = this._onUpdate(particle, key, t, value);
this.current = result;
return result;
},
/**
* An `onEmit` callback that returns the current value of the property.
*
* @method Phaser.GameObjects.Particles.EmitterOp#staticValueEmit
* @since 3.0.0
*
* @return {number} The current value of the property.
*/
staticValueEmit: function ()
{
return this.current;
},
/**
* An `onUpdate` callback that returns the current value of the property.
*
* @method Phaser.GameObjects.Particles.EmitterOp#staticValueUpdate
* @since 3.0.0
*
* @return {number} The current value of the property.
*/
staticValueUpdate: function ()
{
return this.current;
},
/**
* An `onEmit` callback that returns a random value from the current value array.
*
* @method Phaser.GameObjects.Particles.EmitterOp#randomStaticValueEmit
* @since 3.0.0
*
* @return {number} The new value of the property.
*/
randomStaticValueEmit: function ()
{
var randomIndex = Math.floor(Math.random() * this.propertyValue.length);
this.current = this.propertyValue[randomIndex];
return this.current;
},
/**
* An `onEmit` callback that returns a value between the {@link Phaser.GameObjects.Particles.EmitterOp#start} and
* {@link Phaser.GameObjects.Particles.EmitterOp#end} range.
*
* @method Phaser.GameObjects.Particles.EmitterOp#randomRangedValueEmit
* @since 3.0.0
*
* @param {Phaser.GameObjects.Particles.Particle} particle - The particle.
* @param {string} key - The key of the property.
*
* @return {number} The new value of the property.
*/
randomRangedValueEmit: function (particle, key)
{
var value = FloatBetween(this.start, this.end);
if (particle && particle.data[key])
{
particle.data[key].min = value;
particle.data[key].max = this.end;
}
this.current = value;
return value;
},
/**
* An `onEmit` callback that returns a value between the {@link Phaser.GameObjects.Particles.EmitterOp#start} and
* {@link Phaser.GameObjects.Particles.EmitterOp#end} range.
*
* @method Phaser.GameObjects.Particles.EmitterOp#randomRangedIntEmit
* @since 3.60.0
*
* @param {Phaser.GameObjects.Particles.Particle} particle - The particle.
* @param {string} key - The key of the property.
*
* @return {number} The new value of the property.
*/
randomRangedIntEmit: function (particle, key)
{
var value = Between(this.start, this.end);
if (particle && particle.data[key])
{
particle.data[key].min = value;
particle.data[key].max = this.end;
}
this.current = value;
return value;
},
/**
* An `onEmit` callback that returns a stepped value between the
* {@link Phaser.GameObjects.Particles.EmitterOp#start} and {@link Phaser.GameObjects.Particles.EmitterOp#end}
* range.
*
* @method Phaser.GameObjects.Particles.EmitterOp#steppedEmit
* @since 3.0.0
*
* @return {number} The new value of the property.
*/
steppedEmit: function ()
{
var current = this.counter;
var next = current;
var step = (this.end - this.start) / this.steps;
if (this.yoyo)
{
var over;
if (this.direction === 0)
{
// Add step to the current value
next += step;
if (next >= this.end)
{
over = next - this.end;
next = this.end - over;
this.direction = 1;
}
}
else
{
// Down
next -= step;
if (next <= this.start)
{
over = this.start - next;
next = this.start + over;
this.direction = 0;
}
}
this.counter = next;
}
else
{
this.counter = Wrap(next + step, this.start, this.end);
}
this.current = current;
return current;
},
/**
* An `onEmit` callback for an eased property.
*
* It prepares the particle for easing by {@link Phaser.GameObjects.Particles.EmitterOp#easeValueUpdate}.
*
* @method Phaser.GameObjects.Particles.EmitterOp#easedValueEmit
* @since 3.0.0
*
* @param {Phaser.GameObjects.Particles.Particle} particle - The particle.
* @param {string} key - The name of the property.
*
* @return {number} {@link Phaser.GameObjects.Particles.EmitterOp#start}, as the new value of the property.
*/
easedValueEmit: function (particle, key)
{
if (particle && particle.data[key])
{
var data = particle.data[key];
data.min = this.start;
data.max = this.end;
}
this.current = this.start;
return this.start;
},
/**
* An `onUpdate` callback that returns an eased value between the
* {@link Phaser.GameObjects.Particles.EmitterOp#start} and {@link Phaser.GameObjects.Particles.EmitterOp#end}
* range.
*
* @method Phaser.GameObjects.Particles.EmitterOp#easeValueUpdate
* @since 3.0.0
*
* @param {Phaser.GameObjects.Particles.Particle} particle - The particle.
* @param {string} key - The name of the property.
* @param {number} t - The current normalized lifetime of the particle, between 0 (birth) and 1 (death).
*
* @return {number} The new value of the property.
*/
easeValueUpdate: function (particle, key, t)
{
var data = particle.data[key];
var current;
var v = this.ease(t);
if (this.interpolation)
{
current = this.interpolation(this.start, v);
}
else
{
current = (data.max - data.min) * v + data.min;
}
this.current = current;
return current;
},
/**
* Destroys this EmitterOp instance and all of its references.
*
* Called automatically when the ParticleEmitter that owns this
* EmitterOp is destroyed.
*
* @method Phaser.GameObjects.Particles.EmitterOp#destroy
* @since 3.60.0
*/
destroy: function ()
{
this.propertyValue = null;
this.defaultValue = null;
this.ease = null;
this.interpolation = null;
this._onEmit = null;
this._onUpdate = null;
}
});
module.exports = EmitterOp;