phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
1,447 lines (1,303 loc) • 127 kB
JavaScript
/**
* @author Richard Davey <rich@phaser.io>
* @copyright 2013-2025 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var Class = require('../../utils/Class');
var Components = require('../components');
var ComponentsToJSON = require('../components/ToJSON');
var CopyFrom = require('../../geom/rectangle/CopyFrom');
var DeathZone = require('./zones/DeathZone');
var EdgeZone = require('./zones/EdgeZone');
var EmitterColorOp = require('./EmitterColorOp');
var EmitterOp = require('./EmitterOp');
var Events = require('./events');
var GameObject = require('../GameObject');
var GetFastValue = require('../../utils/object/GetFastValue');
var GetRandom = require('../../utils/array/GetRandom');
var GravityWell = require('./GravityWell');
var HasAll = require('../../utils/object/HasAll');
var HasAny = require('../../utils/object/HasAny');
var HasValue = require('../../utils/object/HasValue');
var Inflate = require('../../geom/rectangle/Inflate');
var List = require('../../structs/List');
var MergeRect = require('../../geom/rectangle/MergeRect');
var MergeRight = require('../../utils/object/MergeRight');
var Particle = require('./Particle');
var ParticleBounds = require('./ParticleBounds');
var RandomZone = require('./zones/RandomZone');
var Rectangle = require('../../geom/rectangle/Rectangle');
var RectangleToRectangle = require('../../geom/intersects/RectangleToRectangle');
var Remove = require('../../utils/array/Remove');
var Render = require('./ParticleEmitterRender');
var StableSort = require('../../utils/array/StableSort');
var TransformMatrix = require('../components/TransformMatrix');
var Vector2 = require('../../math/Vector2');
var Wrap = require('../../math/Wrap');
/**
* Names of simple configuration properties.
*
* @ignore
*/
var configFastMap = [
'active',
'advance',
'blendMode',
'colorEase',
'deathCallback',
'deathCallbackScope',
'duration',
'emitCallback',
'emitCallbackScope',
'follow',
'frequency',
'gravityX',
'gravityY',
'maxAliveParticles',
'maxParticles',
'name',
'emitting',
'particleBringToTop',
'particleClass',
'radial',
'sortCallback',
'sortOrderAsc',
'sortProperty',
'stopAfter',
'tintFill',
'timeScale',
'trackVisible',
'visible'
];
/**
* Names of complex configuration properties.
*
* @ignore
*/
var configOpMap = [
'accelerationX',
'accelerationY',
'alpha',
'angle',
'bounce',
'color',
'delay',
'hold',
'lifespan',
'maxVelocityX',
'maxVelocityY',
'moveToX',
'moveToY',
'quantity',
'rotate',
'scaleX',
'scaleY',
'speedX',
'speedY',
'tint',
'x',
'y'
];
/**
* @classdesc
* A Particle Emitter is a special kind of Game Object that controls a pool of {@link Phaser.GameObjects.Particles.Particle Particles}.
*
* Particle Emitters are created via a configuration object. The properties of this object
* can be specified in a variety of formats, given you plenty of scope over the values they
* return, leading to complex visual effects. Here are the different forms of configuration
* value you can give:
*
* ## An explicit static value:
*
* ```js
* x: 400
* ```
*
* The x value will always be 400 when the particle is spawned.
*
* ## A random value:
*
* ```js
* x: [ 100, 200, 300, 400 ]
* ```
*
* The x value will be one of the 4 elements in the given array, picked at random on emission.
*
* ## A custom callback:
*
* ```js
* x: (particle, key, t, value) => {
* return value + 50;
* }
* ```
*
* The x value is the result of calling this function. This is only used when the
* particle is emitted, so it provides it's initial starting value. It is not used
* when the particle is updated (see the onUpdate callback for that)
*
* ## A start / end object:
*
* This allows you to control the change in value between the given start and
* end parameters over the course of the particles lifetime:
*
* ```js
* scale: { start: 0, end: 1 }
* ```
*
* The particle scale will start at 0 when emitted and ease to a scale of 1
* over the course of its lifetime. You can also specify the ease function
* used for this change (the default is Linear):
*
* ```js
* scale: { start: 0, end: 1, ease: 'bounce.out' }
* ```
*
* ## A start / end random object:
*
* The start and end object can have an optional `random` parameter.
* This forces it to pick a random value between the two values and use
* this as the starting value, then easing to the 'end' parameter over
* its lifetime.
*
* ```js
* scale: { start: 4, end: 0.5, random: true }
* ```
*
* The particle will start with a random scale between 0.5 and 4 and then
* scale to the end value over its lifetime. You can combine the above
* with the `ease` parameter as well to control the value easing.
*
* ## An interpolation object:
*
* You can provide an array of values which will be used for interpolation
* during the particles lifetime. You can also define the interpolation
* function to be used. There are three provided: `linear` (the default),
* `bezier` and `catmull`, or you can provide your own function.
*
* ```js
* x: { values: [ 50, 500, 200, 800 ], interpolation: 'catmull' }
* ```
*
* The particle scale will interpolate from 50 when emitted to 800 via the other
* points over the course of its lifetime. You can also specify an ease function
* used to control the rate of change through the values (the default is Linear):
*
* ```js
* x: { values: [ 50, 500, 200, 800 ], interpolation: 'catmull', ease: 'bounce.out }
* ```
*
* ## A stepped emitter object:
*
* The `steps` parameter allows you to control the placement of sequential
* particles across the start-end range:
*
* ```js
* x: { steps: 32, start: 0, end: 576 }
* ```
*
* Here we have a range of 576 (start to end). This is divided into 32 steps.
*
* The first particle will emit at the x position of 0. The next will emit
* at the next 'step' along, which would be 18. The following particle will emit
* at the next step, which is 36, and so on. Because the range of 576 has been
* divided by 32, creating 18 pixels steps. When a particle reaches the 'end'
* value the next one will start from the beginning again.
*
* ## A stepped emitter object with yoyo:
*
* You can add the optional `yoyo` property to a stepped object:
*
* ```js
* x: { steps: 32, start: 0, end: 576, yoyo: true }
* ```
*
* As with the stepped emitter, particles are emitted in sequence, from 'start'
* to 'end' in step sized jumps. Normally, when a stepped emitter reaches the
* end it snaps around to the start value again. However, if you provide the 'yoyo'
* parameter then when it reaches the end it will reverse direction and start
* emitting back down to 'start' again. Depending on the effect you require this
* can often look better.
*
* ## A min / max object:
*
* This allows you to pick a random float value between the min and max properties:
*
* ```js
* x: { min: 100, max: 700 }
* ```
*
* The x value will be a random float between min and max.
*
* You can force it select an integer by setting the 'int' flag:
*
* ```js
* x: { min: 100, max: 700, int: true }
* ```
*
* Or, you could use the 'random' array approach (see below)
*
* ## A random object:
*
* This allows you to pick a random integer value between the first and second array elements:
*
* ```js
* x: { random: [ 100, 700 ] }
* ```
*
* The x value will be a random integer between 100 and 700 as it takes the first
* element in the 'random' array as the 'min' value and the 2nd element as the 'max' value.
*
* ## Custom onEmit and onUpdate callbacks:
*
* If the above won't give you the effect you're after, you can provide your own
* callbacks that will be used when the particle is both emitted and updated:
*
* ```js
* x: {
* onEmit: (particle, key, t, value) => {
* return value;
* },
* onUpdate: (particle, key, t, value) => {
* return value;
* }
* }
* ```
*
* You can provide either one or both functions. The `onEmit` is called at the
* start of the particles life and defines the value of the property on birth.
*
* The `onUpdate` function is called every time the Particle Emitter updates
* until the particle dies. Both must return a value.
*
* The properties are:
*
* particle - A reference to the Particle instance.
* key - The string based key of the property, i.e. 'x' or 'lifespan'.
* t - The current normalized lifetime of the particle, between 0 (birth) and 1 (death).
* value - The current property value. At a minimum you should return this.
*
* By using the above configuration options you have an unlimited about of
* control over how your particles behave.
*
* ## v3.55 Differences
*
* Prior to v3.60 Phaser used a `ParticleEmitterManager`. This was removed in v3.60
* and now calling `this.add.particles` returns a `ParticleEmitter` instance instead.
*
* In order to streamline memory and the display list we have removed the
* `ParticleEmitterManager` entirely. When you call `this.add.particles` you're now
* creating a `ParticleEmitter` instance, which is being added directly to the
* display list and can be manipulated just like any other Game Object, i.e.
* scaled, rotated, positioned, added to a Container, etc. It now extends the
* `GameObject` base class, meaning it's also an event emitter, which allowed us
* to create some handy new events for particles.
*
* So, to create an emitter, you now give it an xy coordinate, a texture and an
* emitter configuration object (you can also set this later, but most commonly
* you'd do it on creation). I.e.:
*
* ```js
* const emitter = this.add.particles(100, 300, 'flares', {
* frame: 'red',
* angle: { min: -30, max: 30 },
* speed: 150
* });
* ```
*
* This will create a 'red flare' emitter at 100 x 300.
*
* Please update your code to ensure it adheres to the new function signatures.
*
* @class ParticleEmitter
* @extends Phaser.GameObjects.GameObject
* @memberof Phaser.GameObjects.Particles
* @constructor
* @since 3.60.0
*
* @extends Phaser.GameObjects.Components.AlphaSingle
* @extends Phaser.GameObjects.Components.BlendMode
* @extends Phaser.GameObjects.Components.Depth
* @extends Phaser.GameObjects.Components.Mask
* @extends Phaser.GameObjects.Components.Pipeline
* @extends Phaser.GameObjects.Components.PostPipeline
* @extends Phaser.GameObjects.Components.ScrollFactor
* @extends Phaser.GameObjects.Components.Texture
* @extends Phaser.GameObjects.Components.Transform
* @extends Phaser.GameObjects.Components.Visible
*
* @param {Phaser.Scene} scene - The Scene to which this Game Object belongs. A Game Object can only belong to one Scene at a time.
* @param {number} [x] - The horizontal position of this Game Object in the world.
* @param {number} [y] - The vertical position of this Game Object in the world.
* @param {(string|Phaser.Textures.Texture)} [texture] - The key, or instance of the Texture this Game Object will use to render with, as stored in the Texture Manager.
* @param {Phaser.Types.GameObjects.Particles.ParticleEmitterConfig} [config] - Settings for this emitter.
*/
var ParticleEmitter = new Class({
Extends: GameObject,
Mixins: [
Components.AlphaSingle,
Components.BlendMode,
Components.Depth,
Components.Mask,
Components.Pipeline,
Components.PostPipeline,
Components.ScrollFactor,
Components.Texture,
Components.Transform,
Components.Visible,
Render
],
initialize:
function ParticleEmitter (scene, x, y, texture, config)
{
GameObject.call(this, scene, 'ParticleEmitter');
/**
* The Particle Class which will be emitted by this Emitter.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#particleClass
* @type {function}
* @default Phaser.GameObjects.Particles.Particle
* @since 3.0.0
* @see Phaser.Types.GameObjects.Particles.ParticleClassConstructor
*/
this.particleClass = Particle;
/**
* An internal object holding the configuration for the Emitter.
*
* These are populated as part of the Emitter configuration parsing.
*
* You typically do not access them directly, but instead use the
* `ParticleEmitter.setConfig` or `ParticleEmitter.updateConfig` methods.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#config
* @type {Phaser.Types.GameObjects.Particles.ParticleEmitterConfig}
* @since 3.85.0
*/
this.config = null;
/**
* An internal object holding all of the EmitterOp instances.
*
* These are populated as part of the Emitter configuration parsing.
*
* You typically do not access them directly, but instead use the
* provided getters and setters on this class, such as `ParticleEmitter.speedX` etc.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#ops
* @type {Phaser.Types.GameObjects.Particles.ParticleEmitterOps}
* @since 3.60.0
*/
this.ops = {
accelerationX: new EmitterOp('accelerationX', 0),
accelerationY: new EmitterOp('accelerationY', 0),
alpha: new EmitterOp('alpha', 1),
angle: new EmitterOp('angle', { min: 0, max: 360 }, true),
bounce: new EmitterOp('bounce', 0),
color: new EmitterColorOp('color'),
delay: new EmitterOp('delay', 0, true),
hold: new EmitterOp('hold', 0, true),
lifespan: new EmitterOp('lifespan', 1000, true),
maxVelocityX: new EmitterOp('maxVelocityX', 10000),
maxVelocityY: new EmitterOp('maxVelocityY', 10000),
moveToX: new EmitterOp('moveToX', 0),
moveToY: new EmitterOp('moveToY', 0),
quantity: new EmitterOp('quantity', 1, true),
rotate: new EmitterOp('rotate', 0),
scaleX: new EmitterOp('scaleX', 1),
scaleY: new EmitterOp('scaleY', 1),
speedX: new EmitterOp('speedX', 0, true),
speedY: new EmitterOp('speedY', 0, true),
tint: new EmitterOp('tint', 0xffffff),
x: new EmitterOp('x', 0),
y: new EmitterOp('y', 0)
};
/**
* A radial emitter will emit particles in all directions between angle min and max,
* using {@link Phaser.GameObjects.Particles.ParticleEmitter#speed} as the value. If set to false then this acts as a point Emitter.
* A point emitter will emit particles only in the direction derived from the speedX and speedY values.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#radial
* @type {boolean}
* @default true
* @since 3.0.0
* @see Phaser.GameObjects.Particles.ParticleEmitter#setRadial
*/
this.radial = true;
/**
* Horizontal acceleration applied to emitted particles, in pixels per second squared.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#gravityX
* @type {number}
* @default 0
* @since 3.0.0
* @see Phaser.GameObjects.Particles.ParticleEmitter#setGravity
*/
this.gravityX = 0;
/**
* Vertical acceleration applied to emitted particles, in pixels per second squared.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#gravityY
* @type {number}
* @default 0
* @since 3.0.0
* @see Phaser.GameObjects.Particles.ParticleEmitter#setGravity
*/
this.gravityY = 0;
/**
* Whether accelerationX and accelerationY are non-zero. Set automatically during configuration.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#acceleration
* @type {boolean}
* @default false
* @since 3.0.0
*/
this.acceleration = false;
/**
* Whether moveToX and moveToY are set. Set automatically during configuration.
*
* When true the particles move toward the moveToX and moveToY coordinates and arrive at the end of their life.
* Emitter angle, speedX, and speedY are ignored.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#moveTo
* @type {boolean}
* @default false
* @since 3.0.0
*/
this.moveTo = false;
/**
* A function to call when a particle is emitted.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#emitCallback
* @type {?Phaser.Types.GameObjects.Particles.ParticleEmitterCallback}
* @default null
* @since 3.0.0
*/
this.emitCallback = null;
/**
* The calling context for {@link Phaser.GameObjects.Particles.ParticleEmitter#emitCallback}.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#emitCallbackScope
* @type {?*}
* @default null
* @since 3.0.0
*/
this.emitCallbackScope = null;
/**
* A function to call when a particle dies.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#deathCallback
* @type {?Phaser.Types.GameObjects.Particles.ParticleDeathCallback}
* @default null
* @since 3.0.0
*/
this.deathCallback = null;
/**
* The calling context for {@link Phaser.GameObjects.Particles.ParticleEmitter#deathCallback}.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#deathCallbackScope
* @type {?*}
* @default null
* @since 3.0.0
*/
this.deathCallbackScope = null;
/**
* Set to hard limit the amount of particle objects this emitter is allowed to create
* in total. This is the number of `Particle` instances it can create, not the number
* of 'alive' particles.
*
* 0 means unlimited.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#maxParticles
* @type {number}
* @default 0
* @since 3.0.0
*/
this.maxParticles = 0;
/**
* The maximum number of alive and rendering particles this emitter will update.
* When this limit is reached, a particle needs to die before another can be emitted.
*
* 0 means no limits.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#maxAliveParticles
* @type {number}
* @default 0
* @since 3.60.0
*/
this.maxAliveParticles = 0;
/**
* If set, either via the Emitter config, or by directly setting this property,
* the Particle Emitter will stop emitting particles once this total has been
* reached. It will then enter a 'stopped' state, firing the `STOP`
* event. Note that entering a stopped state doesn't mean all the particles
* have finished, just that it's not emitting any further ones.
*
* To know when the final particle expires, listen for the COMPLETE event.
*
* Use this if you wish to launch an exact number of particles and then stop
* your emitter afterwards.
*
* The counter is reset each time the `ParticleEmitter.start` method is called.
*
* 0 means the emitter will not stop based on total emitted particles.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#stopAfter
* @type {number}
* @default 0
* @since 3.60.0
*/
this.stopAfter = 0;
/**
* The number of milliseconds this emitter will emit particles for when in flow mode,
* before it stops emission. A value of 0 (the default) means there is no duration.
*
* When the duration expires the `STOP` event is emitted. Note that entering a
* stopped state doesn't mean all the particles have finished, just that it's
* not emitting any further ones.
*
* To know when the final particle expires, listen for the COMPLETE event.
*
* The counter is reset each time the `ParticleEmitter.start` method is called.
*
* 0 means the emitter will not stop based on duration.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#duration
* @type {number}
* @default 0
* @since 3.60.0
*/
this.duration = 0;
/**
* For a flow emitter, the time interval (>= 0) between particle flow cycles in ms.
* A value of 0 means there is one particle flow cycle for each logic update (the maximum flow frequency). This is the default setting.
* For an exploding emitter, this value will be -1.
* Calling {@link Phaser.GameObjects.Particles.ParticleEmitter#flow} also puts the emitter in flow mode (frequency >= 0).
* Calling {@link Phaser.GameObjects.Particles.ParticleEmitter#explode} also puts the emitter in explode mode (frequency = -1).
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#frequency
* @type {number}
* @default 0
* @since 3.0.0
* @see Phaser.GameObjects.Particles.ParticleEmitter#setFrequency
*/
this.frequency = 0;
/**
* Controls if the emitter is currently emitting a particle flow (when frequency >= 0).
*
* Already alive particles will continue to update until they expire.
*
* Controlled by {@link Phaser.GameObjects.Particles.ParticleEmitter#start} and {@link Phaser.GameObjects.Particles.ParticleEmitter#stop}.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#emitting
* @type {boolean}
* @default true
* @since 3.0.0
*/
this.emitting = true;
/**
* Newly emitted particles are added to the top of the particle list, i.e. rendered above those already alive.
*
* Set to false to send them to the back.
*
* Also see the `sortOrder` property for more complex particle sorting.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#particleBringToTop
* @type {boolean}
* @default true
* @since 3.0.0
*/
this.particleBringToTop = true;
/**
* The time rate applied to active particles, affecting lifespan, movement, and tweens. Values larger than 1 are faster than normal.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#timeScale
* @type {number}
* @default 1
* @since 3.0.0
*/
this.timeScale = 1;
/**
* An array containing Particle Emission Zones. These can be either EdgeZones or RandomZones.
*
* Particles are emitted from a randomly selected zone from this array.
*
* Prior to Phaser v3.60 an Emitter could only have one single Emission Zone.
* In 3.60 they can now have an array of Emission Zones.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#emitZones
* @type {Phaser.Types.GameObjects.Particles.EmitZoneObject[]}
* @since 3.60.0
* @see Phaser.GameObjects.Particles.ParticleEmitter#setEmitZone
*/
this.emitZones = [];
/**
* An array containing Particle Death Zone objects. A particle is immediately killed as soon as its x/y coordinates
* intersect with any of the configured Death Zones.
*
* Prior to Phaser v3.60 an Emitter could only have one single Death Zone.
* In 3.60 they can now have an array of Death Zones.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#deathZones
* @type {Phaser.GameObjects.Particles.Zones.DeathZone[]}
* @since 3.60.0
* @see Phaser.GameObjects.Particles.ParticleEmitter#setDeathZone
*/
this.deathZones = [];
/**
* An optional Rectangle object that is used during rendering to cull Particles from
* display. For example, if your particles are limited to only move within a 300x300
* sized area from their origin, then you can set this Rectangle to those dimensions.
*
* The renderer will check to see if the `viewBounds` Rectangle intersects with the
* Camera bounds during the render step and if not it will skip rendering the Emitter
* entirely.
*
* This allows you to create many emitters in a Scene without the cost of
* rendering if the contents aren't visible.
*
* Note that the Emitter will not perform any checks to see if the Particles themselves
* are outside of these bounds, or not. It will simply check the bounds against the
* camera. Use the `getBounds` method with the `advance` parameter to help define
* the location and placement of the view bounds.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#viewBounds
* @type {?Phaser.Geom.Rectangle}
* @default null
* @since 3.60.0
* @see Phaser.GameObjects.Particles.ParticleEmitter#setViewBounds
*/
this.viewBounds = null;
/**
* A Game Object whose position is used as the particle origin.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#follow
* @type {?Phaser.Types.Math.Vector2Like}
* @default null
* @since 3.0.0
* @see Phaser.GameObjects.Particles.ParticleEmitter#startFollow
* @see Phaser.GameObjects.Particles.ParticleEmitter#stopFollow
*/
this.follow = null;
/**
* The offset of the particle origin from the {@link Phaser.GameObjects.Particles.ParticleEmitter#follow} target.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#followOffset
* @type {Phaser.Math.Vector2}
* @since 3.0.0
* @see Phaser.GameObjects.Particles.ParticleEmitter#startFollow
*/
this.followOffset = new Vector2();
/**
* Whether the emitter's {@link Phaser.GameObjects.Particles.ParticleEmitter#visible} state will track
* the {@link Phaser.GameObjects.Particles.ParticleEmitter#follow} target's visibility state.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#trackVisible
* @type {boolean}
* @default false
* @since 3.0.0
* @see Phaser.GameObjects.Particles.ParticleEmitter#startFollow
*/
this.trackVisible = false;
/**
* The texture frames assigned to particles.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#frames
* @type {Phaser.Textures.Frame[]}
* @since 3.0.0
*/
this.frames = [];
/**
* Whether texture {@link Phaser.GameObjects.Particles.ParticleEmitter#frames} are selected at random.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#randomFrame
* @type {boolean}
* @default true
* @since 3.0.0
* @see Phaser.GameObjects.Particles.ParticleEmitter#setEmitterFrame
*/
this.randomFrame = true;
/**
* The number of consecutive particles that receive a single texture frame (per frame cycle).
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#frameQuantity
* @type {number}
* @default 1
* @since 3.0.0
* @see Phaser.GameObjects.Particles.ParticleEmitter#setEmitterFrame
*/
this.frameQuantity = 1;
/**
* The animations assigned to particles.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#anims
* @type {string[]}
* @since 3.60.0
*/
this.anims = [];
/**
* Whether animations {@link Phaser.GameObjects.Particles.ParticleEmitter#anims} are selected at random.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#randomAnim
* @type {boolean}
* @default true
* @since 3.60.0
* @see Phaser.GameObjects.Particles.ParticleEmitter#setAnim
*/
this.randomAnim = true;
/**
* The number of consecutive particles that receive a single animation (per frame cycle).
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#animQuantity
* @type {number}
* @default 1
* @since 3.60.0
* @see Phaser.GameObjects.Particles.ParticleEmitter#setAnim
*/
this.animQuantity = 1;
/**
* An array containing all currently inactive Particle instances.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#dead
* @type {Phaser.GameObjects.Particles.Particle[]}
* @private
* @since 3.0.0
*/
this.dead = [];
/**
* An array containing all currently live and rendering Particle instances.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#alive
* @type {Phaser.GameObjects.Particles.Particle[]}
* @private
* @since 3.0.0
*/
this.alive = [];
/**
* Internal array that holds counter data:
*
* 0 - flowCounter - The time until next flow cycle.
* 1 - frameCounter - Counts up to {@link Phaser.GameObjects.Particles.ParticleEmitter#frameQuantity}.
* 2 - animCounter - Counts up to animQuantity.
* 3 - elapsed - The time remaining until the `duration` limit is reached.
* 4 - stopCounter - The number of particles remaining until `stopAfter` limit is reached.
* 5 - completeFlag - Has the COMPLETE event been emitted?
* 6 - zoneIndex - The emit zone index counter.
* 7 - zoneTotal - The emit zone total counter.
* 8 - currentFrame - The current texture frame, as an index of {@link Phaser.GameObjects.Particles.ParticleEmitter#frames}.
* 9 - currentAnim - The current animation, as an index of {@link Phaser.GameObjects.Particles.ParticleEmitter#anims}.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#counters
* @type {Float32Array}
* @private
* @since 3.60.0
*/
this.counters = new Float32Array(10);
/**
* An internal property used to tell when the emitter is in fast-forwarc mode.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#skipping
* @type {boolean}
* @default true
* @since 3.60.0
*/
this.skipping = false;
/**
* An internal Transform Matrix used to cache this emitters world matrix.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#worldMatrix
* @type {Phaser.GameObjects.Components.TransformMatrix}
* @since 3.60.0
*/
this.worldMatrix = new TransformMatrix();
/**
* Optionally sort the particles before they render based on this
* property. The property must exist on the `Particle` class, such
* as `y`, `lifeT`, `scaleX`, etc.
*
* When set this overrides the `particleBringToTop` setting.
*
* To reset this and disable sorting, so this property to an empty string.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#sortProperty
* @type {string}
* @since 3.60.0
*/
this.sortProperty = '';
/**
* When `sortProperty` is defined this controls the sorting order,
* either ascending or descending. Toggle to control the visual effect.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#sortOrderAsc
* @type {boolean}
* @since 3.60.0
*/
this.sortOrderAsc = true;
/**
* The callback used to sort the particles. Only used if `sortProperty`
* has been set. Set this via the `setSortCallback` method.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#sortCallback
* @type {?Phaser.Types.GameObjects.Particles.ParticleSortCallback}
* @since 3.60.0
*/
this.sortCallback = this.depthSortCallback;
/**
* A list of Particle Processors being managed by this Emitter.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#processors
* @type {Phaser.Structs.List.<Phaser.GameObjects.Particles.ParticleProcessor>}
* @since 3.60.0
*/
this.processors = new List(this);
/**
* The tint fill mode used by the Particles in this Emitter.
*
* `false` = An additive tint (the default), where vertices colors are blended with the texture.
* `true` = A fill tint, where the vertices colors replace the texture, but respects texture alpha.
*
* @name Phaser.GameObjects.Particles.ParticleEmitter#tintFill
* @type {boolean}
* @default false
* @since 3.60.0
*/
this.tintFill = false;
this.initPipeline();
this.initPostPipeline();
this.setPosition(x, y);
this.setTexture(texture);
if (config)
{
this.setConfig(config);
}
},
// Overrides Game Object method
addedToScene: function ()
{
this.scene.sys.updateList.add(this);
},
// Overrides Game Object method
removedFromScene: function ()
{
this.scene.sys.updateList.remove(this);
},
/**
* Takes an Emitter Configuration file and resets this Emitter, using any
* properties defined in the config to then set it up again.
*
* @method Phaser.GameObjects.Particles.ParticleEmitter#setConfig
* @since 3.60.0
*
* @param {Phaser.Types.GameObjects.Particles.ParticleEmitterConfig} config - Settings for this emitter.
*
* @return {this} This Particle Emitter.
*/
setConfig: function (config)
{
if (!config)
{
return this;
}
this.config = config;
var i = 0;
var key = '';
var ops = this.ops;
for (i = 0; i < configOpMap.length; i++)
{
key = configOpMap[i];
ops[key].loadConfig(config);
}
for (i = 0; i < configFastMap.length; i++)
{
key = configFastMap[i];
// Only update properties from their current state if they exist in the given config
if (HasValue(config, key))
{
this[key] = GetFastValue(config, key);
}
}
this.acceleration = (this.accelerationX !== 0 || this.accelerationY !== 0);
this.moveTo = HasAll(config, [ 'moveToX', 'moveToY' ]);
// Special 'speed' override
if (HasValue(config, 'speed'))
{
ops.speedX.loadConfig(config, 'speed');
ops.speedY.active = false;
}
// If you specify speedX, speedY or moveTo then it changes the emitter from radial to a point emitter
if (HasAny(config, [ 'speedX', 'speedY' ]) || this.moveTo)
{
this.radial = false;
}
// Special 'scale' override
if (HasValue(config, 'scale'))
{
ops.scaleX.loadConfig(config, 'scale');
ops.scaleY.active = false;
}
if (HasValue(config, 'callbackScope'))
{
var callbackScope = GetFastValue(config, 'callbackScope', null);
this.emitCallbackScope = callbackScope;
this.deathCallbackScope = callbackScope;
}
if (HasValue(config, 'emitZone'))
{
this.addEmitZone(config.emitZone);
}
if (HasValue(config, 'deathZone'))
{
this.addDeathZone(config.deathZone);
}
if (HasValue(config, 'bounds'))
{
var bounds = this.addParticleBounds(config.bounds);
bounds.collideLeft = GetFastValue(config, 'collideLeft', true);
bounds.collideRight = GetFastValue(config, 'collideRight', true);
bounds.collideTop = GetFastValue(config, 'collideTop', true);
bounds.collideBottom = GetFastValue(config, 'collideBottom', true);
}
if (HasValue(config, 'followOffset'))
{
this.followOffset.setFromObject(GetFastValue(config, 'followOffset', 0));
}
if (HasValue(config, 'texture'))
{
this.setTexture(config.texture);
}
if (HasValue(config, 'frame'))
{
this.setEmitterFrame(config.frame);
}
else if (HasValue(config, 'anim'))
{
this.setAnim(config.anim);
}
if (HasValue(config, 'reserve'))
{
this.reserve(config.reserve);
}
if (HasValue(config, 'advance'))
{
this.fastForward(config.advance);
}
this.resetCounters(this.frequency, this.emitting);
if (this.emitting)
{
this.emit(Events.START, this);
}
return this;
},
/**
* Takes an existing Emitter Configuration file and updates this Emitter.
* Existing properties are overriden while new properties are added. The
* updated configuration is then passed to the `setConfig` method to reset
* the Emitter with the updated configuration.
*
* @method Phaser.GameObjects.Particles.ParticleEmitter#updateConfig
* @since 3.85.0
*
* @param {Phaser.Types.GameObjects.Particles.ParticleEmitterConfig} config - Settings for this emitter.
*
* @return {this} This Particle Emitter.
*/
updateConfig: function (config)
{
if (config)
{
if (!this.config)
{
this.setConfig(config);
}
else
{
this.setConfig(MergeRight(this.config, config));
}
}
return this;
},
/**
* Creates a description of this emitter suitable for JSON serialization.
*
* @method Phaser.GameObjects.Particles.ParticleEmitter#toJSON
* @since 3.0.0
*
* @return {Phaser.Types.GameObjects.JSONGameObject} A JSON representation of the Game Object.
*/
toJSON: function ()
{
var output = ComponentsToJSON(this);
var i = 0;
var key = '';
for (i = 0; i < configFastMap.length; i++)
{
key = configFastMap[i];
output[key] = this[key];
}
var ops = this.ops;
for (i = 0; i < configOpMap.length; i++)
{
key = configOpMap[i];
if (ops[key])
{
output[key] = ops[key].toJSON();
}
}
// special handlers
if (!ops.speedY.active)
{
delete output.speedX;
output.speed = ops.speedX.toJSON();
}
if (this.scaleX === this.scaleY)
{
delete output.scaleX;
delete output.scaleY;
output.scale = ops.scaleX.toJSON();
}
return output;
},
/**
* Resets the internal counter trackers.
*
* You shouldn't ever need to call this directly.
*
* @method Phaser.GameObjects.Particles.ParticleEmitter#resetCounters
* @since 3.60.0
*
* @param {number} frequency - The frequency counter.
* @param {boolean} on - Set the complete flag.
*/
resetCounters: function (frequency, on)
{
var counters = this.counters;
counters.fill(0);
counters[0] = frequency;
if (on)
{
counters[5] = 1;
}
},
/**
* Continuously moves the particle origin to follow a Game Object's position.
*
* @method Phaser.GameObjects.Particles.ParticleEmitter#startFollow
* @since 3.0.0
*
* @param {Phaser.Types.Math.Vector2Like} target - The Object to follow.
* @param {number} [offsetX=0] - Horizontal offset of the particle origin from the Game Object.
* @param {number} [offsetY=0] - Vertical offset of the particle origin from the Game Object.
* @param {boolean} [trackVisible=false] - Whether the emitter's visible state will track the target's visible state.
*
* @return {this} This Particle Emitter.
*/
startFollow: function (target, offsetX, offsetY, trackVisible)
{
if (offsetX === undefined) { offsetX = 0; }
if (offsetY === undefined) { offsetY = 0; }
if (trackVisible === undefined) { trackVisible = false; }
this.follow = target;
this.followOffset.set(offsetX, offsetY);
this.trackVisible = trackVisible;
return this;
},
/**
* Stops following a Game Object.
*
* @method Phaser.GameObjects.Particles.ParticleEmitter#stopFollow
* @since 3.0.0
*
* @return {this} This Particle Emitter.
*/
stopFollow: function ()
{
this.follow = null;
this.followOffset.set(0, 0);
this.trackVisible = false;
return this;
},
/**
* Chooses a texture frame from {@link Phaser.GameObjects.Particles.ParticleEmitter#frames}.
*
* @method Phaser.GameObjects.Particles.ParticleEmitter#getFrame
* @since 3.0.0
*
* @return {Phaser.Textures.Frame} The texture frame.
*/
getFrame: function ()
{
var frames = this.frames;
var len = frames.length;
var current;
if (len === 1)
{
current = frames[0];
}
else if (this.randomFrame)
{
current = GetRandom(frames);
}
else
{
current = frames[this.currentFrame];
this.frameCounter++;
if (this.frameCounter === this.frameQuantity)
{
this.frameCounter = 0;
this.currentFrame++;
if (this.currentFrame === len)
{
this.currentFrame = 0;
}
}
}
return this.texture.get(current);
},
/**
* Sets a pattern for assigning texture frames to emitted particles. The `frames` configuration can be any of:
*
* frame: 0
* frame: 'red'
* frame: [ 0, 1, 2, 3 ]
* frame: [ 'red', 'green', 'blue', 'pink', 'white' ]
* frame: { frames: [ 'red', 'green', 'blue', 'pink', 'white' ], [cycle: bool], [quantity: int] }
*
* @method Phaser.GameObjects.Particles.ParticleEmitter#setEmitterFrame
* @since 3.0.0
*
* @param {(array|string|number|Phaser.Types.GameObjects.Particles.ParticleEmitterFrameConfig)} frames - One or more texture frames, or a configuration object.
* @param {boolean} [pickRandom=true] - Whether frames should be assigned at random from `frames`.
* @param {number} [quantity=1] - The number of consecutive particles that will receive each frame.
*
* @return {this} This Particle Emitter.
*/
setEmitterFrame: function (frames, pickRandom, quantity)
{
if (pickRandom === undefined) { pickRandom = true; }
if (quantity === undefined) { quantity = 1; }
this.randomFrame = pickRandom;
this.frameQuantity = quantity;
this.currentFrame = 0;
var t = typeof (frames);
this.frames.length = 0;
if (Array.isArray(frames))
{
this.frames = this.frames.concat(frames);
}
else if (t === 'string' || t === 'number')
{
this.frames.push(frames);
}
else if (t === 'object')
{
var frameConfig = frames;
frames = GetFastValue(frameConfig, 'frames', null);
if (frames)
{
this.frames = this.frames.concat(frames);
}
var isCycle = GetFastValue(frameConfig, 'cycle', false);
this.randomFrame = (isCycle) ? false : true;
this.frameQuantity = GetFastValue(frameConfig, 'quantity', quantity);
}
if (this.frames.length === 1)
{
this.frameQuantity = 1;
this.randomFrame = false;
}
return this;
},
/**
* Chooses an animation from {@link Phaser.GameObjects.Particles.ParticleEmitter#anims}, if populated.
*
* @method Phaser.GameObjects.Particles.ParticleEmitter#getAnim
* @since 3.60.0
*
* @return {string} The animation to play, or `null` if there aren't any.
*/
getAnim: function ()
{
var anims = this.anims;
var len = anims.length;
if (len === 0)
{
return null;
}
else if (len === 1)
{
return anims[0];
}
else if (this.randomAnim)
{
return GetRandom(anims);
}
else
{
var anim = anims[this.currentAnim];
this.animCounter++;
if (this.animCounter >= this.animQuantity)
{
this.animCounter = 0;
this.currentAnim = Wrap(this.currentAnim + 1, 0, len);
}
return anim;
}
},
/**
* Sets a pattern for assigning animations to emitted particles. The `anims` configuration can be any of:
*
* anim: 'red'
* anim: [ 'red', 'green', 'blue', 'pink', 'white' ]
* anim: { anims: [ 'red', 'green', 'blue', 'pink', 'white' ], [cycle: bool], [quantity: int] }
*
* Call this method at least once before any particles are created, or set `anim` in the Particle Emitter's configuration when creating the Emitter.
*
* @method Phaser.GameObjects.Particles.ParticleEmitter#setAnim
* @since 3.60.0
*
* @param {(string|string[]|Phaser.Types.GameObjects.Particles.ParticleEmitterAnimConfig)} anims - One or more animations, or a configuration object.
* @param {boolean} [pickRandom=true] - Whether animations should be assigned at random from `anims`. If a config object is given, this parameter is ignored.
* @param {number} [quantity=1] - The number of consecutive particles that will receive each animation. If a config object is given, this parameter is ignored.
*
* @return {this} This Particle Emitter.
*/
setAnim: function (anims, pickRandom, quantity)
{
if (pickRandom === undefined) { pickRandom = true; }
if (quantity === undefined) { quantity = 1; }
this.randomAnim = pickRandom;
this.animQuantity = quantity;
this.currentAnim = 0;
var t = typeof (anims);
this.anims.length = 0;
if (Array.isArray(anims))
{
this.anims = this.anims.concat(anims);
}
else if (t === 'string')
{
this.anims.push(anims);
}
else if (t === 'object')
{
var animConfig = anims;
anims = GetFastValue(animConfig, 'anims', null);
if (anims)
{
this.anims = this.anims.concat(anims);
}
var isCycle = GetFastValue(animConfig, 'cycle', false);
this.randomAnim = (isCycle) ? false : true;
this.animQuantity = GetFastValue(animConfig, 'quantity', quantity);
}
if (this.anims.length === 1)
{
this.animQuantity = 1;
this.randomAnim = false;
}
return this;
},
/**
* Turns {@link Phaser.GameObjects.Particles.ParticleEmitter#radial} particle movement on or off.
*
* @method Phaser.GameObjects.Particles.ParticleEmitter#setRadial
* @since 3.0.0
*
* @param {boolean} [value=tru